diff --git a/.LSAN_suppr.txt b/.LSAN_suppr.txt index 6384b856e..92813ffca 100644 --- a/.LSAN_suppr.txt +++ b/.LSAN_suppr.txt @@ -5,3 +5,6 @@ # https://github.com/DrylandEcology/SOILWAT2/issues/205 leak:SW_VPD_construct + +# on Apple arm64: https://github.com/google/sanitizers/issues/1501 +leak:realizeClassWithoutSwift diff --git a/.github/workflows/check_doc.yml b/.github/workflows/check_doc.yml index 723d4cc20..111cc0701 100644 --- a/.github/workflows/check_doc.yml +++ b/.github/workflows/check_doc.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Checkout repository and submodules - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: submodules: recursive @@ -24,4 +24,4 @@ jobs: sudo apt-get install doxygen doxygen-latex graphviz - name: Build documentation and check for warnings - run: make clean doc + run: make clean clean_doc doc diff --git a/.github/workflows/main_nix.yml b/.github/workflows/main_nix.yml index da0e8091f..4e6c43890 100644 --- a/.github/workflows/main_nix.yml +++ b/.github/workflows/main_nix.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Checkout repository and submodules - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: submodules: recursive @@ -29,20 +29,20 @@ jobs: - name: Run binary run: | - make clean bin bint_run - make clean bin_debug_severe bint_run + make clean bin_run + make clean bin_debug_severe - - name: Unit tests - run: make clean test test_run + - name: Unit tests (shuffle and repeat 3x) + run: make clean test_rep3rnd - name: Severe unit tests (without sanitizers) if: ${{ runner.os == 'macOS' }} - run: make clean test_severe test_run + run: make clean test_severe - name: Severe unit tests (with sanitizers) # Apple clang does not support "AddressSanitizer: detect_leaks" (at least as of clang-1200.0.32.29) if: ${{ runner.os != 'macOS' }} - run: ASAN_OPTIONS=detect_leaks=1:strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1 LSAN_OPTIONS=suppressions=.LSAN_suppr.txt make clean test_severe test_run + run: make clean test_leaks check_code_coverage: @@ -50,14 +50,15 @@ jobs: steps: - name: Checkout repository and submodules - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: submodules: recursive - name: Generate coverage report - run: make clean cov cov_run + run: CC=gcc CXX=g++ GCOV=gcov make clean clean_cov cov - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 with: fail_ci_if_error: false # just report, don't fail checks + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/main_win.yml b/.github/workflows/main_win.yml index 97b5424ac..67974db47 100644 --- a/.github/workflows/main_win.yml +++ b/.github/workflows/main_win.yml @@ -26,12 +26,12 @@ jobs: shell: bash - name: Checkout repository and submodules - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: submodules: recursive - name: Install cygwin (windows) - uses: cygwin/cygwin-install-action@v1 + uses: cygwin/cygwin-install-action@v3 with: packages: gcc-core gcc-g++ make @@ -40,7 +40,7 @@ jobs: # AddressSanitizer and LeakSanitizer not available on cygwin - name: Run binary - run: make clean bin bint_run + run: make clean bin_run - - name: Unit tests - run: make clean test test_run + - name: Unit tests (shuffle and repeat 3x) + run: make clean test_rep3rnd diff --git a/.gitignore b/.gitignore index 2ce2a2243..8ee1176be 100644 --- a/.gitignore +++ b/.gitignore @@ -14,8 +14,9 @@ /Debug/ # Test project output -/testing/Output/* -/testing/Output_* +/tests/example/Output/* +/tests/example/Output_* + # Figures created by scripts in tools/ /tools/Fig* @@ -37,9 +38,11 @@ doc/html/* doc/log_doxygen.log doc/Doxyfile.bak -# Binary files +# Build and binary directories and files +bin/* +build/* SOILWAT2 -testing/SOILWAT2 +tests/example/SOILWAT2 libSOILWAT2* libcovSOILWAT2* diff --git a/.gitmodules b/.gitmodules index cad0b4e19..8d43eccfa 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,8 +1,8 @@ [submodule "googletest"] - path = googletest + path = external/googletest url = https://github.com/google/googletest branch = main [submodule "pcg"] - path = pcg + path = external/pcg url = https://github.com/imneme/pcg-c-basic branch = master diff --git a/NEWS.md b/NEWS.md index 8910117ff..e395c2a2c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,120 @@ +# NEWS + +# SOILWAT2 v7.0.0 +* This version produces nearly identical simulation output + as the previous release under default values for the new inputs. + Small deviations arise due to a fix in the handling of soil moisture values + when between field capacity and saturation. + +* Multiple soil water release curves (`SWRC`) are now implemented and can be + selected with new input `swrc_name`. Implemented `SWRCs` currently include + `"Campbell1974"`, `"vanGenuchten1980"`, and `"FXW"`. New input `has_swrcp` + determines if parameters for a `SWRC` are estimated at run-time via an + implemented pedotransfer function (`PTF`) based on new input `ptf_name` or + if they are provided as inputs via new input file `"swrc_params.in"`. + `rSOILWAT2` implements additional pedotransfer functions. See documentation + entry of `"swrc_ptf"` for additional details and for guidance on how to + implement additional `SWRCs` and `PTFs` (issue #315; @dschlaep). + +* Soil density inputs can now represent either matric or bulk density + (issue #280; @dschlaep). + * Automatic conversion between matric and bulk density as needed + using the new input `type_soilDensityInput`. + +* Daily weather inputs that force a simulation are now processed + all at once; previously, values were processed for one year at a time during + the main simulation loop (issue #311; @dschlaep, @N1ckP3rsl3y). + * Daily weather inputs are now obtained by `readAllWeather()` + via `SW_WTH_read()` during `SW_CTL_read_inputs_from_disk()`, i.e., + the same time as other inputs are read from files. + * Then, weather values are "finalized", i.e., missing values are imputed + (e.g., by the weather generator) and scaled with monthly parameters, + by `finalizeAllWeather()` via `SW_WTH_finalize_all_weather()`; + this must occur before the simulation is "initialized" + by `SW_CTL_init_run()`. + * Imputation of missing daily weather values by `generateMissingWeather()` + can be done either by last-observation-carried-forward `"LOCF"` + (which handles all daily weather variables) or by the Markov weather + generator (which handles only temperature and precipitation). + +* Daily weather inputs, in addition to the previous variables + maximum air temperature, minimum air temperature, and precipitation amount, + can now process the following variables (issue #341; @dschlaep, @N1ckP3rsl3y): + * Cloud cover (can be replaced by shortwave radiation) + * Wind speed (can be replaced by wind components) + * Wind speed eastward component (optional) + * Wind speed northward component (optional) + * Relative humidity (can be replaced by max/min humidity, specific humidity, + dew point temperature, or vapor pressure) + * Maximum relative humidity (optional) + * Minimum relative humidity (optional) + * Specific humidity (optional) + * Dew point temperature (optional) + * Actual vapor pressure (optional) + * Downward surface shortwave radiation (optional) + +* SOILWAT2 gains the ability to calculate long-term climate summaries + (issue #317; @N1ckP3rsl3y, @dschlaep). + * New `calcSiteClimate()` calculates monthly and annual time + series of climate variables from daily weather. + * New `averageClimateAcrossYears()` calculates long-term climate summaries. + * Both functions are based on `rSOILWAT2::calc_SiteClimate()` + which was previously coded in R. + * This version fixes issues from the previous R version: + * Mean annual temperature is now the mean across years of + means across days within year of mean daily temperature. + * Years at locations in the southern hemisphere are now adjusted to start + on July 1 of the previous calendar year. + * The cheatgrass-related variables, i.e., `Month7th_PPT_mm`, + `MeanTemp_ofDriestQuarter_C`, and `MinTemp_of2ndMonth_C`, + are now adjusted for location by hemisphere. + +* SOILWAT2 gains the ability to estimate fractional land cover + representing a potential natural vegetation based on climate relationships + (using new input `veg_method`) instead of reading land cover values + from input files (issue #318; @N1ckP3rsl3y, @dschlaep). + * New `estimatePotNatVegComposition()` estimates + fractional land cover representing a potential natural vegetation + based on climate relationships. + This function is based on `rSOILWAT2::estimate_PotNatVeg_composition()` + which was previously coded in R. + * New `estimateVegetationFromClimate()` + (which is called by `SW_VPD_init_run()`) uses `veg_method` to determine + at run time if a simulation utilizes `averageClimateAcrossYears()` and + `estimatePotNatVegComposition()` to set land cover values + instead of using the cover values provided in the input file. + * This version fixes issues from the previous R version: + * The `C4` grass correction based on Teeri & Stowe 1976 is now applied + as documented (`rSOILWAT2` issue #218). + * The sum of all grass components, if fixed, is now incorporated into + the total sum of all fixed components (`rSOILWAT2` issue #219). + + +## Changes to inputs +* New inputs via `"weathsetup.in"` determine whether monthly or daily inputs + for cloud cover, relative humidity, and wind speed are utilized; + describe which daily weather variables are contained in the weather input + files `weath.YYYY`; and describe units of (optional) input shortwave + radiation. +* New (optional) variables (columns) in weather input files `weath.YYYY` that + are described via `"weathsetup.in"`. +* New inputs via `"siteparam.in"` select a soil water release curve `swrc_name` + and determine parameter source `has_swrcp`, i.e., + either estimated via selected pedotransfer function `ptf_name` or + values obtained from new input file `"swrc_params.in"`. + Default values `"Campbell1974"`, `"Cosby1984AndOthers"`, and 0 + (i.e., use `PTF` to estimate paramaters) reproduce previous behavior. +* New input file `"swrc_params.in"` to provide parameters of the selected + soil water release curve (if not estimated via a pedotransfer function) + for each soil layer. +* SOILWAT2 gains `type_soilDensityInput` as new user input (`siteparam.in`) + with default value 0 (i.e., matric soil density) + that reproduces previous behavior. +* SOILWAT2 gains `veg_method` as new user input (`"veg.in"`) + with default value 0 (i.e., land cover are obtained from input files) + that reproduces previous behavior. + + # SOILWAT2 v6.7.0 * This version produces exactly the same simulation output as the previous release under default values @@ -16,15 +133,20 @@ # SOILWAT2 v6.6.0 -* Random number generators now produce sequences that can be exactly reproduced. -* `RandSeed()` gains arguments "initstate" and "initseq" (and lost "seed") to - fully seed a `pcg32` random number generator. -* `RandNorm()` is now re-entrant and discards one of the two generated values. - Compilation with `"RANDNORMSTATIC"` re-produces the old, not re-entrant - implementation. +* Random number generators now produce sequences that can be exactly reproduced + (@dschlaep). + * `RandSeed()` gains arguments "initstate" and "initseq" (and lost "seed") + to fully seed a `pcg32` random number generator. + * `RandNorm()` is now re-entrant and discards one of the two generated + values. Compilation with `"RANDNORMSTATIC"` re-produces the old, + not re-entrant implementation. + * `SW_MKV_construct()` now only seeds `markov_rng` (the random number + generator of the weather generator) if run as `SOILWAT2` using the new + input `rng_seed`; `SW_MKV_construct()` does not seed `markov_rng` + when run as part of `STEPWAT2` or `rSOILWAT2` + (both of which use their own `RNG` initialization procedures). +* `SW_WTH_init_run()` now also initializes yesterday's weather values + (@dschlaep). + +## Changes to inputs * SOILWAT2 gains `rng_seed` as new user input (`"weathsetup.in"`). -* `SW_MKV_construct()` now only seeds `markov_rng` (the random number generator - of the weather generator) if run as `SOILWAT2` using the new input `rng_seed`; - `SW_MKV_construct()` does not seed `markov_rng` when run as part of `STEPWAT2` - or `rSOILWAT2` (both of which use their own `RNG` initialization procedures). -* `SW_WTH_init_run()` now also initializes yesterday's weather values. diff --git a/README.md b/README.md index 946c38cd8..48995d3ed 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ A full code documentation may be built, see [here](#get_documentation). - for unit tests (using `googletest`), additionally, - `g++ >= v5.0` or `clang++ >= v5.0` compliant with `C++11` - `POSIX API` - - POSIX- or GNU-compliant `make` + - GNU-compliant `make` - On Windows OS: an installation of `cygwin` * Clone the repository @@ -239,15 +239,15 @@ __Integration tests__ We use integration tests to check that the entire simulation model works as expected when used in a real-world application setting. -The folder `testing/` contains all necessary inputs to run `SOILWAT2` +The folder `tests/example/` contains all necessary inputs to run `SOILWAT2` for one generic location (it is a relatively wet and cool site in the sagebrush steppe). ```{.sh} - make bin bint_run + make bin_run ``` -The simulated output is stored at `testing/Output/`. +The simulated output is stored at `tests/example/Output/`. Another use case is to compare output of a new (development) branch to output @@ -262,16 +262,16 @@ The following steps provide a starting point for such comparisons: ```{.sh} # Simulate on refernce branch and copy output to "Output_ref" git checkout master - make bin bint_run - cp -r testing/Output testing/Output_ref + make bin_run + cp -r tests/example/Output tests/example/Output_ref # Switch to development branch and run the same simulation git checkout - make bin bint_run + make bin_run # Compare the two sets of outputs # * Lists all output files and determine if they are exactly they same - diff testing/Output/ testing/Output_ref/ -qs + diff tests/example/Output/ tests/example/Output_ref/ -qs ``` @@ -289,17 +289,17 @@ Currently, the following is implemented: and some slope/aspect/day of year combinations ```{.sh} - CPPFLAGS=-DSW2_SolarPosition_Test__hourangles_by_lat_and_doy make test test_run + CPPFLAGS=-DSW2_SolarPosition_Test__hourangles_by_lat_and_doy make test_run Rscript tools/plot__SW2_SolarPosition_Test__hourangles_by_lat_and_doy.R - CPPFLAGS=-DSW2_SolarPosition_Test__hourangles_by_lats make test test_run + CPPFLAGS=-DSW2_SolarPosition_Test__hourangles_by_lats make test_run Rscript tools/plot__SW2_SolarPosition_Test__hourangles_by_lats.R ``` - PET plots as function of radiation, relative humidity, wind speed, and cover ```{.sh} - CPPFLAGS=-DSW2_PET_Test__petfunc_by_temps make test test_run + CPPFLAGS=-DSW2_PET_Test__petfunc_by_temps make test_run Rscript tools/plot__SW2_PET_Test__petfunc_by_temps.R ``` diff --git a/SW_VegProd.c b/SW_VegProd.c deleted file mode 100644 index 3277ac29e..000000000 --- a/SW_VegProd.c +++ /dev/null @@ -1,843 +0,0 @@ -/********************************************************/ -/********************************************************/ -/* Source file: Veg_Prod.c -Type: module -Application: SOILWAT - soilwater dynamics simulator -Purpose: Read / write and otherwise manage the model's -vegetation production parameter information. -History: -(8/28/01) -- INITIAL CODING - cwb -11/16/2010 (drs) added LAIforest, biofoliage_for, lai_conv_for, TypeGrassOrShrub, TypeForest to SW_VEGPROD -lai_live/biolive/total_agb include now LAIforest, respectively biofoliage_for -updated SW_VPD_read(), SW_VPD_init(), and _echo_inits() -increased length of char outstr[1000] to outstr[1500] because of increased echo -02/22/2011 (drs) added scan for litter_for to SW_VPD_read() -02/22/2011 (drs) added litter_for to SW_VegProd.litter and to SW_VegProd.tot_agb -02/22/2011 (drs) if TypeGrassOrShrub is turned off, then its biomass, litter, etc. values are set to 0 -08/22/2011 (drs) use variable veg_height [MAX_MONTHS] from SW_VEGPROD instead of static canopy_ht -09/08/2011 (drs) adapted SW_VPD_read() and SW_VPD_init() to reflect that now each vegetation type has own elements -09/08/2011 (drs) added input in SW_VPD_read() of tanfunc_t tr_shade_effects, and RealD shade_scale and shade_deadmax (they were previously hidden as constants in code in SW_Flow_lib.h) -09/08/2011 (drs) moved all input of hydraulic redistribution variables from SW_Site.c to SW_VPD_read() for each vegetation type -09/08/2011 (drs) added input in SW_VPD_read() of RealD veg_intPPT_a, veg_intPPT_b, veg_intPPT_c, veg_intPPT_d (they were previously hidden as constants in code in SW_Flow_lib.h) -09/09/2011 (drs) added input in SW_VPD_read() of RealD EsTpartitioning_param (it were previously hidden as constant in code in SW_Flow_lib.h) -09/09/2011 (drs) added input in SW_VPD_read() of RealD Es_param_limit (it was previously hidden as constant in code in SW_Flow_lib.h) -09/13/2011 (drs) added input in SW_VPD_read() of RealD litt_intPPT_a, litt_intPPT_b, litt_intPPT_c, litt_intPPT_d (they were previously hidden as constants in code in SW_Flow_lib.h) -09/13/2011 (drs) added input in SW_VPD_read() of RealD canopy_height_constant and updated SW_VPD_init() (as option: if > 0 then constant canopy height (cm) and overriding cnpy-tangens=f(biomass)) -09/15/2011 (drs) added input in SW_VPD_read() of RealD albedo -09/26/2011 (drs) added calls to Times.c:interpolate_monthlyValues() in SW_VPD_init() for each monthly input variable; replaced monthly loop with a daily loop for additional daily variables; adjusted _echo_inits() -10/17/2011 (drs) in SW_VPD_init(): v->veg[SW_TREES].total_agb_daily[doy] = v->veg[SW_TREES].litter_daily[doy] + v->veg[SW_TREES].biolive_daily[doy] instead of = v->veg[SW_TREES].litter_daily[doy] + v->veg[SW_TREES].biomass_daily[doy] to adjust for better scaling of potential bare-soil evaporation -02/04/2012 (drs) added input in SW_VPD_read() of RealD SWPcrit -01/29/2013 (clk) changed the read in to account for the extra fractional component in total vegetation, bare ground -added the variable RealF help_bareGround as a place holder for the sscanf call. -01/31/2013 (clk) changed the read in to account for the albedo for bare ground, storing the input in bare_cov.albedo -changed _echo_inits() to now display the bare ground components in logfile.log -06/27/2013 (drs) closed open files if LogError() with LOGFATAL is called in SW_VPD_read() -07/09/2013 (clk) added initialization of all the values of the new vegtype variable forb and forb.cov.fCover -*/ -/********************************************************/ -/********************************************************/ - -/* =================================================== */ -/* =================================================== */ -/* INCLUDES / DEFINES */ - -#include -#include -#include -#include -#include "generic.h" // externs `QuietMode`, `EchoInits` -#include "filefuncs.h" // externs `_firstfile`, `inbuf` -#include "myMemory.h" -#include "SW_Defines.h" -#include "SW_Files.h" -#include "SW_Times.h" -#include "SW_VegProd.h" -#include "SW_Model.h" // externs SW_Model - - - -/* =================================================== */ -/* Global Variables */ -/* --------------------------------------------------- */ -SW_VEGPROD SW_VegProd; - -// key2veg must be in the same order as the indices to vegetation types defined in SW_Defines.h -char const *key2veg[NVEGTYPES] = {"Trees", "Shrubs", "Forbs", "Grasses"}; - - - -/* =================================================== */ -/* Local Variables */ -/* --------------------------------------------------- */ -static char *MyFileName; - - -/* =================================================== */ -/* Global Function Definitions */ -/* --------------------------------------------------- */ - -/** -@brief Reads file for SW_VegProd. -*/ -void SW_VPD_read(void) { - /* =================================================== */ - SW_VEGPROD *v = &SW_VegProd; - FILE *f; - TimeInt mon = Jan; - int x, k, lineno = 0; - const int line_help = 27; // last case line number before monthly biomass densities - RealF help_veg[NVEGTYPES], help_bareGround, litt, biom, pctl, laic; - - MyFileName = SW_F_name(eVegProd); - f = OpenFile(MyFileName, "r"); - - while (GetALine(f, inbuf)) { - if (lineno++ < line_help) { - switch (lineno) { - /* fractions of vegetation types */ - case 1: - x = sscanf(inbuf, "%f %f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS], &help_bareGround); - if (x < NVEGTYPES + 1) { - sprintf(errstr, "ERROR: invalid record in vegetation type components in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].cov.fCover = help_veg[k]; - } - v->bare_cov.fCover = help_bareGround; - break; - - /* albedo */ - case 2: - x = sscanf(inbuf, "%f %f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS], &help_bareGround); - if (x < NVEGTYPES + 1) { - sprintf(errstr, "ERROR: invalid record in albedo values in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].cov.albedo = help_veg[k]; - } - v->bare_cov.albedo = help_bareGround; - break; - - /* canopy height */ - case 3: - x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS]); - if (x < NVEGTYPES) { - sprintf(errstr, "ERROR: invalid record in canopy xinflec in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].cnpy.xinflec = help_veg[k]; - } - break; - case 4: - x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS]); - if (x < NVEGTYPES) { - sprintf(errstr, "ERROR: invalid record in canopy yinflec in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].cnpy.yinflec = help_veg[k]; - } - break; - case 5: - x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS]); - if (x < NVEGTYPES) { - sprintf(errstr, "ERROR: invalid record in canopy range in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].cnpy.range = help_veg[k]; - } - break; - case 6: - x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS]); - if (x < NVEGTYPES) { - sprintf(errstr, "ERROR: invalid record in canopy slope in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].cnpy.slope = help_veg[k]; - } - break; - case 7: - x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS]); - if (x < NVEGTYPES) { - sprintf(errstr, "ERROR: invalid record in canopy height constant option in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].canopy_height_constant = help_veg[k]; - } - break; - - /* vegetation interception parameters */ - case 8: - x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS]); - if (x < NVEGTYPES) { - sprintf(errstr, "ERROR: invalid record in interception parameter kSmax(veg) in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].veg_kSmax = help_veg[k]; - } - break; - case 9: - x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS]); - if (x < NVEGTYPES) { - sprintf(errstr, "ERROR: invalid record in interception parameter kdead(veg) in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].veg_kdead = help_veg[k]; - } - break; - - /* litter interception parameters */ - case 10: - x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS]); - if (x < NVEGTYPES) { - sprintf(errstr, "ERROR: invalid record in litter interception parameter kSmax(litter) in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].lit_kSmax = help_veg[k]; - } - break; - - /* parameter for partitioning of bare-soil evaporation and transpiration */ - case 11: - x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS]); - if (x < NVEGTYPES) { - sprintf(errstr, "ERROR: invalid record in parameter for partitioning of bare-soil evaporation and transpiration in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].EsTpartitioning_param = help_veg[k]; - } - break; - - /* Parameter for scaling and limiting bare soil evaporation rate */ - case 12: - x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS]); - if (x < NVEGTYPES) { - sprintf(errstr, "ERROR: invalid record in parameter for Parameter for scaling and limiting bare soil evaporation rate in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].Es_param_limit = help_veg[k]; - } - break; - - /* shade effects */ - case 13: - x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS]); - if (x < NVEGTYPES) { - sprintf(errstr, "ERROR: invalid record in shade scale in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].shade_scale = help_veg[k]; - } - break; - case 14: - x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS]); - if (x < NVEGTYPES) { - sprintf(errstr, "ERROR: invalid record in shade max dead biomass in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].shade_deadmax = help_veg[k]; - } - break; - case 15: - x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS]); - if (x < NVEGTYPES) { - sprintf(errstr, "ERROR: invalid record in shade xinflec in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].tr_shade_effects.xinflec = help_veg[k]; - } - break; - case 16: - x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS]); - if (x < NVEGTYPES) { - sprintf(errstr, "ERROR: invalid record in shade yinflec in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].tr_shade_effects.yinflec = help_veg[k]; - } - break; - case 17: - x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS]); - if (x < NVEGTYPES) { - sprintf(errstr, "ERROR: invalid record in shade range in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].tr_shade_effects.range = help_veg[k]; - } - break; - case 18: - x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS]); - if (x < NVEGTYPES) { - sprintf(errstr, "ERROR: invalid record in shade slope in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].tr_shade_effects.slope = help_veg[k]; - } - break; - - /* Hydraulic redistribution */ - case 19: - x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS]); - if (x < NVEGTYPES) { - sprintf(errstr, "ERROR: invalid record in hydraulic redistribution: flag in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].flagHydraulicRedistribution = (Bool) EQ(help_veg[k], 1.); - } - break; - case 20: - x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS]); - if (x < NVEGTYPES) { - sprintf(errstr, "ERROR: invalid record in hydraulic redistribution: maxCondroot in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].maxCondroot = help_veg[k]; - } - break; - case 21: - x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS]); - if (x < NVEGTYPES) { - sprintf(errstr, "ERROR: invalid record in hydraulic redistribution: swpMatric50 in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].swpMatric50 = help_veg[k]; - } - break; - case 22: - x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS]); - if (x < NVEGTYPES) { - sprintf(errstr, "ERROR: invalid record in hydraulic redistribution: shapeCond in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].shapeCond = help_veg[k]; - } - break; - - /* Critical soil water potential */ - case 23: - x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS]); - if (x < NVEGTYPES) { - sprintf(errstr, "ERROR: invalid record in critical soil water potentials: flag in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].SWPcrit = -10. * help_veg[k]; - SW_VegProd.critSoilWater[k] = help_veg[k]; // for use with get_swa for properly partitioning available soilwater - } - get_critical_rank(); - break; - - /* CO2 Biomass Power Equation */ - // Coefficient 1 - case 24: - x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS]); - if (x < NVEGTYPES) { - sprintf(errstr, "ERROR: Not enough arguments for CO2 Biomass Coefficient 1 in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].co2_bio_coeff1 = help_veg[k]; - } - break; - // Coefficient 2 - case 25: - x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS]); - if (x < NVEGTYPES) { - sprintf(errstr, "ERROR: Not enough arguments for CO2 Biomass Coefficient 2 in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].co2_bio_coeff2 = help_veg[k]; - } - break; - - /* CO2 WUE Power Equation */ - // Coefficient 1 - case 26: - x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS]); - if (x < NVEGTYPES) { - sprintf(errstr, "ERROR: Not enough arguments for CO2 WUE Coefficient 1 in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].co2_wue_coeff1 = help_veg[k]; - } - break; - // Coefficient 2 - case 27: - x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], - &help_veg[SW_TREES], &help_veg[SW_FORBS]); - if (x < NVEGTYPES) { - sprintf(errstr, "ERROR: Not enough arguments for CO2 WUE Coefficient 2 in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - ForEachVegType(k) { - v->veg[k].co2_wue_coeff2 = help_veg[k]; - } - break; - - default: - break; - } - } - else { - if (lineno == line_help + 1 || lineno == line_help + 1 + 12 || lineno == line_help + 1 + 12 * 2 || lineno == line_help + 1 + 12 * 3) - mon = Jan; - - x = sscanf(inbuf, "%f %f %f %f", &litt, &biom, &pctl, &laic); - if (x < NVEGTYPES) { - sprintf(errstr, "ERROR: invalid record %d in %s\n", mon + 1, MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - if (lineno > line_help + 12 * 3 && lineno <= line_help + 12 * 4) { - v->veg[SW_FORBS].litter[mon] = litt; - v->veg[SW_FORBS].biomass[mon] = biom; - v->veg[SW_FORBS].pct_live[mon] = pctl; - v->veg[SW_FORBS].lai_conv[mon] = laic; - } - else if (lineno > line_help + 12 * 2 && lineno <= line_help + 12 * 3) { - v->veg[SW_TREES].litter[mon] = litt; - v->veg[SW_TREES].biomass[mon] = biom; - v->veg[SW_TREES].pct_live[mon] = pctl; - v->veg[SW_TREES].lai_conv[mon] = laic; - } - else if (lineno > line_help + 12 && lineno <= line_help + 12 * 2) { - v->veg[SW_SHRUB].litter[mon] = litt; - v->veg[SW_SHRUB].biomass[mon] = biom; - v->veg[SW_SHRUB].pct_live[mon] = pctl; - v->veg[SW_SHRUB].lai_conv[mon] = laic; - } - else if (lineno > line_help && lineno <= line_help + 12) { - v->veg[SW_GRASS].litter[mon] = litt; - v->veg[SW_GRASS].biomass[mon] = biom; - v->veg[SW_GRASS].pct_live[mon] = pctl; - v->veg[SW_GRASS].lai_conv[mon] = laic; - } - - mon++; - - } - } - - if (mon < Dec) { - sprintf(errstr, "No Veg Production values after month %d", mon + 1); - LogError(logfp, LOGWARN, errstr); - } - - SW_VPD_fix_cover(); - - CloseFile(&f); - - if (EchoInits) - _echo_VegProd(); -} - -/** - \brief Check that sum of land cover is 1; adjust if not. - - \sideeffect - - Adjusts `SW_VegProd->bare_cov.fCover` and `SW_VegProd->veg[k].cov.fCover` - to sum to 1. - - Print a warning that values are adjusted and notes with the new values. -*/ -void SW_VPD_fix_cover(void) -{ - SW_VEGPROD *v = &SW_VegProd; - int k; - RealD fraction_sum = 0.; - - fraction_sum = v->bare_cov.fCover; - ForEachVegType(k) { - fraction_sum += v->veg[k].cov.fCover; - } - - if (!EQ_w_tol(fraction_sum, 1.0, 1e-4)) { - // inputs are never more precise than at most 3-4 digits - - LogError(logfp, LOGWARN, - "Fractions of land cover components were normalized:\n" \ - "\tSum of fractions was %.4f instead of 1.0. " \ - "New coefficients are:", fraction_sum); - - v->bare_cov.fCover /= fraction_sum; - LogError(logfp, LOGNOTE, "Bare ground fraction = %.4f", v->bare_cov.fCover); - - ForEachVegType(k) { - v->veg[k].cov.fCover /= fraction_sum; - LogError(logfp, LOGNOTE, "%s fraction = %.4f", - key2veg[k], v->veg[k].cov.fCover); - } - - LogError(logfp, LOGQUIET, ""); - } -} - -/** -@brief Constructor for SW_VegProd. -*/ -void SW_VPD_construct(void) { - /* =================================================== */ - OutPeriod pd; - - // Clear the module structure: - memset(&SW_VegProd, 0, sizeof(SW_VegProd)); - - // Allocate output structures: - ForEachOutPeriod(pd) - { - SW_VegProd.p_accu[pd] = (SW_VEGPROD_OUTPUTS *) Mem_Calloc(1, - sizeof(SW_VEGPROD_OUTPUTS), "SW_VPD_construct()"); - if (pd > eSW_Day) { - SW_VegProd.p_oagg[pd] = (SW_VEGPROD_OUTPUTS *) Mem_Calloc(1, - sizeof(SW_VEGPROD_OUTPUTS), "SW_VPD_construct()"); - } - } -} - - - -void SW_VPD_init_run(void) { - TimeInt year; - int k; - - /* Set co2-multipliers to default */ - for (year = 0; year < MAX_NYEAR; year++) - { - ForEachVegType(k) - { - SW_VegProd.veg[k].co2_multipliers[BIO_INDEX][year] = 1.; - SW_VegProd.veg[k].co2_multipliers[WUE_INDEX][year] = 1.; - } - } -} - - -/** -@brief Deconstructor for SW_VegProd. -*/ -void SW_VPD_deconstruct(void) -{ - OutPeriod pd; - - // De-allocate output structures: - ForEachOutPeriod(pd) - { - if (pd > eSW_Day && !isnull(SW_VegProd.p_oagg[pd])) { - Mem_Free(SW_VegProd.p_oagg[pd]); - SW_VegProd.p_oagg[pd] = NULL; - } - - if (!isnull(SW_VegProd.p_accu[pd])) { - Mem_Free(SW_VegProd.p_accu[pd]); - SW_VegProd.p_accu[pd] = NULL; - } - } -} - -/** - * @brief Applies CO2 effects to supplied biomass data. - * - * Two biomass parameters are needed so that we do not have a compound effect - * on the biomass. - * - * @param new_biomass The resulting biomass after applying the multiplier. - * @param biomass The biomass to be modified (representing the value under reference - * conditions (i.e., 360 ppm CO2, currently). - * @param multiplier The biomass multiplier for this PFT. - * - * @sideeffect new_biomass Updated biomass. - */ -void apply_biomassCO2effect(double new_biomass[], double biomass[], double multiplier) { - int i; - for (i = 0; i < 12; i++) new_biomass[i] = (biomass[i] * multiplier); -} - - - -/** -@brief Update vegetation parameters for new year -*/ -void SW_VPD_new_year(void) { - /* ================================================== */ - /* - * History: - * Originally included in the FORTRAN model. - * - * 20-Oct-03 (cwb) removed the calculation of - * lai_corr and changed the lai_conv value of 80 - * as found in the prod.in file. The conversion - * factor is now simply a divisor rather than - * an equation. Removed the following code: - lai_corr = v->lai_conv[m] * (1. - pstem) + aconst * pstem; - lai_standing = v->biomass[m] / lai_corr; - where pstem = 0.3, - aconst = 464.0, - conv_stcr = 3.0; - * - */ - - SW_VEGPROD *v = &SW_VegProd; /* convenience */ - TimeInt doy; /* base1 */ - int k; - - - // Grab the real year so we can access CO2 data - ForEachVegType(k) - { - if (GT(v->veg[k].cov.fCover, 0.)) - { - if (k == SW_TREES) - { - // CO2 effects on biomass restricted to percent live biomass - apply_biomassCO2effect(v->veg[k].CO2_pct_live, v->veg[k].pct_live, - v->veg[k].co2_multipliers[BIO_INDEX][SW_Model.simyear]); - - interpolate_monthlyValues(v->veg[k].CO2_pct_live, v->veg[k].pct_live_daily); - interpolate_monthlyValues(v->veg[k].biomass, v->veg[k].biomass_daily); - - } else { - // CO2 effects on biomass applied to total biomass - apply_biomassCO2effect(v->veg[k].CO2_biomass, v->veg[k].biomass, - v->veg[k].co2_multipliers[BIO_INDEX][SW_Model.simyear]); - - interpolate_monthlyValues(v->veg[k].CO2_biomass, v->veg[k].biomass_daily); - interpolate_monthlyValues(v->veg[k].pct_live, v->veg[k].pct_live_daily); - } - - // Interpolation of remaining variables from monthly to daily values - interpolate_monthlyValues(v->veg[k].litter, v->veg[k].litter_daily); - interpolate_monthlyValues(v->veg[k].lai_conv, v->veg[k].lai_conv_daily); - } - } - - for (doy = 1; doy <= MAX_DAYS; doy++) - { - ForEachVegType(k) { - if (GT(v->veg[k].cov.fCover, 0.)) - { - /* vegetation height = 'veg_height_daily' is used for 'snowdepth_scale'; historically, also for 'vegcov' */ - if (GT(v->veg[k].canopy_height_constant, 0.)) - { - v->veg[k].veg_height_daily[doy] = v->veg[k].canopy_height_constant; - - } else { - v->veg[k].veg_height_daily[doy] = tanfunc(v->veg[k].biomass_daily[doy], - v->veg[k].cnpy.xinflec, - v->veg[k].cnpy.yinflec, - v->veg[k].cnpy.range, - v->veg[k].cnpy.slope); - } - - /* live biomass = 'biolive_daily' is used for canopy-interception, transpiration, bare-soil evaporation, and hydraulic redistribution */ - v->veg[k].biolive_daily[doy] = v->veg[k].biomass_daily[doy] * v->veg[k].pct_live_daily[doy]; - - /* dead biomass = 'biodead_daily' is used for canopy-interception and transpiration */ - v->veg[k].biodead_daily[doy] = v->veg[k].biomass_daily[doy] - v->veg[k].biolive_daily[doy]; - - /* live leaf area index = 'lai_live_daily' is used for E-T partitioning */ - v->veg[k].lai_live_daily[doy] = v->veg[k].biolive_daily[doy] / v->veg[k].lai_conv_daily[doy]; - - /* compound leaf area index = 'bLAI_total_daily' is used for canopy-interception */ - v->veg[k].bLAI_total_daily[doy] = v->veg[k].lai_live_daily[doy] + - v->veg[k].veg_kdead * v->veg[k].biodead_daily[doy] / v->veg[k].lai_conv_daily[doy]; - - /* total above-ground biomass = 'total_agb_daily' is used for bare-soil evaporation */ - if (k == SW_TREES) - { - v->veg[k].total_agb_daily[doy] = v->veg[k].litter_daily[doy] + v->veg[k].biolive_daily[doy]; - } else { - v->veg[k].total_agb_daily[doy] = v->veg[k].litter_daily[doy] + v->veg[k].biomass_daily[doy]; - } - - } else { - v->veg[k].lai_live_daily[doy] = 0.; - v->veg[k].bLAI_total_daily[doy] = 0.; - v->veg[k].biolive_daily[doy] = 0.; - v->veg[k].biodead_daily[doy] = 0.; - v->veg[k].total_agb_daily[doy] = 0.; - } - } - } -} - - -/** - @brief Sum up values across vegetation types - - @param[in] *x Array of size \ref NVEGTYPES - @return Sum across `*x` -*/ -RealD sum_across_vegtypes(RealD *x) -{ - unsigned int k; - RealD sum = 0.; - - ForEachVegType(k) - { - sum += x[k]; - } - - return sum; -} - - -/** -@brief Text output for VegProd. -*/ -void _echo_VegProd(void) { - /* ================================================== */ - - SW_VEGPROD *v = &SW_VegProd; /* convenience */ - char outstr[1500]; - int k; - - sprintf(errstr, "\n==============================================\n" - "Vegetation Production Parameters\n\n"); - strcpy(outstr, errstr); - LogError(logfp, LOGNOTE, outstr); - - ForEachVegType(k) { - sprintf(errstr, - "%s component\t= %1.2f\n" - "\tAlbedo\t= %1.2f\n" - "\tHydraulic redistribution flag\t= %d\n", - key2veg[k], v->veg[k].cov.fCover, - v->veg[k].cov.albedo, - v->veg[k].flagHydraulicRedistribution); - strcpy(outstr, errstr); - LogError(logfp, LOGNOTE, outstr); - } - - sprintf(errstr, "Bare Ground component\t= %1.2f\n" - "\tAlbedo\t= %1.2f\n", v->bare_cov.fCover, v->bare_cov.albedo); - strcpy(outstr, errstr); - LogError(logfp, LOGNOTE, outstr); -} - - -/** @brief Determine vegetation type of decreasingly ranked the critical SWP - - @sideeffect Sets `SW_VegProd.rank_SWPcrits[]` based on - `SW_VegProd.critSoilWater[]` -*/ -void get_critical_rank(void){ - /*---------------------------------------------------------- - Get proper order for rank_SWPcrits - ----------------------------------------------------------*/ - int i, outerLoop, innerLoop; - float key; - - RealF tempArray[NVEGTYPES], tempArrayUnsorted[NVEGTYPES]; // need two temp arrays equal to critSoilWater since we dont want to alter the original at all - - ForEachVegType(i){ - tempArray[i] = SW_VegProd.critSoilWater[i]; - tempArrayUnsorted[i] = SW_VegProd.critSoilWater[i]; - } - - // insertion sort to rank the veg types and store them in their proper order - for (outerLoop = 1; outerLoop < NVEGTYPES; outerLoop++) - { - key = tempArray[outerLoop]; // set key equal to critical value - innerLoop = outerLoop-1; - while (innerLoop >= 0 && tempArray[innerLoop] < key) - { - // code to switch values - tempArray[innerLoop+1] = tempArray[innerLoop]; - innerLoop = innerLoop-1; - } - tempArray[innerLoop+1] = key; - } - - // loops to compare sorted v unsorted array and find proper index - for(outerLoop = 0; outerLoop < NVEGTYPES; outerLoop++){ - for(innerLoop = 0; innerLoop < NVEGTYPES; innerLoop++){ - if(tempArray[outerLoop] == tempArrayUnsorted[innerLoop]){ - SW_VegProd.rank_SWPcrits[outerLoop] = innerLoop; - tempArrayUnsorted[innerLoop] = SW_MISSING; // set value to something impossible so if a duplicate a different index is picked next - break; - } - } - } - /*printf("%d = %f\n", SW_VegProd.rank_SWPcrits[0], SW_VegProd.critSoilWater[SW_VegProd.rank_SWPcrits[0]]); - printf("%d = %f\n", SW_VegProd.rank_SWPcrits[1], SW_VegProd.critSoilWater[SW_VegProd.rank_SWPcrits[1]]); - printf("%d = %f\n", SW_VegProd.rank_SWPcrits[2], SW_VegProd.critSoilWater[SW_VegProd.rank_SWPcrits[2]]); - printf("%d = %f\n\n", SW_VegProd.rank_SWPcrits[3], SW_VegProd.critSoilWater[SW_VegProd.rank_SWPcrits[3]]);*/ - /*---------------------------------------------------------- - End of rank_SWPcrits - ----------------------------------------------------------*/ -} diff --git a/SW_Weather.c b/SW_Weather.c deleted file mode 100644 index 8b62b8265..000000000 --- a/SW_Weather.c +++ /dev/null @@ -1,584 +0,0 @@ -/********************************************************/ -/********************************************************/ -/* Source file: Weather.c - Type: class - Application: SOILWAT - soilwater dynamics simulator - Purpose: Read / write and otherwise manage the model's - weather-related information. - History: - (8/28/01) -- INITIAL CODING - cwb - 12/02 - IMPORTANT CHANGE - cwb - refer to comments in Times.h regarding base0 - 20090916 (drs) changed in SW_WTH_new_day() the following code: - wn->temp_avg[Today] = (tmpmax + tmpmin) / 2.; - to wn->temp_avg[Today] = (wn->temp_max[Today] + wn->temp_min[Today]) / 2.; - so that monthly scaling factors also influence average T and not only Tmin and Tmax - 20091009 (drs) moved call to SW_SWC_adjust_snow () to SW_Flow.c - and split it up into snow accumulation and snow melt - 20091014 (drs) added pct_snowdrift as input to weathsetup.in - 20091015 (drs) ppt is divided into rain and snow, added snowmelt - 20101004 (drs) moved call to SW_SWC_adjust_snow() from SW_Flow.c back to SW_WTH_new_day(), see comment 20091009 - 01/04/2011 (drs) added parameter '*snowloss' to function SW_SWC_adjust_snow() call and updated function SW_WTH_new_year() - 01/05/2011 (drs) removed unused variables rain and snow from SW_WTH_new_day() - 02/16/2011 (drs) added pct_runoff read from input file to SW_WTH_read() - 02/19/2011 (drs) to track 'runoff', updated functions SW_WTH_new_year(), SW_WTH_new_day() - moved soil_inf from SW_Soilwat to SW_Weather - 09/30/2011 (drs) weather name prefix no longer read in from file weathersetup.in with function SW_WTH_read(), but extracted from SW_Files.c:SW_WeatherPrefix() - 01/13/2011 (drs) function '_read_hist' didn't close opened files: after reaching OS-limit of openend connections, no files could be read any more -> added 'fclose(f);' to close open connections after use - 06/01/2012 (DLM) edited _read_hist() function to calculate the yearly avg air temperature & the monthly avg air temperatures... - 11/30/2010 (clk) added appopriate lines for 'surfaceRunoff' in SW_WTH_new_year() and SW_WTH_new_day(); - 06/21/2013 (DLM) variable 'tail' got too large by 1 and leaked memory when accessing array runavg_list[] in function _runavg_temp(): changed test from '(tail < SW_Weather.days_in_runavg)' to '(tail < (SW_Weather.days_in_runavg-1))' - in function _clear_hist_weather() temp_max was tested twice, one should be temp_min - 06/24/2013 (rjm) added function void SW_WTH_clear_runavg_list(void) to free memory - 06/24/2013 (rjm) made 'tail' and 'firsttime' a module-level static variable (instead of function-level): otherwise it will not get reset to 0 between consecutive calls as a dynamic library - need to set these variables to 0 resp TRUE in function SW_WTH_construct() - 06/27/2013 (drs) closed open files if LogError() with LOGFATAL is called in SW_WTH_read(), _read_hist() - 08/26/2013 (rjm) removed extern SW_OUTPUT never used. - */ -/********************************************************/ -/********************************************************/ - -#include -#include -#include -#include "generic.h" -#include "filefuncs.h" -#include "myMemory.h" -#include "Times.h" -#include "SW_Defines.h" -#include "SW_Files.h" -#include "SW_Model.h" // externs SW_Model -#include "SW_SoilWater.h" -#include "SW_Markov.h" - -#include "SW_Weather.h" -#ifdef RSOILWAT - #include "../rSW_Weather.h" -#endif - - - -/* =================================================== */ -/* Global Variables */ -/* --------------------------------------------------- */ - -SW_WEATHER SW_Weather; - - - -/* =================================================== */ -/* Local Variables */ -/* --------------------------------------------------- */ - -static char *MyFileName; - -/** `swTRUE`/`swFALSE` if historical daily meteorological inputs - are available/not available for the current simulation year -*/ -#define weth_found -#undef weth_found - -static Bool weth_found; - - -/* =================================================== */ -/* Local Function Definitions */ -/* --------------------------------------------------- */ - -static void _update_yesterday(void) { - /* --------------------------------------------------- */ - /* save today's temp values as yesterday */ - /* this must be done after all calculations are - * finished for the day and before today's weather - * is read from the file. Assumes Today's weather - * is always validated (non-missing). - */ - SW_WEATHER_2DAYS *wn = &SW_Weather.now; - - wn->temp_max[Yesterday] = wn->temp_max[Today]; - wn->temp_min[Yesterday] = wn->temp_min[Today]; - wn->temp_avg[Yesterday] = wn->temp_avg[Today]; - - wn->ppt[Yesterday] = wn->ppt[Today]; - wn->rain[Yesterday] = wn->rain[Today]; -} - - -static void _todays_weth(RealD *tmax, RealD *tmin, RealD *ppt) { - /* --------------------------------------------------- */ - /* If use_weathergenerator = swFALSE and no weather file found, we won't - * get this far because the new_year() will fail, so if - * no weather file found and we make it here, use_weathergenerator = swTRUE - * and we call mkv_today(). Otherwise, we're using this - * year's weather file and this logic sets today's value - * to yesterday's if today's is missing. This may not - * always be most desirable, especially for ppt, so its - * default is 0. - */ - SW_WEATHER *w = &SW_Weather; - TimeInt doy = SW_Model.doy - 1; - Bool no_missing = swTRUE; - - if (!weth_found) { - // no weather input file for current year ==> use weather generator - *ppt = w->now.ppt[Yesterday]; /* reqd for markov */ - SW_MKV_today(doy, tmax, tmin, ppt); - - } else { - // weather input file for current year available - - no_missing = (Bool) (!missing(w->hist.temp_max[doy]) && - !missing(w->hist.temp_min[doy]) && - !missing(w->hist.ppt[doy])); - - if (no_missing) { - // all values available - *tmax = w->hist.temp_max[doy]; - *tmin = w->hist.temp_min[doy]; - *ppt = w->hist.ppt[doy]; - - } else { - // some of today's values are missing - - if (SW_Weather.use_weathergenerator) { - // if weather generator is turned on then use it for all values - *ppt = w->now.ppt[Yesterday]; /* reqd for markov */ - SW_MKV_today(doy, tmax, tmin, ppt); - - } else { - // impute missing values with 0 for precipitation and - // with LOCF for temperature (i.e., last-observation-carried-forward) - *tmax = (!missing(w->hist.temp_max[doy])) ? w->hist.temp_max[doy] : w->now.temp_max[Yesterday]; - *tmin = (!missing(w->hist.temp_min[doy])) ? w->hist.temp_min[doy] : w->now.temp_min[Yesterday]; - *ppt = (!missing(w->hist.ppt[doy])) ? w->hist.ppt[doy] : 0.; - } - } - } - -} - - -/* =================================================== */ -/* Global Function Definitions */ -/* --------------------------------------------------- */ - - -/** -@brief Clears weather history. -@note Used by rSOILWAT2 -*/ -void _clear_hist_weather(void) { - /* --------------------------------------------------- */ - SW_WEATHER_HIST *wh = &SW_Weather.hist; - TimeInt d; - - for (d = 0; d < MAX_DAYS; d++) - wh->ppt[d] = wh->temp_max[d] = wh->temp_min[d] = SW_MISSING; -} - - -/* =================================================== */ -/* =================================================== */ -/* Global Function Definitions */ -/* --------------------------------------------------- */ - -/** -@brief Constructor for SW_Weather. -*/ -void SW_WTH_construct(void) { - /* =================================================== */ - OutPeriod pd; - - // Clear the module structure: - memset(&SW_Weather, 0, sizeof(SW_Weather)); - - // Allocate output structures: - ForEachOutPeriod(pd) - { - SW_Weather.p_accu[pd] = (SW_WEATHER_OUTPUTS *) Mem_Calloc(1, - sizeof(SW_WEATHER_OUTPUTS), "SW_WTH_construct()"); - if (pd > eSW_Day) { - SW_Weather.p_oagg[pd] = (SW_WEATHER_OUTPUTS *) Mem_Calloc(1, - sizeof(SW_WEATHER_OUTPUTS), "SW_WTH_construct()"); - } - } -} - -/** -@brief Deconstructor for SW_Weather and SW_Markov (if used) -*/ -void SW_WTH_deconstruct(void) -{ - OutPeriod pd; - - // De-allocate output structures: - ForEachOutPeriod(pd) - { - if (pd > eSW_Day && !isnull(SW_Weather.p_oagg[pd])) { - Mem_Free(SW_Weather.p_oagg[pd]); - SW_Weather.p_oagg[pd] = NULL; - } - - if (!isnull(SW_Weather.p_accu[pd])) { - Mem_Free(SW_Weather.p_accu[pd]); - SW_Weather.p_accu[pd] = NULL; - } - } - - if (SW_Weather.use_weathergenerator) { - SW_MKV_deconstruct(); - } -} - -/** -@brief Initialize weather variables for a simulation run - - They are used as default if weather for the first day is missing -*/ -void SW_WTH_init_run(void) { - /* setup today's weather because it's used as a default - * value when weather for the first day is missing. - * Notice that temps of 0. are reasonable for January - * (doy=1) and are below the critical temps for freezing - * and with ppt=0 there's nothing to freeze. - */ - SW_Weather.now.temp_max[Today] = SW_Weather.now.temp_min[Today] = 0.; - SW_Weather.now.temp_max[Yesterday] = SW_Weather.now.temp_min[Yesterday] = 0.; - SW_Weather.now.ppt[Today] = SW_Weather.now.rain[Today] = 0.; - SW_Weather.now.ppt[Yesterday] = SW_Weather.now.rain[Yesterday] = 0.; - SW_Weather.snow = SW_Weather.snowmelt = SW_Weather.snowloss = 0.; - SW_Weather.snowRunoff = 0.; - SW_Weather.surfaceRunoff = SW_Weather.surfaceRunon = 0.; - SW_Weather.soil_inf = 0.; -} - - -/** @brief Sets up daily meteorological inputs for current simulation year - - Meteorological inputs are required for each day; they can either be - observed and provided via weather input files or they can be generated - by a weather generator (which has separate input requirements). - - SOILWAT2 handles three scenarios of missing data: - 1. Some individual days are missing (set to the missing value) - 2. An entire year is missing (file `weath.xxxx` for year `xxxx` is absent) - 3. No daily weather input files are available - - SOILWAT2 may be set up such that the weather generator is exclusively: - - Set the weather generator to exclusive use - or - 1. Turn on the weather generator - 2. Set the "first year to begin historical weather" to a year after - the last simulated year - - @sideeffect - - if historical daily meteorological inputs are successfully located, - then \ref weth_found is set to `swTRUE` - - otherwise, \ref weth_found is `swFALSE` -*/ -void SW_WTH_new_year(void) { - - if ( - SW_Weather.use_weathergenerator_only || - SW_Model.year < SW_Weather.yr.first - ) { - weth_found = swFALSE; - - } else { - #ifdef RSOILWAT - weth_found = onSet_WTH_DATA_YEAR(SW_Model.year); - #else - weth_found = _read_weather_hist(SW_Model.year); - #endif - } - - if (!weth_found && !SW_Weather.use_weathergenerator) { - LogError( - logfp, - LOGFATAL, - "Markov Simulator turned off and weather file not found for year %d", - SW_Model.year - ); - } -} - -/** -@brief Updates 'yesterday'. -*/ -void SW_WTH_end_day(void) { - /* =================================================== */ - _update_yesterday(); -} - -/** -@brief Guarantees that today's weather will not be invalid via -_todays_weth(). -*/ -void SW_WTH_new_day(void) { - /* =================================================== */ - /* History - * - * 20-Jul-2002 -- added growing season computation to - * facilitate the steppe/soilwat interface. - * 06-Dec-2002 -- modified the seasonal computation to - * account for n-s hemispheres. - * 16-Sep-2009 -- (drs) scaling factors were only applied to Tmin and Tmax - * but not to Taverage -> corrected - * 09-Oct-2009 -- (drs) commented out snow adjustement, because call moved to SW_Flow.c - * 20091015 (drs) ppt is divided into rain and snow - */ - - SW_WEATHER *w = &SW_Weather; - SW_WEATHER_2DAYS *wn = &SW_Weather.now; - RealD tmpmax, tmpmin, ppt; - TimeInt month = SW_Model.month; - -#ifdef STEPWAT - /* - TimeInt doy = SW_Model.doy; - Bool is_warm; - Bool is_growingseason = swFALSE; - */ -#endif - - /* get the plain unscaled values */ - _todays_weth(&tmpmax, &tmpmin, &ppt); - - /* scale the weather according to monthly factors */ - wn->temp_max[Today] = tmpmax + w->scale_temp_max[month]; - wn->temp_min[Today] = tmpmin + w->scale_temp_min[month]; - - wn->temp_avg[Today] = (wn->temp_max[Today] + wn->temp_min[Today]) / 2.; - - ppt *= w->scale_precip[month]; - - wn->ppt[Today] = wn->rain[Today] = ppt; - w->snow = w->snowmelt = w->snowloss = 0.; - w->snowRunoff = w->surfaceRunoff = w->surfaceRunon = w->soil_inf = 0.; - - if (w->use_snow) - { - SW_SWC_adjust_snow(wn->temp_min[Today], wn->temp_max[Today], wn->ppt[Today], - &wn->rain[Today], &w->snow, &w->snowmelt); - } -} - -/** -@brief Reads in file for SW_Weather. -*/ -void SW_WTH_read(void) { - /* =================================================== */ - SW_WEATHER *w = &SW_Weather; - const int nitems = 18; - FILE *f; - int lineno = 0, month, x; - RealF sppt, stmax, stmin; - RealF sky, wind, rH; - - MyFileName = SW_F_name(eWeather); - f = OpenFile(MyFileName, "r"); - - while (GetALine(f, inbuf)) { - switch (lineno) { - case 0: - w->use_snow = itob(atoi(inbuf)); - break; - case 1: - w->pct_snowdrift = atoi(inbuf); - break; - case 2: - w->pct_snowRunoff = atoi(inbuf); - break; - - case 3: - x = atoi(inbuf); - if (x > 1) { - w->use_weathergenerator_only = w->use_weathergenerator = swTRUE; - } else { - w->use_weathergenerator_only = swFALSE; - w->use_weathergenerator = itob(x); - } - break; - - case 4: - w->rng_seed = atoi(inbuf); - break; - - case 5: - x = atoi(inbuf); - w->yr.first = (x < 0) ? SW_Model.startyr : yearto4digit(x); - break; - - default: - if (lineno == 6 + MAX_MONTHS) - break; - - x = sscanf( - inbuf, - "%d %f %f %f %f %f %f", - &month, &sppt, &stmax, &stmin, &sky, &wind, &rH - ); - - if (x != 7) { - CloseFile(&f); - LogError(logfp, LOGFATAL, "%s : Bad record %d.", MyFileName, lineno); - } - - month--; // convert to base0 - w->scale_precip[month] = sppt; - w->scale_temp_max[month] = stmax; - w->scale_temp_min[month] = stmin; - w->scale_skyCover[month] = sky; - w->scale_wind[month] = wind; - w->scale_rH[month] = rH; - } - - lineno++; - } - - SW_WeatherPrefix(w->name_prefix); - CloseFile(&f); - - if (lineno < nitems) { - LogError(logfp, LOGFATAL, "%s : Too few input lines.", MyFileName); - } - - w->yr.last = SW_Model.endyr; - w->yr.total = w->yr.last - w->yr.first + 1; - - if (!w->use_weathergenerator && SW_Model.startyr < w->yr.first) { - LogError( - logfp, - LOGFATAL, - "%s : Model year (%d) starts before weather files (%d) " - "and the Markov weather generator is turned off. \n" - "Please synchronize the years or " - "activate the weather generator " - "(and set up input files `mkv_prob.in` and `mkv_covar.in`).", - MyFileName, SW_Model.startyr, w->yr.first - ); - } - /* else we assume weather files match model run years */ -} - - -/** @brief Read the historical (observed) weather file for a simulation year. - - The naming convection of the weather input files: - `[weather-data path/][weather-file prefix].[year]` - - Format of a input file (white-space separated values): - `doy maxtemp(°C) mintemp (°C) precipitation (cm)` - - @note Used by rSOILWAT2 - - @param year - - @return `swTRUE`/`swFALSE` if historical daily meteorological inputs are - successfully/unsuccessfully read in. -*/ -Bool _read_weather_hist(TimeInt year) { - /* =================================================== */ - /* Read the historical (measured) weather files. - * Format is - * day-of-month, month number, year, doy, mintemp, maxtemp, ppt - * - * I dislike the inclusion of the first three columns - * but this is the old format. If a new format emerges - * these columns will likely be removed. - * - * temps are in degrees C, ppt is in cm, - * - * 26-Jan-02 - changed format of the input weather files. - * - */ - - SW_WEATHER_HIST *wh = &SW_Weather.hist; - FILE *f; - int x, lineno = 0, doy; - // TimeInt mon, j, k = 0; - RealF tmpmax, tmpmin, ppt; - // RealF acc = 0.0; - - char fname[MAX_FILENAMESIZE]; - - sprintf(fname, "%s.%4d", SW_Weather.name_prefix, year); - - _clear_hist_weather(); // clear values before returning - - if (NULL == (f = fopen(fname, "r"))) - return swFALSE; - - while (GetALine(f, inbuf)) { - lineno++; - x = sscanf(inbuf, "%d %f %f %f", &doy, &tmpmax, &tmpmin, &ppt); - if (x < 4) { - CloseFile(&f); - LogError(logfp, LOGFATAL, "%s : Incomplete record %d (doy=%d).", fname, lineno, doy); - } - if (x > 4) { - CloseFile(&f); - LogError(logfp, LOGFATAL, "%s : Too many values in record %d (doy=%d).", fname, lineno, doy); - } - if (doy < 1 || doy > MAX_DAYS) { - CloseFile(&f); - LogError(logfp, LOGFATAL, "%s : Day of year out of range, line %d.", fname, lineno); - } - - /* --- Make the assignments ---- */ - doy--; // base1 -> base0 - wh->temp_max[doy] = tmpmax; - wh->temp_min[doy] = tmpmin; - wh->temp_avg[doy] = (tmpmax + tmpmin) / 2.0; - wh->ppt[doy] = ppt; - - // Calculate annual average temperature based on historical input, i.e., - // the `temp_year_avg` calculated here is prospective and unsuitable when - // the weather generator is used to generate values for the - // current simulation year, e.g., STEPWAT2 - /* - if (!missing(tmpmax) && !missing(tmpmin)) { - k++; - acc += wh->temp_avg[doy]; - } - */ - } /* end of input lines */ - - // Calculate annual average temperature based on historical input - // wh->temp_year_avg = acc / (k + 0.0); - - // Calculate monthly average temperature based on historical input - // the `temp_month_avg` calculated here is prospective and unsuitable when - // the weather generator is used to generate values for the - // current simulation year, e.g., STEPWAT2 - /* - for (mon = Jan; mon <= Dec; i++) { - k = Time_days_in_month(mon); - - acc = 0.0; - for (j = 0; j < k; j++) - { - acc += wh->temp_avg[j + x]; - } - wh->temp_month_avg[i] = acc / (k + 0.0); - } - */ - - fclose(f); - return swTRUE; -} - -#ifdef DEBUG_MEM -#include "myMemory.h" -/*======================================================*/ -void SW_WTH_SetMemoryRefs( void) { - /* when debugging memory problems, use the bookkeeping - code in myMemory.c - This routine sets the known memory refs in this module - so they can be checked for leaks, etc. All refs will - have been cleared by a call to ClearMemoryRefs() before - this, and will be checked via CheckMemoryRefs() after - this, most likely in the main() function. - */ -} - -#endif diff --git a/SW_Weather.h b/SW_Weather.h deleted file mode 100644 index 26c7118b8..000000000 --- a/SW_Weather.h +++ /dev/null @@ -1,125 +0,0 @@ -/********************************************************/ -/********************************************************/ -/* Source file: SW_Weather.h - Type: header - Application: SOILWAT - soilwater dynamics simulator - Purpose: Support definitions/declarations for - weather-related information. - History: - (8/28/01) -- INITIAL CODING - cwb - 20091014 (drs) added pct_snowdrift as input to weathsetup.in - 20091015 (drs) ppt is divided into rain and snow, added snowmelt - 01/04/2011 (drs) added variable 'snowloss' to SW_WEATHER_2DAYS and to SW_WEATHER_OUTPUTS - 02/16/2011 (drs) added variable 'pct_runoff' to SW_WEATHER as input to weathsetup.in - 02/19/2011 (drs) added variable 'runoff' to SW_WEATHER_2DAYS and to SW_WEATHER_OUTPUTS - moved soil_inf from SW_Soilwat to SW_Weather (added to SW_WEATHER and to SW_WEATHER_OUTPUTS) - 06/01/2012 (DLM) added temp_year_avg variable to SW_WEATHER_HIST struct & temp_month_avg[MAX_MONTHS] variable - 11/30/2012 (clk) added variable 'surfaceRunoff' to SW_WEATHER and SW_WEATHER_OUTPUTS - changed 'runoff' to 'snowRunoff' to better distinguish between surface runoff and snowmelt runoff - - */ -/********************************************************/ -/********************************************************/ - -#ifndef SW_WEATHER_H -#define SW_WEATHER_H - -#include "SW_Times.h" -#include "SW_Defines.h" - -#ifdef __cplusplus -extern "C" { -#endif - - - -/* all temps are in degrees C, all precip is in cm */ -/* in fact, all water variables are in cm throughout - * the model. this facilitates additions and removals - * as they're always in the right units. - */ - -typedef struct { - /* comes from markov weather day-to-day */ - RealD temp_avg[TWO_DAYS], temp_max[TWO_DAYS], temp_min[TWO_DAYS], - ppt[TWO_DAYS], rain[TWO_DAYS]; -} SW_WEATHER_2DAYS; - -typedef struct { - /* comes from historical weather files */ - RealD temp_max[MAX_DAYS], temp_min[MAX_DAYS], temp_avg[MAX_DAYS], ppt[MAX_DAYS]; - // RealD temp_month_avg[MAX_MONTHS], temp_year_avg; // currently not used -} SW_WEATHER_HIST; - -/* accumulators for output values hold only the */ -/* current period's values (eg, weekly or monthly) */ -typedef struct { - RealD temp_max, temp_min, temp_avg, ppt, rain, snow, snowmelt, snowloss, /* 20091015 (drs) ppt is divided into rain and snow */ - snowRunoff, surfaceRunoff, surfaceRunon, soil_inf, et, aet, pet, surfaceAvg, surfaceMax, surfaceMin; -} SW_WEATHER_OUTPUTS; - -typedef struct { - - Bool - use_weathergenerator_only, - // swTRUE: set use_weathergenerator = swTRUE and ignore weather inputs - use_weathergenerator, - // swTRUE: use weather generator for missing weather input (values/files) - // swFALSE: fail if any weather input is missing (values/files) - use_snow; - int rng_seed; // initial state for `markov_rng` - RealD pct_snowdrift, pct_snowRunoff; - SW_TIMES yr; - RealD - scale_precip[MAX_MONTHS], - scale_temp_max[MAX_MONTHS], - scale_temp_min[MAX_MONTHS], - scale_skyCover[MAX_MONTHS], - scale_wind[MAX_MONTHS], - scale_rH[MAX_MONTHS]; - char name_prefix[MAX_FILENAMESIZE - 5]; // subtract 4-digit 'year' file type extension - RealD snowRunoff, surfaceRunoff, surfaceRunon, soil_inf, surfaceAvg; - RealD snow, snowmelt, snowloss, surfaceMax, surfaceMin; - - /* This section is required for computing the output quantities. */ - SW_WEATHER_OUTPUTS - *p_accu[SW_OUTNPERIODS], // output accumulator: summed values for each time period - *p_oagg[SW_OUTNPERIODS]; // output aggregator: mean or sum for each time periods - SW_WEATHER_HIST hist; - SW_WEATHER_2DAYS now; - -} SW_WEATHER; - - - -/* =================================================== */ -/* Externed Global Variables */ -/* --------------------------------------------------- */ -extern SW_WEATHER SW_Weather; - - -/* =================================================== */ -/* Global Function Declarations */ -/* --------------------------------------------------- */ -void SW_WTH_read(void); -Bool _read_weather_hist(TimeInt year); -void _clear_hist_weather(void); -void SW_WTH_init_run(void); -void SW_WTH_construct(void); -void SW_WTH_deconstruct(void); -void SW_WTH_new_day(void); -void SW_WTH_new_year(void); -void SW_WTH_sum_today(void); -void SW_WTH_end_day(void); - - -#ifdef DEBUG_MEM -void SW_WTH_SetMemoryRefs(void); -#endif - - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/doc/Doxyfile b/doc/Doxyfile index 42e2fba40..777146019 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -261,7 +261,7 @@ TAB_SIZE = 4 # with the commands \{ and \} for these it is advised to use the version @{ and # @} or use a double escape (\\{ and \\}) -ALIASES = "sideeffect=\par \"Side Effects:\"" +ALIASES = "sideeffect=@par Side Effects:^^" # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For @@ -875,9 +875,11 @@ WARN_LOGFILE = # Note: If this tag is empty the current directory is searched. INPUT = . \ + include/ \ + src/ \ doc/additional_pages/ \ - testing/ \ - testing/Input + tests/example/ \ + tests/example/Input # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -925,7 +927,7 @@ RECURSIVE = NO # Note that relative paths are relative to the directory from which doxygen is # run. -EXCLUDE = testing/README.md +EXCLUDE = tests/example/README.md # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -958,8 +960,8 @@ EXCLUDE_SYMBOLS = # that contain example code fragments that are included (see the \include # command). -EXAMPLE_PATH = testing \ - testing/Input +EXAMPLE_PATH = tests/example \ + tests/example/Input # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and @@ -1620,7 +1622,7 @@ FORMULA_MACROFILE = # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. -USE_MATHJAX = NO +USE_MATHJAX = YES # With MATHJAX_VERSION it is possible to specify the MathJax version to be used. # Note that the different versions of MathJax have different requirements with @@ -1666,8 +1668,8 @@ MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example -# for MathJax version 2 (see https://docs.mathjax.org/en/v2.7-latest/tex.html -# #tex-and-latex-extensions): +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # For example for MathJax version 3 (see # http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): diff --git a/doc/SOILWAT2.bib b/doc/SOILWAT2.bib index f3f50a028..508883098 100644 --- a/doc/SOILWAT2.bib +++ b/doc/SOILWAT2.bib @@ -1,20 +1,10 @@ -@Book{ASCE1985, - author = {Jones, E.B., Ward, T.J.}, - title = {Watershed management in the eighties}, - publisher = {American Society of civil Engineers}, - year = 1985, - address = {New York}, - isbn = {0872624498}, - pages = {293-299}, -} - @Article{ASCE2000, author = "Walter, I.A., Allen, A.G., et. al", year = 2000, title = "ASCEs Standardized Reference Evapotranspiration Equation", journal = "Watershed Management and Operations Management 2000", pages = {59}, - } +} @report{ASCE2005, @@ -70,6 +60,7 @@ @Article{Cosby1984 journal = "Water Resources Research", volume = 20, pages = {682-690}, + doi = {10.1029/WR020i006p00682} } @Article{Eitzinger2000, @@ -198,7 +189,6 @@ @Article{Parton1984 @phdthesis{Gerrits2010, title = {The role of interception in the hydrological cycle}, ISBN = {9789065622488}, - url = {http://resolver.tudelft.nl/uuid:7dd2523b-2169-4e7e-992c-365d2294d02e}, school = {Technische Universiteit Delft}, author = {Gerrits, A. M. J.}, year = {2010} @@ -333,6 +323,122 @@ @article{huang2018JAMC doi = {10.1175/JAMC-D-17-0334.1} } +@article{Campbell1974, + title = {A Simple Method for Determining Unsaturated Conductivity from Moisture Retention Data}, + author = {Campbell, Gaylon S.}, + date = {1974}, + journaltitle = {Soil Science}, + volume = {117}, + number = {6}, + pages = {311--314}, + doi = {10.1097/00010694-197406000-00001} +} + +@article{vanGenuchten1980, + title = {A Closed-form Equation for Predicting the Hydraulic Conductivity of Unsaturated Soils}, + author = {van Genuchten, M. Th.}, + date = {1980}, + journaltitle = {Soil Science Society of America Journal}, + volume = {44}, + number = {5}, + pages = {892--898}, + doi = {10.2136/sssaj1980.03615995004400050002x} +} + +@article{zhang2017JoH, + title = {Weighted Recalibration of the Rosetta Pedotransfer Model with Improved Estimates of Hydraulic Parameter Distributions and Summary Statistics (Rosetta3)}, + author = {Zhang, Yonggen and Schaap, Marcel G.}, + year = {2017}, + journal = {Journal of Hydrology}, + volume = {547}, + pages = {39--53}, + doi = {10.1016/j.jhydrol.2017.01.004} +} + +@book{brooks1964a, + title = {Hydraulic Properties of Porous Media. Hydrology Papers: 3}, + author = {Brooks, R. H. and Corey, A. T.}, + year = {1964}, + publisher = {Colorado State University}, + address = {Fort Collins, Colorado, USA} +} + +@incollection{rawls1985WmitE, + title = {Prediction of Soil Water Properties for Hydrological Modeling}, + booktitle = {Watershed Management in the Eighties}, + author = {Rawls, W J and Brakensiek, D L}, + editor = {Jones, E B and Ward, T. J.}, + year = {1985}, + pages = {293--299}, + publisher = {American Society of Civil Engineers}, + address = {New York, USA} +} + + +@article{fredlund1994CGJa, + title = {Equations for the soil-water characteristic curve}, + author = {Fredlund, D. G. and Xing, A. Q.}, + year = {1994}, + journal = {Canadian Geotechnical Journal}, + volume = {31}, + pages = {512–-532}, + doi = {10.1139/t94-061} +} + +@article{rudiyanto2020JoH, + title = {Simple Functions for Describing Soil Water Retention and the Unsaturated Hydraulic Conductivity from Saturation to Complete Dryness}, + author = {{Rudiyanto} and Minasny, Budiman and Shah, Ramisah M. and Setiawan, Budi I. and {van Genuchten}, Martinus Th.}, + year = {2020}, + journal = {Journal of Hydrology}, + volume = {588}, + pages = {125041}, + doi = {10.1016/j.jhydrol.2020.125041} +} + +@article{rudiyanto2021G, + title = {Pedotransfer Functions for Estimating Soil Hydraulic Properties from Saturation to Dryness}, + author = {{Rudiyanto} and Minasny, Budiman and Chaney, Nathaniel W. and Maggi, Federico and Goh Eng Giap, Sunny and Shah, Ramisah M. and Fiantis, Dian and Setiawan, Budi I.}, + year = {2021}, + journal = {Geoderma}, + volume = {403}, + pages = {115194}, + issn = {0016-7061}, + doi = {10.1016/j.geoderma.2021.115194} +} + + +@article{wang2018wrr, + title = {Alternative Model for Predicting Soil Hydraulic Conductivity Over the Complete Moisture Range}, + author = {Wang, Yunquan and Jin, Menggui and Deng, Zijuan}, + year = {2018}, + journal = {Water Resources Research}, + volume = {54}, + number = {9}, + pages = {6860--6876}, + doi = {10.1029/2018WR023037} +} + +@article{wang2022WRRa, + title = {Development of a New Pedotransfer Function Addressing Limitations in Soil Hydraulic Models and Observations}, + author = {Wang, Yunquan and Zhou, Jieliang and Ma, Rui and Zhu, Gaofeng and Zhang, Yongyong}, + year = {2022}, + journal = {Water Resources Research}, + volume = {58}, + number = {6}, + pages = {e2021WR031406}, + doi = {10.1029/2021WR031406} +} + +@article{oliveira2021ATMS, + title = {An Enhancement of the Bisection Method Average Performance Preserving Minmax Optimality}, + author = {Oliveira, I. F. D. and Takahashi, R. H. C.}, + year = {2021}, + journal = {ACM Transactions on Mathematical Software}, + volume = {47}, + number = {1}, + pages = {1--24}, + doi = {10.1145/3423597} +} @article{marsaglia1964SR, title = {A Convenient Method for Generating Normal Variables}, @@ -347,4 +453,24 @@ @article{marsaglia1964SR doi = {10.1137/1006063} } +@article{paruelo1996EA, + title = {Relative Abundance of Plant Functional Types in Grasslands and Shrublands of North America}, + author = {Paruelo, Jose M. and Lauenroth, W. K.}, + year = {1996}, + journal = {Ecological Applications}, + volume = {6}, + number = {4}, + pages = {1212--1224}, + doi = {10.2307/2269602} +} +@article{teeri1976O, + title = {Climatic Patterns and the Distribution of C4 Grasses in North America}, + author = {Teeri, J. A. and Stowe, L. G.}, + year = {1976}, + journal = {Oecologia}, + volume = {23}, + number = {1}, + pages = {1--12}, + doi = {10.1007/bf00351210} +} diff --git a/doc/additional_pages/A_SOILWAT2_user_guide.md b/doc/additional_pages/A_SOILWAT2_user_guide.md index 8ca3ce03d..6a19030f1 100644 --- a/doc/additional_pages/A_SOILWAT2_user_guide.md +++ b/doc/additional_pages/A_SOILWAT2_user_guide.md @@ -133,18 +133,16 @@ on your side. ### Example - * The source code contains a complete example simulation project in `testing/` - * Copy the executable to the testing path, modify inputs as desired, - and run a simulation, e.g., + * The source code contains a complete example simulation project in + `tests/example/` + * Modify inputs as desired and run a simulation, e.g., ```{.sh} - make bint bint_run + make bin_run ``` or, equivalently, ```{.sh} make bin - cp SOILWAT2 testing/ - cd testing/ - ./SOILWAT2 + bin/SOILWAT2 -d ./tests/example -f files.in ``` * The inputs comprise the master file `files.in` and the content of the @@ -154,7 +152,7 @@ on your side. warnings and errors. The output files are in `.csv` format and can be opened by a spreadsheet program (e.g., `LibreOffice` or `Excel`) or imported into `R` - (e.g., `data <- read.csv("testing/Output/sw2_yearly.csv")`). + (e.g., `data <- read.csv("tests/example/Output/sw2_yearly.csv")`). Outputs are explained in detail [here](doc/additional_pages/SOILWAT2_Inputs.md). diff --git a/doc/additional_pages/SOILWAT2_Inputs.md b/doc/additional_pages/SOILWAT2_Inputs.md index ab7d23509..533408119 100644 --- a/doc/additional_pages/SOILWAT2_Inputs.md +++ b/doc/additional_pages/SOILWAT2_Inputs.md @@ -8,20 +8,19 @@ Note: this document is best viewed as part of the doxygen-built documentation
### Example - * The source code contains a complete example simulation project in `testing/` - * Copy the executable to the testing path, modify inputs as desired, - and run a simulation, e.g., + * The source code contains a complete example simulation project in + `tests/example/` + * Modify inputs as desired and run a simulation, e.g., ```{.sh} - make bint bint_run + make bin_run ``` or, equivalently, ```{.sh} make bin - cp SOILWAT2 testing/ - cd testing/ - ./SOILWAT2 + bin/SOILWAT2 -d ./tests/example -f files.in ``` + * The inputs comprise the master file `files.in` and the content of the `Input/` folder. They are explained in detail \ref explain_inputs "below". @@ -41,6 +40,7 @@ SOILWAT2 needs the following input files for a simulation run: \refitem yearsin years.in \refitem siteparamin siteparam.in \refitem soilsin soils.in +\refitem swrcpin swrc_params.in \refitem weathsetupin weathsetup.in \refitem mkvprobin mkv_prob.in \refitem mkvcovarin mkv_covar.in @@ -63,72 +63,78 @@ Go back to the \ref explain_inputs "list of input files"
\section yearsin years.in -\verbinclude testing/Input/years.in +\verbinclude tests/example/Input/years.in Go back to the \ref explain_inputs "list of input files"
\section siteparamin siteparam.in -\verbinclude testing/Input/siteparam.in +\verbinclude tests/example/Input/siteparam.in Go back to the \ref explain_inputs "list of input files"
\section soilsin soils.in -\verbinclude testing/Input/soils.in +\verbinclude tests/example/Input/soils.in + +Go back to the \ref explain_inputs "list of input files" +
+ +\section swrcpin swrc_params.in +\verbinclude tests/example/Input/swrc_params.in Go back to the \ref explain_inputs "list of input files"
\section weathsetupin weathsetup.in -\verbinclude testing/Input/weathsetup.in +\verbinclude tests/example/Input/weathsetup.in Go back to the \ref explain_inputs "list of input files"
\section mkvprobin mkv_prob.in -\verbinclude testing/Input/mkv_prob.in +\verbinclude tests/example/Input/mkv_prob.in Go back to the \ref explain_inputs "list of input files"
\section mkvcovarin mkv_covar.in -\verbinclude testing/Input/mkv_covar.in +\verbinclude tests/example/Input/mkv_covar.in Go back to the \ref explain_inputs "list of input files"
\section climatein climate.in -\verbinclude testing/Input/climate.in +\verbinclude tests/example/Input/climate.in Go back to the \ref explain_inputs "list of input files"
\section vegin veg.in -\verbinclude testing/Input/veg.in +\verbinclude tests/example/Input/veg.in Go back to the \ref explain_inputs "list of input files"
\section estabin estab.in -\verbinclude testing/Input/estab.in +\verbinclude tests/example/Input/estab.in
Go back to the \ref explain_inputs "list of input files" \section carbonin carbon.in -\verbinclude testing/Input/carbon.in +\verbinclude tests/example/Input/carbon.in Go back to the \ref explain_inputs "list of input files"
\section swcsetupin swcsetup.in -\verbinclude testing/Input/swcsetup.in +\verbinclude tests/example/Input/swcsetup.in Go back to the \ref explain_inputs "list of input files"
\section outsetupin outsetup.in -\verbinclude testing/Input/outsetup.in +\verbinclude tests/example/Input/outsetup.in Go back to the \ref explain_inputs "list of input files"
diff --git a/doc/additional_pages/SOILWAT2_Outputs.md b/doc/additional_pages/SOILWAT2_Outputs.md index b46d71176..391b2f4bf 100644 --- a/doc/additional_pages/SOILWAT2_Outputs.md +++ b/doc/additional_pages/SOILWAT2_Outputs.md @@ -8,18 +8,16 @@ Note: this document is best viewed as part of the doxygen-built documentation
### Example - * The source code contains a complete example simulation project in `testing/` - * Copy the executable to the testing path, modify inputs as desired, - and run a simulation, e.g., + * The source code contains a complete example simulation project in + `tests/example/` + * Modify inputs as desired and run a simulation, e.g., ```{.sh} - make bint bint_run + make bin_run ``` or, equivalently, ```{.sh} make bin - cp SOILWAT2 testing/ - cd testing/ - ./SOILWAT2 + bin/SOILWAT2 -d ./tests/example -f files.in ``` * The inputs comprise the master file `files.in` and the content of the diff --git a/doc/doxygen_exceptions.txt b/doc/doxygen_exceptions.txt index 8b1378917..1d7af4499 100755 --- a/doc/doxygen_exceptions.txt +++ b/doc/doxygen_exceptions.txt @@ -1 +1,2 @@ - +SOILWAT2/NEWS.md:[0-9]\+: warning: Found unknown command +SOILWAT2/README.md:[0-9]\+: warning: unable to resolve reference to diff --git a/googletest b/external/googletest similarity index 100% rename from googletest rename to external/googletest diff --git a/pcg b/external/pcg similarity index 100% rename from pcg rename to external/pcg diff --git a/SW_Carbon.h b/include/SW_Carbon.h similarity index 94% rename from SW_Carbon.h rename to include/SW_Carbon.h index 9a63525df..ec46c4494 100644 --- a/SW_Carbon.h +++ b/include/SW_Carbon.h @@ -7,7 +7,7 @@ #ifndef CARBON #define CARBON -#include "SW_Defines.h" +#include "include/SW_Defines.h" #ifdef __cplusplus diff --git a/SW_Control.h b/include/SW_Control.h similarity index 92% rename from SW_Control.h rename to include/SW_Control.h index 246fa4fbd..acbd2aeb3 100644 --- a/SW_Control.h +++ b/include/SW_Control.h @@ -18,7 +18,7 @@ #ifndef SW_CONTROL_H #define SW_CONTROL_H -#include "generic.h" // for `Bool`, `swTRUE`, `swFALSE` +#include "include/generic.h" // for `Bool`, `swTRUE`, `swFALSE` #ifdef __cplusplus extern "C" { diff --git a/SW_Defines.h b/include/SW_Defines.h similarity index 83% rename from SW_Defines.h rename to include/SW_Defines.h index 586ef1c57..ffe4cb8c7 100644 --- a/SW_Defines.h +++ b/include/SW_Defines.h @@ -20,7 +20,7 @@ #define SOILW_DEF_H #include /* >= C99; for: atan(), isfinite() */ -#include "generic.h" +#include "include/generic.h" #ifdef __cplusplus extern "C" { @@ -51,13 +51,20 @@ extern "C" { #define SLOW_DRAIN_DEPTH 15. /* numerator over depth in slow drain equation */ /* some basic constants */ -#define MAX_LAYERS 25 -#define MAX_TRANSP_REGIONS 4 -#define MAX_ST_RGR 100 +#define MAX_LAYERS 25 /**< Maximum number of soil layers */ +#define MAX_TRANSP_REGIONS 4 /**< Maximum number of transpiration regions */ +#define MAX_ST_RGR 100 /**< Maximum number of soil temperature nodes */ #define MAX_NYEAR 2500 /**< An integer representing the max calendar year that is supported. The number just needs to be reasonable, it is an artifical limit. */ -#define SW_MISSING 999. /* value to use as MISSING */ +#define SW_MISSING 999. /**< Value to use as MISSING */ + +// Euler's constant +#ifdef M_E + #define swE M_E +#else + #define swE 2.71828182845904523536028747135266249 +#endif /* M_PI and M_PI_2 from if implementation conforms to POSIX extension @@ -80,7 +87,6 @@ extern "C" { #define deg_to_rad 0.0174532925199433 /**< Convert arc-degrees to radians, i.e., x * deg_to_rad with deg_to_rad = pi / 180 */ #define rad_to_deg 57.29577951308232 /**< Convert radians to arc-degrees, i.e., x * rad_to_deg with rad_to_deg = 180 / pi */ -#define BARCONV 1024. #define SEC_PER_DAY 86400. // the # of seconds in a day... (24 hrs * 60 mins/hr * 60 sec/min = 86400 seconds) @@ -104,12 +110,32 @@ extern "C" { #define SW_MAX 1 /* indices to vegetation types */ -#define NVEGTYPES 4 +#define NVEGTYPES 4 /**< Number of vegetation types implemented */ #define SW_TREES 0 #define SW_SHRUB 1 #define SW_FORBS 2 #define SW_GRASS 3 +/* + Indices to daily input flags/indices (dailyInputFlags & dailyInputIndices in SW_WEATHER) + The order of these indices must match the order of weather input flags within `weathsetup.in` +*/ +#define MAX_INPUT_COLUMNS 14 /**< Maximum number of columns that can be input in a weath.YYYY file*/ +#define TEMP_MAX 0 +#define TEMP_MIN 1 +#define PPT 2 +#define CLOUD_COV 3 +#define WIND_SPEED 4 +#define WIND_EAST 5 +#define WIND_NORTH 6 +#define REL_HUMID 7 +#define REL_HUMID_MAX 8 +#define REL_HUMID_MIN 9 +#define SPEC_HUMID 10 +#define TEMP_DEWPOINT 11 +#define ACTUAL_VP 12 +#define SHORT_WR 13 + /* output period specifiers */ #define SW_DAY "DY" diff --git a/SW_Files.h b/include/SW_Files.h similarity index 77% rename from SW_Files.h rename to include/SW_Files.h index 6c3e10c99..c0b97d50a 100644 --- a/SW_Files.h +++ b/include/SW_Files.h @@ -20,16 +20,30 @@ extern "C" { #endif -#define SW_NFILES 22 +#define SW_NFILES 23 /* The number of enum elements between eNoFile and * eEndFile (not inclusive) must match SW_NFILES. * also, these elements must match the order of - * input from files.in. + * input from `files.in`. */ typedef enum { - eNoFile = -1, eFirst = 0, eModel, eLog, eSite, eLayers, eWeather, - eMarkovProb, eMarkovCov, eSky, eVegProd, eVegEstab, eCarbon, eSoilwat, + eNoFile = -1, + /* List of all input files */ + eFirst = 0, + /* Description of a model run */ + eModel, eLog, + /* Description of simulated site */ + eSite, eLayers, eSWRCp, + /* Weather and climate forcing */ + eWeather, eMarkovProb, eMarkovCov, eSky, + /* Description of vegetation */ + eVegProd, eVegEstab, + /* Description of CO2 effects */ + eCarbon, + /* (optional) soil moisture measurements */ + eSoilwat, + /* Simulation outputs */ eOutput, eOutputDaily, eOutputWeekly, eOutputMonthly, eOutputYearly, eOutputDaily_soil, eOutputWeekly_soil, eOutputMonthly_soil, eOutputYearly_soil, eEndFile diff --git a/SW_Flow.h b/include/SW_Flow.h similarity index 100% rename from SW_Flow.h rename to include/SW_Flow.h diff --git a/SW_Flow_lib.h b/include/SW_Flow_lib.h similarity index 100% rename from SW_Flow_lib.h rename to include/SW_Flow_lib.h diff --git a/SW_Flow_lib_PET.h b/include/SW_Flow_lib_PET.h similarity index 75% rename from SW_Flow_lib_PET.h rename to include/SW_Flow_lib_PET.h index e605d3541..915c16499 100644 --- a/SW_Flow_lib_PET.h +++ b/include/SW_Flow_lib_PET.h @@ -35,11 +35,15 @@ double overcast_attenuation_KastenCzeplak1980(double cloud_cover); double overcast_attenuation_Angstrom1924(double sunshine_fraction); double clearsky_directbeam(double P, double e_a, double int_sin_beta); double clearnessindex_diffuse(double K_b); +double actual_horizontal_transmissivityindex(double tau); -double solar_radiation(unsigned int doy, +double solar_radiation( + unsigned int doy, double lat, double elev, double slope, double aspect, - double albedo, double cloud_cover, double rel_humidity, double air_temp_mean, - double *H_oh, double *H_ot, double *H_gh); + double albedo, double *cloud_cover, double e_a, + double rsds, unsigned int desc_rsds, + double *H_oh, double *H_ot, double *H_gh +); double blackbody_radiation(double T); @@ -49,9 +53,14 @@ double petfunc(double H_g, double avgtemp, double elev, double reflec, double humid, double windsp, double cloudcov); double svp(double T, double *slope_svp_to_t); +double svp2(double temp); double atmospheric_pressure(double elev); double psychrometric_constant(double pressure); +double actualVaporPressure1(double hurs, double meanTemp); +double actualVaporPressure2(double maxHurs, double minHurs, double maxTemp, double minTemp); +double actualVaporPressure3(double dewpointTemp); + #ifdef __cplusplus } #endif diff --git a/SW_Main_lib.h b/include/SW_Main_lib.h similarity index 90% rename from SW_Main_lib.h rename to include/SW_Main_lib.h index a94465a77..149d93186 100644 --- a/SW_Main_lib.h +++ b/include/SW_Main_lib.h @@ -17,7 +17,7 @@ extern "C" { /* --------------------------------------------------- */ void sw_init_args(int argc, char **argv); void sw_print_version(void); - +void sw_check_log(void); #ifdef __cplusplus } diff --git a/SW_Markov.h b/include/SW_Markov.h similarity index 95% rename from SW_Markov.h rename to include/SW_Markov.h index ce0e8c36f..e7e99854a 100644 --- a/SW_Markov.h +++ b/include/SW_Markov.h @@ -13,7 +13,7 @@ #ifndef SW_MARKOV_H #define SW_MARKOV_H -#include "pcg/pcg_basic.h" +#include "external/pcg/pcg_basic.h" #ifdef __cplusplus extern "C" { diff --git a/SW_Model.h b/include/SW_Model.h similarity index 94% rename from SW_Model.h rename to include/SW_Model.h index 2cb5d4862..4087c32df 100644 --- a/SW_Model.h +++ b/include/SW_Model.h @@ -20,8 +20,8 @@ #ifndef SW_MODEL_H #define SW_MODEL_H -#include "Times.h" -#include "SW_Defines.h" +#include "include/Times.h" +#include "include/SW_Defines.h" #ifdef __cplusplus extern "C" { diff --git a/SW_Output.h b/include/SW_Output.h similarity index 95% rename from SW_Output.h rename to include/SW_Output.h index e9c2980f7..8b1917ac1 100644 --- a/SW_Output.h +++ b/include/SW_Output.h @@ -56,11 +56,10 @@ #ifndef SW_OUTPUT_H #define SW_OUTPUT_H -#include "Times.h" -#include "SW_Defines.h" -#include "SW_SoilWater.h" -#include "SW_Weather.h" -#include "SW_VegProd.h" +#include "include/Times.h" +#include "include/SW_Defines.h" +#include "include/SW_SoilWater.h" +#include "include/SW_VegProd.h" #ifdef __cplusplus extern "C" { @@ -235,6 +234,7 @@ extern IntUS ncol_OUT[SW_OUTNKEYS]; extern char const *key2str[]; extern char const *pd2longstr[]; +extern char const *styp2str[]; @@ -247,7 +247,7 @@ void SW_OUT_deconstruct(Bool full_reset); void SW_OUT_set_ncol(void); void SW_OUT_set_colnames(void); void SW_OUT_new_year(void); -int SW_OUT_read_onekey(OutKey k, OutSum sumtype, int first, int last, char msg[]); +int SW_OUT_read_onekey(OutKey k, OutSum sumtype, int first, int last, char msg[], size_t sizeof_msg); void SW_OUT_read(void); void SW_OUT_sum_today(ObjType otyp); void SW_OUT_write_today(void); diff --git a/SW_Output_outarray.h b/include/SW_Output_outarray.h similarity index 100% rename from SW_Output_outarray.h rename to include/SW_Output_outarray.h diff --git a/SW_Output_outtext.h b/include/SW_Output_outtext.h similarity index 94% rename from SW_Output_outtext.h rename to include/SW_Output_outtext.h index 442148488..5eca8a185 100644 --- a/SW_Output_outtext.h +++ b/include/SW_Output_outtext.h @@ -70,7 +70,7 @@ void SW_OUT_create_summary_files(void); void SW_OUT_create_iteration_files(int iteration); #endif -void get_outstrleader(OutPeriod pd, char *str); +void get_outstrleader(OutPeriod pd, char *str, size_t sizeof_str); void write_headers_to_csv(OutPeriod pd, FILE *fp_reg, FILE *fp_soil, Bool does_agg); void find_TXToutputSoilReg_inUse(void); void SW_OUT_close_files(void); diff --git a/SW_Site.h b/include/SW_Site.h similarity index 57% rename from SW_Site.h rename to include/SW_Site.h index 9b7ad141d..0a48b8f69 100644 --- a/SW_Site.h +++ b/include/SW_Site.h @@ -47,12 +47,118 @@ #ifndef SW_SITE_H #define SW_SITE_H -#include "SW_Defines.h" +#include "include/SW_Defines.h" #ifdef __cplusplus extern "C" { #endif +#define SW_MATRIC 0 +#define SW_BULK 1 + + +/** + @defgroup swrc_ptf Soil Water Retention Curves + + __Soil Water Retention Curves (SWRCs) -- Pedotransfer functions (PTFs)__ + + __Overview__ + + Historically (before v7.0.0), `SOILWAT2` utilized a hard-coded SWRC + by Campbell 1974 (\cite Campbell1974) and estimated SWRC parameters at + run-time from soil texture using PTFs by Cosby et al. 1984 (\cite Cosby1984). + This behavior can be reproduced with "Campbell1974" and "Cosby1984AndOthers" + (see input file `siteparam.in`). + + Now, users of `SOILWAT2` can choose from a range of implemented SWRCs + (see input file `siteparam.in`); + SWRC parameters can be estimated at run-time from soil properties by + selecting a matching PTF (see input file `siteparam.in`) or, + alternatively (`has_swrcp`), provide adequate SWRC parameter values + (see input file `swrc_params.in`). + Please note that `rSOILWAT2` may provide additional PTF functionality. + + + __Approach__ + + -# User selections of SWRC and PTF are read in from input file `siteparam.in` + by `SW_SIT_read()` and, if `has_swrcp`, SWRC parameters are read from + input file `swrc_params.in` by `SW_SWRC_read()`. + + -# `SW_SIT_init_run()` + - if not `has_swrcp` + - calls `check_SWRC_vs_PTF()` to check that selected SWRC and PTF are + compatible + - calls `SWRC_PTF_estimate_parameters()` to estimate + SWRC parameter values from soil properties based on selected PTF + - calls `SWRC_check_parameters()` to check that SWRC parameter values + are resonable for the selected SWRC. + + -# `SW_SWRC_SWCtoSWP()` and `SW_SWRC_SWPtoSWC()` are used during simulation + runs to convert between soil water content and soil water potential. + + -# These high-level "wrapper" functions hide details of any specific SWRC/PTF + implementations and are used by SOILWAT2 simulation code. + Thus, most of SOILWAT2 is "unaware" about the selected SWRC/PTF + and how to interpret SWRC parameters. Instead, these "wrapper" functions + know how to call the appropriate SWRC and/or PTF specific functions + which implement SWRC and/or PTF specific details. + + + __Steps to implement a new SWRC "XXX" and corresponding PTF "YYY"__ + + -# Update #N_SWRCs and #N_PTFs + + -# Add new names to #swrc2str and #ptf2str and + add corresponding macros of indices + + -# Update input files `siteparam.in` and `swrc_params.in` + + -# Implement new XXX/YYY-specific functions + - `SWRC_check_parameters_for_XXX()` to validate parameter values, + e.g., `SWRC_check_parameters_for_Campbell1974()` + - `SWRC_PTF_YYY_for_XXX()` to estimate parameter values (if implemented), + e.g., `SWRC_PTF_Cosby1984_for_Campbell1974()` + - `SWRC_SWCtoSWP_XXX()` to translate moisture content to water potential, + e.g., `SWRC_SWCtoSWP_Campbell1974()` + - `SWRC_SWPtoSWC_XXX()` to translate water potential to moisture content, + e.g., `SWRC_SWPtoSWC_Campbell1974()` + + -# Update "wrapper" functions that select and call XXX/YYY-specific functions + and/or parameters + - `check_SWRC_vs_PTF()` + - `SWRC_PTF_estimate_parameters()` (if PTF is implemented) + - `SWRC_check_parameters()` + - `SWRC_SWCtoSWP()` + - `SWRC_SWPtoSWC()` + - `SW_swcBulk_minimum()` + - `SW_swcBulk_saturated()` + + -# Expand existing unit tests and add new unit tests + to utilize new XXX/YYY functions +*/ + +#define SWRC_PARAM_NMAX 6 /**< Maximal number of SWRC parameters implemented */ +#define N_SWRCs 3 /**< Number of SWRCs implemented by SOILWAT2 */ +#define N_PTFs 2 /**< Number of PTFs implemented by SOILWAT2 */ + +// Indices of #swrc2str (for code readability) +#define sw_Campbell1974 0 +#define sw_vanGenuchten1980 1 +#define sw_FXW 2 + +// Indices of #swrc2str (for code readability) +#define sw_Cosby1984AndOthers 0 +#define sw_Cosby1984 1 + + +#define FXW_h0 6.3e6 /**< Pressure head at zero water content [cm] of FWX SWRC */ +#define FXW_hr 1500. /**< Pressure head at residual water content [cm] of FXW SWRC */ + + +/* =================================================== */ +/* TYPEDEFS */ +/* --------------------------------------------------- */ typedef unsigned int LyrIndex; @@ -60,10 +166,12 @@ typedef struct { /* bulk = relating to the whole soil, i.e., matric + rock/gravel/coarse fragments */ /* matric = relating to the < 2 mm fraction of the soil, i.e., sand, clay, and silt */ + LyrIndex id; /**< Number of soil layer: 1 = most shallow, 2 = second shallowest, etc. up to ::MAX_LAYERS */ + RealD /* Inputs */ width, /* width of the soil layer (cm) */ - soilMatric_density, /* matric soil density of the < 2 mm fraction, i.e., gravel component excluded, (g/cm3) */ + soilDensityInput, /* soil density [g / cm3]: either of the matric component or bulk soil */ evap_coeff, /* prop. of total soil evap from this layer */ transp_coeff[NVEGTYPES], /* prop. of total transp from this layer */ fractionVolBulk_gravel, /* gravel content (> 2 mm) as volume-fraction of bulk soil (g/cm3) */ @@ -73,6 +181,7 @@ typedef struct { avgLyrTemp, /* initial soil temperature for each soil layer */ /* Derived soil characteristics */ + soilMatric_density, /* matric soil density of the < 2 mm fraction, i.e., gravel component excluded, (g/cm3) */ soilBulk_density, /* bulk soil density of the whole soil, i.e., including rock/gravel component, (g/cm3) */ swcBulk_fieldcap, /* Soil water content (SWC) corresponding to field capacity (SWP = -0.033 MPa) [cm] */ swcBulk_wiltpt, /* SWC corresponding to wilting point (SWP = -1.5 MPa) [cm] */ @@ -83,17 +192,19 @@ typedef struct { swcBulk_atSWPcrit[NVEGTYPES], /* SWC corresponding to critical SWP for transpiration */ /* Saxton et al. 2006 */ - swcBulk_saturated, /* saturated bulk SWC [cm] */ - Saxton2006_K_sat_matric, /* saturated matric conductivity [cm / day] */ - Saxton2006_K_sat_bulk, /* saturated bulk conductivity [cm / day] */ - Saxton2006_fK_gravel, /* gravel-correction factor for conductivity [1] */ - Saxton2006_lambda, /* Slope of logarithmic tension-moisture curve */ - - /* Cosby et al. (1984): SOILWAT2's soil water retention curve */ - thetasMatric, /* saturated matric SWC [cm] */ - psisMatric, /* saturated matric SWP [cm] */ - bMatric, /* slope of the logarithmic retention curve */ - binverseMatric; /* inverse of bMatric */ + swcBulk_saturated; /* saturated bulk SWC [cm] */ + // currently, not used; + //Saxton2006_K_sat_matric, /* saturated matric conductivity [cm / day] */ + //Saxton2006_K_sat_bulk, /* saturated bulk conductivity [cm / day] */ + //Saxton2006_fK_gravel, /* gravel-correction factor for conductivity [1] */ + //Saxton2006_lambda; /* Slope of logarithmic tension-moisture curve */ + + + /* Soil water retention curve (SWRC) */ + unsigned int + swrc_type, /**< Type of SWRC (see #swrc2str) */ + ptf_type; /**< Type of PTF (see #ptf2str) */ + RealD swrcp[SWRC_PARAM_NMAX]; /**< Parameters of SWRC: parameter interpretation varies with selected SWRC, see `SWRC_check_parameters()` */ LyrIndex my_transp_rgn[NVEGTYPES]; /* which transp zones from Site am I in? */ } SW_LAYER_INFO; @@ -105,6 +216,8 @@ typedef struct { deepdrain, /* 1: allow drainage into deepest layer */ use_soil_temp; /* whether or not to do soil_temperature calculations */ + unsigned int type_soilDensityInput; /* Encodes whether `soilDensityInput` represent matric density (type = SW_MATRIC = 0) or bulk density (type = SW_BULK = 1) */ + LyrIndex n_layers, /* total number of soil layers */ n_transp_rgn, /* soil layers are grouped into n transp. regions */ n_evap_lyrs, /* number of layers in which evap is possible */ @@ -148,6 +261,17 @@ typedef struct { SW_LAYER_INFO **lyr; /* one struct per soil layer pointed to by */ /* a dynamically allocated block of pointers */ + /* Soil water retention curve (SWRC), see `SW_LAYER_INFO` */ + unsigned int + site_swrc_type, + site_ptf_type; + + char + site_swrc_name[64], + site_ptf_name[64]; + + Bool site_has_swrcp; /**< Are `swrcp` already (TRUE) or not yet estimated (FALSE)? */ + } SW_SITE; @@ -159,13 +283,73 @@ extern SW_SITE SW_Site; extern LyrIndex _TranspRgnBounds[MAX_TRANSP_REGIONS]; extern RealD _SWCInitVal, _SWCWetVal, _SWCMinVal; +extern char const *swrc2str[]; +extern char const *ptf2str[]; /* =================================================== */ /* Global Function Declarations */ /* --------------------------------------------------- */ -void water_eqn(RealD fractionGravel, RealD sand, RealD clay, LyrIndex n); + +unsigned int encode_str2swrc(char *swrc_name); +unsigned int encode_str2ptf(char *ptf_name); + +void SWRC_PTF_estimate_parameters( + unsigned int ptf_type, + double *swrcp, + double sand, + double clay, + double gravel, + double bdensity +); +void SWRC_PTF_Cosby1984_for_Campbell1974( + double *swrcp, + double sand, + double clay +); + + +Bool check_SWRC_vs_PTF(char *swrc_name, char *ptf_name); +Bool SWRC_check_parameters(unsigned int swrc_type, double *swrcp); +Bool SWRC_check_parameters_for_Campbell1974(double *swrcp); +Bool SWRC_check_parameters_for_vanGenuchten1980(double *swrcp); +Bool SWRC_check_parameters_for_FXW(double *swrcp); + +double SW_swcBulk_saturated( + unsigned int swrc_type, + double *swrcp, + double gravel, + double width, + unsigned int ptf_type, + double sand, + double clay +); +double SW_swcBulk_minimum( + unsigned int swrc_type, + double *swrcp, + double gravel, + double width, + unsigned int ptf_type, + double ui_sm_min, + double sand, + double clay, + double swcBulk_sat +); +void PTF_Saxton2006( + double *theta_sat, + double sand, + double clay +); +void PTF_RawlsBrakensiek1985( + double *theta_min, + double sand, + double clay, + double porosity +); + + RealD calculate_soilBulkDensity(RealD matricDensity, RealD fractionGravel); +RealD calculate_soilMatricDensity(RealD bulkDensity, RealD fractionGravel); LyrIndex nlayers_bsevap(void); void nlayers_vegroots(LyrIndex n_transp_lyrs[]); @@ -177,11 +361,13 @@ void SW_SIT_init_run(void); void _echo_inputs(void); /* these used to be in Layers */ +void SW_LYR_read(void); +void SW_SWRC_read(void); void SW_SIT_clear_layers(void); LyrIndex _newlayer(void); void add_deepdrain_layer(void); -void set_soillayers(LyrIndex nlyrs, RealF *dmax, RealF *matricd, RealF *f_gravel, +void set_soillayers(LyrIndex nlyrs, RealF *dmax, RealF *bd, RealF *f_gravel, RealF *evco, RealF *trco_grass, RealF *trco_shrub, RealF *trco_tree, RealF *trco_forb, RealF *psand, RealF *pclay, RealF *imperm, RealF *soiltemp, int nRegions, RealD *regionLowerBounds); diff --git a/SW_Sky.h b/include/SW_Sky.h similarity index 80% rename from SW_Sky.h rename to include/SW_Sky.h index c6a8beb74..0868f1f64 100644 --- a/SW_Sky.h +++ b/include/SW_Sky.h @@ -18,7 +18,7 @@ #ifndef SW_SKY_H #define SW_SKY_H -#include "SW_Times.h" +#include "include/SW_Times.h" #ifdef __cplusplus extern "C" { @@ -32,10 +32,7 @@ typedef struct { snow_density [MAX_MONTHS], /* snow density (kg/m3) */ n_rain_per_day[MAX_MONTHS]; /* number of precipitation events per month (currently used in interception functions) */ - RealD cloudcov_daily [MAX_DAYS+1], /* interpolated daily cloud cover (frac) */ - windspeed_daily [MAX_DAYS+1], /* interpolated daily windspeed (m/s) */ - r_humidity_daily [MAX_DAYS+1], /* interpolated daily relative humidity (%) */ - snow_density_daily [MAX_DAYS+1]; /* interpolated daily snow density (kg/m3) */ + RealD snow_density_daily [MAX_DAYS+1]; /* interpolated daily snow density (kg/m3) */ } SW_SKY; @@ -51,7 +48,6 @@ extern SW_SKY SW_Sky; /* Global Function Declarations */ /* --------------------------------------------------- */ void SW_SKY_read(void); -void SW_SKY_init_run(void); void SW_SKY_new_year(void); #ifdef __cplusplus diff --git a/SW_SoilWater.h b/include/SW_SoilWater.h similarity index 85% rename from SW_SoilWater.h rename to include/SW_SoilWater.h index ce7a676b6..d9424fef9 100644 --- a/SW_SoilWater.h +++ b/include/SW_SoilWater.h @@ -45,10 +45,10 @@ extern "C" { #endif -#include "generic.h" -#include "SW_Defines.h" -#include "SW_Times.h" -#include "SW_Site.h" +#include "include/generic.h" +#include "include/SW_Defines.h" +#include "include/SW_Times.h" +#include "include/SW_Site.h" typedef enum { SW_Adjust_Avg = 1, SW_Adjust_StdErr @@ -161,11 +161,67 @@ void SW_SWC_adjust_snow(RealD temp_min, RealD temp_max, RealD ppt, RealD *rain, RealD SW_SWC_snowloss(RealD pet, RealD *snowpack); RealD SW_SnowDepth(RealD SWE, RealD snowdensity); void SW_SWC_end_day(void); -RealD SW_SWCbulk2SWPmatric(RealD fractionGravel, RealD swcBulk, LyrIndex n); -RealD SW_SWPmatric2VWCBulk(RealD fractionGravel, RealD swpMatric, LyrIndex n); -RealD SW_VWCBulkRes(RealD fractionGravel, RealD sand, RealD clay, RealD porosity); void get_dSWAbulk(int i); +double SW_SWRC_SWCtoSWP(double swcBulk, SW_LAYER_INFO *lyr); +double SWRC_SWCtoSWP( + double swcBulk, + unsigned int swrc_type, + double *swrcp, + double gravel, + double width, + const int errmode +); +double SWRC_SWCtoSWP_Campbell1974( + double swcBulk, + double *swrcp, + double gravel, + double width, + const int errmode +); +double SWRC_SWCtoSWP_vanGenuchten1980( + double swcBulk, + double *swrcp, + double gravel, + double width, + const int errmode +); +double SWRC_SWCtoSWP_FXW( + double swcBulk, + double *swrcp, + double gravel, + double width, + const int errmode +); + +RealD SW_SWRC_SWPtoSWC(RealD swpMatric, SW_LAYER_INFO *lyr); +double SWRC_SWPtoSWC( + double swpMatric, + unsigned int swrc_type, + double *swrcp, + double gravel, + double width, + const int errmode +); +double SWRC_SWPtoSWC_Campbell1974( + double swpMatric, + double *swrcp, + double gravel, + double width +); +double SWRC_SWPtoSWC_vanGenuchten1980( + double swpMatric, + double *swrcp, + double gravel, + double width +); +double SWRC_SWPtoSWC_FXW( + double swpMatric, + double *swrcp, + double gravel, + double width +); + #ifdef SWDEBUG void SW_WaterBalance_Checks(void); #endif diff --git a/SW_Times.h b/include/SW_Times.h similarity index 95% rename from SW_Times.h rename to include/SW_Times.h index 401c6d199..439e1bf5f 100644 --- a/SW_Times.h +++ b/include/SW_Times.h @@ -31,7 +31,7 @@ #ifndef SW_TIMES_H #define SW_TIMES_H -#include "Times.h" +#include "include/Times.h" #ifdef __cplusplus extern "C" { diff --git a/SW_VegEstab.h b/include/SW_VegEstab.h similarity index 96% rename from SW_VegEstab.h rename to include/SW_VegEstab.h index 6ba251bfd..dd71c5ac3 100644 --- a/SW_VegEstab.h +++ b/include/SW_VegEstab.h @@ -15,8 +15,8 @@ #ifndef SW_VEGESTAB_H #define SW_VEGESTAB_H -#include "SW_Defines.h" -#include "SW_Times.h" +#include "include/SW_Defines.h" +#include "include/SW_Times.h" #ifdef __cplusplus diff --git a/SW_VegProd.h b/include/SW_VegProd.h similarity index 90% rename from SW_VegProd.h rename to include/SW_VegProd.h index 1641530ca..bb8659613 100644 --- a/SW_VegProd.h +++ b/include/SW_VegProd.h @@ -34,8 +34,8 @@ #ifndef SW_VEGPROD_H #define SW_VEGPROD_H -#include "SW_Defines.h" /* for tanfunc_t*/ -#include "Times.h" // for MAX_MONTHS +#include "include/SW_Defines.h" /* for tanfunc_t*/ +#include "include/Times.h" // for MAX_MONTHS #ifdef __cplusplus extern "C" { @@ -96,14 +96,9 @@ typedef struct { covers 100% of the simulated surface; user input from file `Input/veg.in` */ biomass[MAX_MONTHS], - /** Monthly aboveground biomass after CO2 effects */ - CO2_biomass[MAX_MONTHS], /** Monthly live biomass in percent of aboveground biomass; user input from file `Input/veg.in` */ pct_live[MAX_MONTHS], - /** Monthly live biomass in percent of aboveground biomass after - CO2 effects */ - CO2_pct_live[MAX_MONTHS], /** Parameter to translate biomass to LAI = 1 [g / m2]; user input from file `Input/veg.in` */ lai_conv[MAX_MONTHS]; @@ -238,7 +233,8 @@ typedef struct { int // `rank_SWPcrits[k]` hold the vegetation type at rank `k` of decreasingly // sorted critical SWP values - rank_SWPcrits[NVEGTYPES]; + rank_SWPcrits[NVEGTYPES], + veg_method; SW_VEGPROD_OUTPUTS /** output accumulator: summed values for each output time period */ @@ -263,6 +259,15 @@ void SW_VPD_read(void); void SW_VPD_new_year(void); void SW_VPD_fix_cover(void); void SW_VPD_construct(void); +void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYear, + int veg_method, double latitude); +void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTempMon_C[], + double PPTMon_cm[], double inputValues[], double shrubLimit, double SumGrassesFraction, + double C4Variables[], Bool fillEmptyWithBareGround, Bool inNorthHem, Bool warnExtrapolation, + Bool fixBareGround, double *grassOutput, double *RelAbundanceL0, double *RelAbundanceL1); +double cutZeroInf(double testValue); +void uniqueIndices(int arrayOne[], int arrayTwo[], int arrayOneSize, int arrayTwoSize, + int *finalIndexArray, int *finalIndexArraySize); void SW_VPD_init_run(void); void SW_VPD_deconstruct(void); void apply_biomassCO2effect(double* new_biomass, double *biomass, double multiplier); diff --git a/include/SW_Weather.h b/include/SW_Weather.h new file mode 100644 index 000000000..05d5f55ba --- /dev/null +++ b/include/SW_Weather.h @@ -0,0 +1,294 @@ +/********************************************************/ +/********************************************************/ +/* Source file: SW_Weather.h + Type: header + Application: SOILWAT - soilwater dynamics simulator + Purpose: Support definitions/declarations for + weather-related information. + History: + (8/28/01) -- INITIAL CODING - cwb + 20091014 (drs) added pct_snowdrift as input to weathsetup.in + 20091015 (drs) ppt is divided into rain and snow, added snowmelt + 01/04/2011 (drs) added variable 'snowloss' to SW_WEATHER_NOW and to SW_WEATHER_OUTPUTS + 02/16/2011 (drs) added variable 'pct_runoff' to SW_WEATHER as input to weathsetup.in + 02/19/2011 (drs) added variable 'runoff' to SW_WEATHER_NOW and to SW_WEATHER_OUTPUTS + moved soil_inf from SW_Soilwat to SW_Weather (added to SW_WEATHER and to SW_WEATHER_OUTPUTS) + 06/01/2012 (DLM) added temp_year_avg variable to SW_WEATHER_HIST struct & temp_month_avg[MAX_MONTHS] variable + 11/30/2012 (clk) added variable 'surfaceRunoff' to SW_WEATHER and SW_WEATHER_OUTPUTS + changed 'runoff' to 'snowRunoff' to better distinguish between surface runoff and snowmelt runoff + + */ +/********************************************************/ +/********************************************************/ + +#ifndef SW_WEATHER_H +#define SW_WEATHER_H + +#include "include/SW_Times.h" +#include "include/SW_Defines.h" + +#ifdef __cplusplus +extern "C" { +#endif + + + +/* all temps are in degrees C, all precip is in cm */ +/* in fact, all water variables are in cm throughout + * the model. this facilitates additions and removals + * as they're always in the right units. + */ + +typedef struct { + /* Weather values of the current simulation day */ + RealD temp_avg, temp_max, temp_min, ppt, rain, cloudCover, windSpeed, relHumidity, + shortWaveRad, actualVaporPressure; +} SW_WEATHER_NOW; + +typedef struct { + /* Daily weather values for one year */ + RealD temp_max[MAX_DAYS], temp_min[MAX_DAYS], temp_avg[MAX_DAYS], ppt[MAX_DAYS], + cloudcov_daily[MAX_DAYS], windspeed_daily[MAX_DAYS], r_humidity_daily[MAX_DAYS], + shortWaveRad[MAX_DAYS], actualVaporPressure[MAX_DAYS]; + // RealD temp_month_avg[MAX_MONTHS], temp_year_avg; // currently not used +} SW_WEATHER_HIST; + +/* accumulators for output values hold only the */ +/* current period's values (eg, weekly or monthly) */ +typedef struct { + RealD temp_max, temp_min, temp_avg, ppt, rain, snow, snowmelt, snowloss, /* 20091015 (drs) ppt is divided into rain and snow */ + snowRunoff, surfaceRunoff, surfaceRunon, soil_inf, et, aet, pet, surfaceAvg, surfaceMax, surfaceMin; +} SW_WEATHER_OUTPUTS; + +/** + @brief Annual time-series of climate variables + + Output of the function `calcSiteClimate()` + + @note 2D array dimensions represent month (1st D) and year (2nd D); 1D array dimension represents year. + @note Number of years is variable and determined at runtime. + */ +typedef struct { + RealD **PPTMon_cm, /**< 2D array containing monthly amount precipitation [cm]*/ + *PPT_cm, /**< Array containing annual precipitation amount [cm]*/ + *PPT7thMon_mm, /**< Array containing July precipitation amount in July (northern hemisphere) + or January (southern hemisphere) [mm]*/ + + **meanTempMon_C, /**< 2D array containing monthly mean average daily air temperature [C]*/ + **maxTempMon_C, /**< 2D array containing monthly mean max daily air temperature [C]*/ + **minTempMon_C, /**< 2D array containing monthly mean min daily air temperature [C]*/ + *meanTemp_C, /**< Array containing annual mean temperatures [C]*/ + *meanTempDriestQtr_C, /**< Array containing the average temperatureof the driest quarter of the year [C]*/ + *minTemp2ndMon_C, /**< Array containing the mean daily minimum temperature in August (southern hemisphere) + or February (northern hemisphere) [C]*/ + *minTemp7thMon_C, /**< Array containing minimum July temperatures in July (northern hisphere) + or Janurary (southern hemisphere) [C]*/ + + *frostFree_days, /**< Array containing the maximum consecutive days without frost [days]*/ + *ddAbove65F_degday; /**< Array containing the amount of degree days [C x day] above 65 F*/ +} SW_CLIMATE_YEARLY; + +/** + @brief A structure holding all variables that are output to the function `averageClimateAcrossYears()` #SW_CLIMATE_YEARLY + + @note Values are across-year averages of #SW_CLIMATE_YEARLY and 1D array dimension represents month. + The exceptions are `sdC4` and `sdCheatgrass` which represent across-year standard devations and the 1D array dimension + represents different variables, see `averageClimateAcrossYears()`. + */ +typedef struct { + RealD *meanTempMon_C, /**< Array of size MAX_MONTHS containing sum of monthly mean temperatures [C]*/ + *maxTempMon_C, /**< Array of size MAX_MONTHS containing sum of monthly maximum temperatures [C]*/ + *minTempMon_C, /**< Array of size MAX_MONTHS containing sum of monthly minimum temperatures [C]*/ + *PPTMon_cm, /**< Array of size MAX_MONTHS containing sum of monthly mean precipitation [cm]*/ + *sdC4, /**< Array of size three holding the standard deviations of: 0) minimum July (northern hisphere) + or Janurary (southern hemisphere) temperature [C], + 1) frost free days [days], 2) number of days above 65F [C x day]*/ + + *sdCheatgrass, /**< Array of size three holding: 0) the standard deviations of July (northern hisphere) + or Janurary (southern hemisphere) [cm], + 1) mean temperature of dry quarter [C], 2) mean minimum temperature of February + (northern hemisphere) or August (southern hemisphere) [C]*/ + meanTemp_C, /**< Value containing the average of yearly temperatures [C]*/ + PPT_cm, /**< Value containing the average of yearly precipitation [cm]*/ + PPT7thMon_mm, /**< Value containing average precipitation in July (northern hemisphere) + or January (southern hemisphere) [mm]*/ + meanTempDriestQtr_C, /**< Value containing average of mean temperatures in the driest quarters of years [C]*/ + minTemp2ndMon_C, /**< Value containing average of minimum temperatures in August (southern hemisphere) or + February (northern hemisphere) [C]*/ + ddAbove65F_degday, /**< Value containing average of total degrees above 65F (18.33C) throughout the year [C x day]*/ + frostFree_days, /**< Value containing average of most consectutive days in a year without frost [days]*/ + minTemp7thMon_C; /**< Value containing the average of lowest temperature in July (northern hisphere) + or Janurary (southern hemisphere) [C]*/ +} SW_CLIMATE_CLIM; + +typedef struct { + RealD **meanMonthlyTemp_C, **maxMonthlyTemp_C, **minMonthlyTemp_C, **monthlyPPT_cm, + *annualPPT_cm, *meanAnnualTemp_C, *JulyMinTemp, *frostFreeDays_days, *ddAbove65F_degday, + *JulyPPT_mm, *meanTempDriestQuarter_C, *minTempFebruary_C; +} SW_CLIMATE_CALC; + +typedef struct { + RealD *meanMonthlyTempAnn, *maxMonthlyTempAnn, *minMonthlyTempAnn, *meanMonthlyPPTAnn, + *sdC4, *sdCheatgrass, MAT_C, MAP_cm, JulyPPTAnn_mm, meanTempDriestQuarterAnn_C, minTempFebruaryAnn_C, + ddAbove65F_degdayAnn, frostFreeAnn, JulyMinTempAnn; +} SW_CLIMATE_AVERAGES; + +typedef struct { + + Bool + use_snow, + use_weathergenerator_only; + // swTRUE: use weather generator and ignore weather inputs + + unsigned int + generateWeatherMethod; + // see `generateMissingWeather()` + // 0 : pass through missing values + // 1 : LOCF (temp) + 0 (ppt) + // 2 : weather generator (previously, `use_weathergenerator`) + + int rng_seed; // initial state for `mark + + RealD pct_snowdrift, pct_snowRunoff; + RealD + scale_precip[MAX_MONTHS], + scale_temp_max[MAX_MONTHS], + scale_temp_min[MAX_MONTHS], + scale_skyCover[MAX_MONTHS], + scale_wind[MAX_MONTHS], + scale_rH[MAX_MONTHS], + scale_actVapPress[MAX_MONTHS], + scale_shortWaveRad[MAX_MONTHS]; + char name_prefix[MAX_FILENAMESIZE - 5]; // subtract 4-digit 'year' file type extension + RealD snowRunoff, surfaceRunoff, surfaceRunon, soil_inf, surfaceAvg; + RealD snow, snowmelt, snowloss, surfaceMax, surfaceMin; + + Bool use_cloudCoverMonthly, use_windSpeedMonthly, use_humidityMonthly; + Bool dailyInputFlags[MAX_INPUT_COLUMNS]; + + unsigned int dailyInputIndices[MAX_INPUT_COLUMNS], + n_input_forcings, // Number of input columns found in weath.YYYY + desc_rsds; /**< Description of units and definition of daily inputs of observed shortwave radiation, see `solar_radiation()` */ + + /* This section is required for computing the output quantities. */ + SW_WEATHER_OUTPUTS + *p_accu[SW_OUTNPERIODS], // output accumulator: summed values for each time period + *p_oagg[SW_OUTNPERIODS]; // output aggregator: mean or sum for each time periods + + + /* Daily weather record */ + SW_WEATHER_HIST **allHist; /**< Daily weather values; array of length `n_years` of pointers to struct #SW_WEATHER_HIST where the first represents values for calendar year `startYear` */ + unsigned int n_years; /**< Length of `allHist`, i.e., number of years of daily weather */ + unsigned int startYear; /**< Calendar year corresponding to first year of `allHist` */ + + SW_WEATHER_NOW now; /**< Weather values of the current simulation day */ + +} SW_WEATHER; + + + +/* =================================================== */ +/* Externed Global Variables */ +/* --------------------------------------------------- */ +extern SW_WEATHER SW_Weather; + + +/* =================================================== */ +/* Global Function Declarations */ +/* --------------------------------------------------- */ +void SW_WTH_setup(void); +void set_dailyInputIndices( + Bool dailyInputFlags[MAX_INPUT_COLUMNS], + unsigned int dailyInputIndices[MAX_INPUT_COLUMNS], + unsigned int *n_input_forcings +); +void check_and_update_dailyInputFlags( + Bool use_cloudCoverMonthly, + Bool use_humidityMonthly, + Bool use_windSpeedMonthly, + Bool *dailyInputFlags +); +void SW_WTH_read(void); +void averageClimateAcrossYears(SW_CLIMATE_YEARLY *climateOutput, int numYears, + SW_CLIMATE_CLIM *climateAverages); +void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, + Bool inNorthHem, SW_CLIMATE_YEARLY *climateOutput); +void calcSiteClimateLatInvariants(SW_WEATHER_HIST **allHist, int numYears, int startYear, + SW_CLIMATE_YEARLY *climateOutput); +void findDriestQtr(int numYears, Bool inNorthHem, double *meanTempDriestQtr_C, + double **meanTempMon_C, double **PPTMon_cm); +void driestQtrSouthAdjMonYears(int month, int *adjustedYearZero, int *adjustedYearOne, + int *adjustedYearTwo, int *adjustedMonth, int *prevMonth, + int *nextMonth); +void allocateClimateStructs(int numYears, SW_CLIMATE_YEARLY *climateOutput, + SW_CLIMATE_CLIM *climateAverages); +void deallocateClimateStructs(SW_CLIMATE_YEARLY *climateOutput, + SW_CLIMATE_CLIM *climateAverages); +void _read_weather_hist( + TimeInt year, + SW_WEATHER_HIST *yearWeather, + char weather_prefix[], + unsigned int n_input_forcings, + unsigned int *dailyInputIndices, + Bool *dailyInputFlags +); +void readAllWeather( + SW_WEATHER_HIST **allHist, + int startYear, + unsigned int n_years, + Bool use_weathergenerator_only, + char weather_prefix[], + Bool use_cloudCoverMonthly, + Bool use_humidityMonthly, + Bool use_windSpeedMonthly, + unsigned int n_input_forcings, + unsigned int *dailyInputIndices, + Bool *dailyInputFlags, + RealD *cloudcov, + RealD *windspeed, + RealD *r_humidity +); +void finalizeAllWeather(SW_WEATHER *w); + +void scaleAllWeather( + SW_WEATHER_HIST **allHist, + int startYear, + unsigned int n_years, + double *scale_temp_max, + double *scale_temp_min, + double *scale_precip, + double *scale_skyCover, + double *scale_wind, + double *scale_rH, + double *scale_actVapPress, + double *scale_shortWaveRad +); +void generateMissingWeather( + SW_WEATHER_HIST **allHist, + int startYear, + unsigned int n_years, + unsigned int method, + unsigned int optLOCF_nMax +); +void checkAllWeather(SW_WEATHER *weather); +void allocateAllWeather(SW_WEATHER *w); +void deallocateAllWeather(SW_WEATHER *w); +void _clear_hist_weather(SW_WEATHER_HIST *yearWeather); +void SW_WTH_finalize_all_weather(void); +void SW_WTH_init_run(void); +void SW_WTH_construct(void); +void SW_WTH_deconstruct(void); +void SW_WTH_new_day(void); +void SW_WTH_sum_today(void); + + +#ifdef DEBUG_MEM +void SW_WTH_SetMemoryRefs(void); +#endif + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Times.h b/include/Times.h similarity index 92% rename from Times.h rename to include/Times.h index bceef1e06..ff3e1ee03 100644 --- a/Times.h +++ b/include/Times.h @@ -41,7 +41,7 @@ #define TIMES_H #include -#include "generic.h" +#include "include/generic.h" #ifdef __cplusplus extern "C" { @@ -100,7 +100,8 @@ TimeInt yearto4digit(TimeInt yr); Bool isleapyear(const TimeInt year); -void interpolate_monthlyValues(double monthlyValues[], double dailyValues[]); +void interpolate_monthlyValues(double monthlyValues[], Bool interpAsBase1, + double dailyValues[]); #ifdef __cplusplus diff --git a/filefuncs.h b/include/filefuncs.h similarity index 95% rename from filefuncs.h rename to include/filefuncs.h index ab28edbbd..f168ad5e3 100644 --- a/filefuncs.h +++ b/include/filefuncs.h @@ -9,8 +9,8 @@ #ifndef FILEFUNCS_H #define FILEFUNCS_H -#include "generic.h" -#include "SW_Defines.h" +#include "include/generic.h" +#include "include/SW_Defines.h" #ifdef __cplusplus extern "C" { diff --git a/generic.h b/include/generic.h similarity index 98% rename from generic.h rename to include/generic.h index 6bc33f0b2..160d77861 100644 --- a/generic.h +++ b/include/generic.h @@ -242,12 +242,14 @@ void lobf(double *m, double* b, double xs[], double ys[], unsigned int size); double get_running_mean(unsigned int n, double mean_prev, double val_to_add); double get_running_sqr(double mean_prev, double mean_current, double val_to_add); double final_running_sd(unsigned int n, double ssqr); +double mean(double values[], int length); +double standardDeviation(double inputArray[], int length); #ifdef DEBUG extern errstr[]; #define LogError(fp, m, fmt, p1, p2, p3, p4, p5, p6, p7, p8, p9) \ - sprintf(errstr, fmt, p1, p2, p3, p4, p5, p6, p7, p8, p9); \ + snprintf(errstr, MAX_ERROR, fmt, p1, p2, p3, p4, p5, p6, p7, p8, p9); \ LogError(fp, m, errstr); #endif diff --git a/memblock.h b/include/memblock.h similarity index 100% rename from memblock.h rename to include/memblock.h diff --git a/myMemory.h b/include/myMemory.h similarity index 89% rename from myMemory.h rename to include/myMemory.h index 54e224a83..a150e4be0 100644 --- a/myMemory.h +++ b/include/myMemory.h @@ -6,11 +6,11 @@ #define MYMEMORY_H #include -#include "generic.h" +#include "include/generic.h" #ifdef DEBUG_MEM -#include "memblock.h" +#include "include/memblock.h" #endif #ifdef __cplusplus diff --git a/rands.h b/include/rands.h similarity index 93% rename from rands.h rename to include/rands.h index 68b708f2e..33a58e6af 100644 --- a/rands.h +++ b/include/rands.h @@ -12,7 +12,7 @@ #include #include -#include "pcg/pcg_basic.h" // see https://github.com/imneme/pcg-c-basic +#include "external/pcg/pcg_basic.h" // see https://github.com/imneme/pcg-c-basic #ifdef __cplusplus extern "C" { diff --git a/makefile b/makefile index efdb71a49..7d7e3b283 100644 --- a/makefile +++ b/makefile @@ -3,70 +3,115 @@ #------------------------------------------------------------------------------- # make help display the top of this file, i.e., print explanations # -# make all (synonym to 'bin'): compile the binary executable using -# optimizations -# make bint same as 'make bin' plus move a copy of the binary to the -# 'testing/' folder -# make bint_run same as 'make bint' plus execute the binary in testing/ +# make compiler_version print version information of CC and CXX compilers +# +# --- Binary executable ------ +# make create the binary executable 'SOILWAT2' +# make all +# make bin +# +# make bin_run same as 'make bin' plus execute the binary for tests/example/ +# (previously, `make bint_run`; target `bint` is obsolete) # +# --- Documentation ------ # make doc create html documentation for SOILWAT2 using doxygen # # make doc_open open documentation # +# --- SOILWAT2 library ------ # make lib create SOILWAT2 library # -# make test compile unit tests in 'test/ folder (with googletest) -# make test_severe compile unit tests with severe flags in 'test/' -# make test_run run unit tests (in a previous step compiled with 'make test' -# or 'make test_severe') +# --- Tests ------ +# make test create a test binary executable that includes the unit tests +# in 'tests/gtests/' (using googletest) +# make test_run execute the test binary +# make test_severe similar to `make test_run` with stricter flags for warnings +# and instrumentation; consider cleaning previous build +# artifacts beforehand, e.g., `make clean_test` +# make test_leaks similar to `make test_severe` with stricter +# sanitizer settings; consider cleaning previous build +# artifacts beforehand, e.g., `make clean_test` +# make test_reprnd similar to `make test_run`, i.e., execute the test binary +# repeatedly while randomly shuffling tests +# make test_rep3rnd similar to `make test_run`, i.e., execute the test binary +# three times while randomly shuffling tests # -# make bin_debug compile the binary executable in debug mode -# make bin_debug_severe same as 'make bin_debug' but with severe flags -# make bind_valgrind same as 'make bin_debug' plus run valgrind -# on the debug binary in the testing/ folder +# make bin_debug similar to `make bin_run` with debug settings; +# consider cleaning previous build artifacts beforehand, +# e.g., `make clean_build` +# make bin_debug_severe similar to `make bin_debug` stricter flags for +# warnings and instrumentation; consider cleaning previous +# build artifacts beforehand, e.g., `make clean_build` # -# make cov same as 'make test' but with code coverage support -# make cov_run run unit tests and gcov on each source file (in a previous -# step compiled with 'make cov') +# --- Code coverage ------ +# make cov same as 'make test_run' with code coverage support and +# running `gcov` on each source file +# (previously, `make cov cov_run`); consider cleaning +# previous artifacts beforehand, e.g., `make clean_cov`; +# use matching compiler and `gcov` versions, e.g., for gcc +# `CC=gcc CXX=g++ make clean cov` or for clang v14 +# `CC=clang CXX=clang++ GCOV="llvm-cov-mp-14 gcov" make clean_cov cov` # -# make clean (synonym to 'cleaner'): delete all of the o files, test -# files, libraries, and the binary exe(s) -# make test_clean delete test files and libraries -# make cov_clean delete files associated with code coverage -# make doc_clean delete documentation -# -# make compiler_version print version information of CC and CXX compilers +# --- Cleanup ------ +# make clean same as 'make clean_bin clean_build clean_test'; +# does not clean artifacts from code coverage or documentation +# make clean_bin delete 'bin/' (binary and test executables and library) +# make clean_build delete binary executable build artifacts +# make clean_test delete test executable build artifacts +# make clean_cov delete code coverage artifacts +# make clean_doc delete documentation #------------------------------------------------------------------------------- +#------ Requirement: GNU make +SHELL = /bin/sh +.SUFFIXES: +.SUFFIXES: .c .cc .o + + #------ Identification SW2_VERSION := "$(shell git describe --abbrev=7 --dirty --always --tags)" HOSTNAME := "$(shell uname -sn)" -sw_info = -DSW2_VERSION=\"$(SW2_VERSION)\" \ +sw_info := -DSW2_VERSION=\"$(SW2_VERSION)\" \ -DUSERNAME=\"$(USER)\" \ -DHOSTNAME=\"$(HOSTNAME)\" +#------ PATHS +# directory for source code (including headers) of SOILWAT2 +dir_src := src +# directory for unit test source code +dir_test := tests/gtests +# directories that contain submodules +dir_pcg := external/pcg +dir_gtest := external/googletest/googletest + +# output directory for executables, shared objects, libraries +dir_bin := bin +# directories for intermediate (ephemeral) compile objects +dir_build := build +dir_build_sw2 := $(dir_build)/sw2 +dir_build_test := $(dir_build)/test + + + #------ OUTPUT NAMES -target = SOILWAT2 -bin_test = sw_test -target_debug = $(target)_debug -target_debugsevere = $(target)_debugsevere +target := SOILWAT2 +lib_sw2 := $(dir_bin)/lib$(target).a +bin_sw2 := $(dir_bin)/$(target) + target_test = $(target)_test -target_testsevere = $(target)_testsevere -target_cov = $(target)_cov +lib_test := $(dir_build_test)/lib$(target_test).a +bin_test := $(dir_bin)/sw_test + +gtest := gtest +lib_gtest := $(dir_build_test)/lib$(gtest).a -lib_target_CC = lib$(target).a # used by `make all` (= `make bin`) -lib_target_CC_debug = lib$(target_debug).a # used by `make bin_debug` and `make bind_valgrind` -lib_target_CC_debugsevere = lib$(target_debugsevere).a # used by `make bin_debug_severe` -lib_target_CC_test = lib$(target_test).a # used by `make test` -lib_target_CXX_testsevere = lib$(target_testsevere).a # used by `make test_severe` -lib_target_CXX_cov = lib$(target_cov).a # used by `make cov` #------ COMMANDS -# CC = gcc -# CXX = g++ +# CC = gcc or clang +# CXX = g++ or clang++ # AR = ar # RM = rm @@ -79,17 +124,16 @@ lib_target_CXX_cov = lib$(target_cov).a # used by `make cov` # see https://github.com/google/googletest/issues/813 and # see https://github.com/google/googletest/pull/2839#issue-613300962 -set_std = -std=c11 -set_std_tests = -std=c11 -set_std++_tests = -std=c++11 +set_std := -std=c11 +set_std++_tests := -std=c++11 #------ FLAGS # Diagnostic warning/error messages -warning_flags = -Wall -Wextra +warning_flags := -Wall -Wextra # Don't use 'warning_flags_severe*' for production builds and rSOILWAT2 -warning_flags_severe = \ +warning_flags_severe := \ $(warning_flags) \ -Wpedantic \ -Werror \ @@ -97,11 +141,11 @@ warning_flags_severe = \ -Wmissing-declarations \ -Wredundant-decls -warning_flags_severe_cc = \ +warning_flags_severe_cc := \ $(warning_flags_severe) \ -Wstrict-prototypes # '-Wstrict-prototypes' is valid for C/ObjC but not for C++ -warning_flags_severe_cxx = \ +warning_flags_severe_cxx := \ $(warning_flags_severe) \ -Wno-error=deprecated # TODO: address underlying problems so that we can eliminate @@ -111,9 +155,9 @@ warning_flags_severe_cxx = \ # Instrumentation options for debugging and testing -instr_flags = -fstack-protector-all +instr_flags := -fstack-protector-all -instr_flags_severe = \ +instr_flags_severe := \ $(instr_flags) \ -fsanitize=undefined \ -fsanitize=address \ @@ -128,40 +172,71 @@ instr_flags_severe = \ # Precompiler and compiler flags and options -sw_CPPFLAGS = $(CPPFLAGS) $(sw_info) -sw_CFLAGS = $(CFLAGS) -sw_CXXFLAGS = $(CXXFLAGS) +sw_CPPFLAGS := $(CPPFLAGS) $(sw_info) -MMD -MP -I. +sw_CPPFLAGS_bin := $(sw_CPPFLAGS) -I$(dir_build_sw2) +sw_CPPFLAGS_test := $(sw_CPPFLAGS) -I$(dir_build_test) +sw_CFLAGS := $(CFLAGS) +sw_CXXFLAGS := $(CXXFLAGS) -bin_flags = -O2 -fno-stack-protector -debug_flags = -g -O0 -DSWDEBUG -cov_flags = -O0 -coverage -gtest_flags = -D_POSIX_C_SOURCE=200809L # googletest requires POSIX API +# `SW2_FLAGS` can be used to pass in additional flags +bin_flags := -O2 -fno-stack-protector $(SW2_FLAGS) +debug_flags := -g -O0 -DSWDEBUG $(SW2_FLAGS) +#cov_flags := -O0 -coverage +gtest_flags := -D_POSIX_C_SOURCE=200809L # googletest requires POSIX API # Linker flags and libraries # order of libraries is important for GNU gcc (libSOILWAT2 depends on libm) -sw_LDFLAGS = $(LDFLAGS) -L. -sw_LDLIBS = $(LDLIBS) -lm - -target_LDLIBS = -l$(target) $(sw_LDLIBS) -debug_LDLIBS = -l$(target_debug) $(sw_LDLIBS) -debugsevere_LDLIBS = -l$(target_debugsevere) $(sw_LDLIBS) -test_LDLIBS = -l$(target_test) $(sw_LDLIBS) -testsevere_LDLIBS = -l$(target_testsevere) $(sw_LDLIBS) -cov_LDLIBS = -l$(target_cov) $(sw_LDLIBS) +sw_LDFLAGS_bin := $(LDFLAGS) -L$(dir_bin) +sw_LDFLAGS_test := $(LDFLAGS) -L$(dir_bin) -L$(dir_build_test) +sw_LDLIBS := $(LDLIBS) -lm -gtest_LDLIBS = -l$(gtest) +target_LDLIBS := -l$(target) $(sw_LDLIBS) +test_LDLIBS := -l$(target_test) $(sw_LDLIBS) +gtest_LDLIBS := -l$(gtest) #------ CODE FILES -# SOILWAT2 files -sources_core = SW_Main_lib.c SW_VegEstab.c SW_Control.c generic.c \ - rands.c Times.c mymemory.c filefuncs.c SW_Files.c SW_Model.c \ - SW_Site.c SW_SoilWater.c SW_Markov.c SW_Weather.c SW_Sky.c \ - SW_VegProd.c SW_Flow_lib_PET.c SW_Flow_lib.c SW_Flow.c SW_Carbon.c +# sw_sources is used by STEPWAT2 and rSOILWAT2 to pass relevant output code file +ifneq ($(origin sw_sources), undefined) + # Add source path + sw_sources := $(sw_sources:%.c=$(dir_src)/%.c) +endif -sources_lib = $(sw_sources) $(sources_core) SW_Output.c SW_Output_get_functions.c -objects_lib = $(sources_lib:.c=.o) +# SOILWAT2 files +sources_core := \ + $(dir_src)/SW_Main_lib.c \ + $(dir_src)/SW_VegEstab.c \ + $(dir_src)/SW_Control.c \ + $(dir_src)/generic.c \ + $(dir_src)/rands.c \ + $(dir_src)/Times.c \ + $(dir_src)/mymemory.c \ + $(dir_src)/filefuncs.c \ + $(dir_src)/SW_Files.c \ + $(dir_src)/SW_Model.c \ + $(dir_src)/SW_Site.c \ + $(dir_src)/SW_SoilWater.c \ + $(dir_src)/SW_Markov.c \ + $(dir_src)/SW_Weather.c \ + $(dir_src)/SW_Sky.c \ + $(dir_src)/SW_VegProd.c \ + $(dir_src)/SW_Flow_lib_PET.c \ + $(dir_src)/SW_Flow_lib.c \ + $(dir_src)/SW_Flow.c \ + $(dir_src)/SW_Carbon.c + +sources_lib = \ + $(sw_sources) \ + $(sources_core) \ + $(dir_src)/SW_Output.c \ + $(dir_src)/SW_Output_get_functions.c +objects_lib = $(sources_lib:$(dir_src)/%.c=$(dir_build_sw2)/%.o) + + +# SOILWAT2-standalone +sources_bin := $(dir_src)/SW_Main.c $(dir_src)/SW_Output_outtext.c +objects_bin := $(sources_bin:$(dir_src)/%.c=$(dir_build_sw2)/%.o) # Unfortunately, we currently cannot include 'SW_Output.c' because @@ -169,206 +244,176 @@ objects_lib = $(sources_lib:.c=.o) # - assigning to 'OutKey' from incompatible type 'int' # ==> instead, we use 'SW_Output_mock.c' which provides mock versions of the # public functions (but will result in some compiler warnings) -sources_lib_test = $(sources_core) SW_Output_mock.c -objects_lib_test = $(sources_lib_test:.c=.o) +sources_lib_test := $(sources_core) $(dir_src)/SW_Output_mock.c +objects_lib_test := $(sources_lib_test:$(dir_src)/%.c=$(dir_build_test)/%.o) -sources_bin = SW_Main.c SW_Output_outtext.c # SOILWAT2-standalone -objects_bin = $(sources_bin:.c=.o) +# Unit test files +sources_test := $(wildcard $(dir_test)/*.cc) +objects_test := $(sources_test:$(dir_test)/%.cc=$(dir_build_test)/%.o) # PCG random generator files -PCG_DIR = pcg -sources_pcg = $(PCG_DIR)/pcg_basic.c -objects_pcg = pcg_basic.o +sources_pcg := $(dir_pcg)/pcg_basic.c +objects_lib_pcg := $(sources_pcg:$(dir_pcg)/%.c=$(dir_build_sw2)/%.o) +objects_test_pcg := $(sources_pcg:$(dir_pcg)/%.c=$(dir_build_test)/%.o) -# Google unit test files -gtest = gtest -lib_gtest = lib$(gtest).a -GTEST_DIR = googletest/googletest -GTEST_SRCS_ = $(GTEST_DIR)/src/*.cc $(GTEST_DIR)/src/*.h $(GTEST_HEADERS) -GTEST_HEADERS = $(GTEST_DIR)/include/gtest/*.h $(GTEST_DIR)/include/gtest/internal/*.h +# Google test code +GTEST_SRCS_ := $(dir_gtest)/src/*.cc $(dir_gtest)/src/*.h $(GTEST_HEADERS) +GTEST_HEADERS := $(dir_gtest)/include/gtest/*.h $(dir_gtest)/include/gtest/internal/*.h + #------ TARGETS -all : $(target) -bin : $(target) +all : $(bin_sw2) -#--- Targets for SOILWAT2 library -lib : $(lib_target_CC) +lib : $(lib_sw2) -$(lib_target_CC) : - $(CC) $(sw_CPPFLAGS) $(sw_CFLAGS) $(bin_flags) $(warning_flags) \ - $(set_std) -c $(sources_lib) $(sources_pcg) +test : $(bin_test) - -@$(RM) -f $(lib_target_CC) - $(AR) -rcs $(lib_target_CC) $(objects_lib) $(objects_pcg) - -@$(RM) -f $(objects_lib) $(objects_pcg) +.PHONY : all lib test -$(lib_target_CC_debug) : - $(CC) $(sw_CPPFLAGS) $(sw_CFLAGS) $(debug_flags) $(warning_flags) \ - $(instr_flags) $(set_std) -c $(sources_lib) $(sources_pcg) - -@$(RM) -f $(lib_target_CC_debug) - $(AR) -rcs $(lib_target_CC_debug) $(objects_lib) $(objects_pcg) - -@$(RM) -f $(objects_lib) $(objects_pcg) -$(lib_target_CC_debugsevere) : - $(CC) $(sw_CPPFLAGS) $(sw_CFLAGS) $(debug_flags) $(warning_flags_severe_cc) \ - $(instr_flags_severe) $(set_std) -c $(sources_lib) $(sources_pcg) +#--- SOILWAT2 library (utilized by SOILWAT2, rSOILWAT2, and STEPWAT2) +$(lib_sw2) : $(objects_lib) $(objects_lib_pcg) | $(dir_bin) + $(AR) -rcs $(lib_sw2) $(objects_lib) $(objects_lib_pcg) - -@$(RM) -f $(lib_target_CC_debugsevere) - $(AR) -rcs $(lib_target_CC_debugsevere) $(objects_lib) $(objects_pcg) - -@$(RM) -f $(objects_lib) $(objects_pcg) -$(lib_target_CC_test) : - $(CC) $(sw_CPPFLAGS) $(sw_CFLAGS) $(debug_flags) $(warning_flags) \ - $(instr_flags) $(set_std_tests) -c $(sources_lib_test) $(sources_pcg) +#--- SOILWAT2 stand-alone executable (utilizing SOILWAT2 library) +$(bin_sw2) : $(lib_sw2) $(objects_bin) | $(dir_bin) + $(CC) $(bin_flags) $(warning_flags) $(set_std) $(objects_bin) $(sw_LDFLAGS_bin) $(target_LDLIBS) -o $(bin_sw2) - -@$(RM) -f $(lib_target_CC_test) - $(AR) -rcs $(lib_target_CC_test) $(objects_lib_test) $(objects_pcg) - -@$(RM) -f $(objects_lib_test) $(objects_pcg) -$(lib_target_CXX_testsevere) : # needs CXX because of '*_severe' flags which must match test executable - $(CXX) $(sw_CPPFLAGS) $(sw_CXXFLAGS) $(debug_flags) $(warning_flags_severe_cxx) \ - $(instr_flags_severe) $(set_std++_tests) -c $(sources_lib_test) $(sources_pcg) +#--- Unit test library and executable +$(lib_test) : $(objects_lib_test) $(objects_test_pcg) | $(dir_build_test) + $(AR) -rcs $(lib_test) $(objects_lib_test) $(objects_test_pcg) - -@$(RM) -f $(lib_target_CXX_testsevere) - $(AR) -rcs $(lib_target_CXX_testsevere) $(objects_lib_test) $(objects_pcg) - -@$(RM) -f $(objects_lib_test) $(objects_pcg) +$(bin_test) : $(lib_gtest) $(lib_test) $(objects_test) | $(dir_bin) + $(CXX) $(gtest_flags) $(debug_flags) $(warning_flags) \ + $(instr_flags) $(set_std++_tests) \ + -isystem ${dir_gtest}/include -pthread \ + $(objects_test) $(sw_LDFLAGS_test) $(gtest_LDLIBS) $(test_LDLIBS) -o $(bin_test) -$(lib_target_CXX_cov) : # needs CXX because of 'coverage' flags which must match test executable - $(CXX) $(sw_CPPFLAGS) $(sw_CXXFLAGS) $(debug_flags) $(warning_flags) \ - $(instr_flags) $(cov_flags) $(set_std++_tests) -c $(sources_lib_test) $(sources_pcg) +# GoogleTest library +# based on section 'Generic Build Instructions' at +# https://github.com/google/googletest/tree/master/googletest) +# 1) build googletest library +# 2) compile SOILWAT2 test source file +$(lib_gtest) : | $(dir_build_test) + $(CXX) $(sw_CPPFLAGS_test) $(sw_CXXFLAGS) $(gtest_flags) $(set_std++_tests) \ + -isystem ${dir_gtest}/include -I${dir_gtest} \ + -pthread -c ${dir_gtest}/src/gtest-all.cc -o $(dir_build_test)/gtest-all.o - -@$(RM) -f $(lib_target_CXX_cov) - $(AR) -rcs $(lib_target_CXX_cov) $(objects_lib_test) $(objects_pcg) - -@$(RM) -f $(objects_lib_test) $(objects_pcg) + $(AR) -r $(lib_gtest) $(dir_build_test)/gtest-all.o -#--- Targets for SOILWAT2 executable -$(target) : $(lib_target_CC) - $(CC) $(sw_CPPFLAGS) $(sw_CFLAGS) $(bin_flags) $(warning_flags) \ - $(set_std) \ - -o $(target) $(sources_bin) $(target_LDLIBS) $(sw_LDFLAGS) +#--- Compile source files for library and executable +$(dir_build_sw2)/%.o: $(dir_src)/%.c | $(dir_build_sw2) + $(CC) $(sw_CPPFLAGS_bin) $(sw_CFLAGS) $(bin_flags) $(warning_flags) $(set_std) -c $< -o $@ -bin_debug : $(lib_target_CC_debug) - $(CC) $(sw_CPPFLAGS) $(sw_CFLAGS) $(debug_flags) $(warning_flags) \ - $(instr_flags) $(set_std) \ - -o $(target) $(sources_bin) $(debug_LDLIBS) $(sw_LDFLAGS) +$(dir_build_sw2)/%.o: $(dir_pcg)/%.c | $(dir_build_sw2) + $(CC) $(sw_CPPFLAGS_bin) $(sw_CFLAGS) $(bin_flags) $(warning_flags) $(set_std) -c $< -o $@ -bin_debug_severe : $(lib_target_CC_debugsevere) - $(CC) $(sw_CPPFLAGS) $(sw_CFLAGS) $(debug_flags) $(warning_flags_severe_cc) \ - $(instr_flags_severe) $(set_std) \ - -o $(target) $(sources_bin) $(debugsevere_LDLIBS) $(sw_LDFLAGS) +#--- Compile source files for unit tests +$(dir_build_test)/%.o: $(dir_src)/%.c | $(dir_build_test) + $(CXX) $(sw_CPPFLAGS_test) $(sw_CXXFLAGS) $(gtest_flags) $(debug_flags) $(warning_flags) $(instr_flags) $(set_std++_tests) -c $< -o $@ -.PHONY : bint -bint : - cp $(target) testing/$(target) +$(dir_build_test)/%.o: $(dir_pcg)/%.c | $(dir_build_test) + $(CXX) $(sw_CPPFLAGS_test) $(sw_CXXFLAGS) $(gtest_flags) $(debug_flags) $(warning_flags) $(instr_flags) $(set_std++_tests) -c $< -o $@ -.PHONY : bint_run -bint_run : bint - ./testing/$(target) -d ./testing -f files.in +$(dir_build_test)/%.o: $(dir_test)/%.cc | $(dir_build_test) + $(CXX) $(sw_CPPFLAGS_test) $(sw_CXXFLAGS) $(gtest_flags) $(debug_flags) $(warning_flags) $(instr_flags) $(set_std++_tests) -isystem ${dir_gtest}/include -pthread -c $< -o $@ -.PHONY : bind_valgrind -bind_valgrind : bin_debug bint - valgrind -v --track-origins=yes --leak-check=full ./testing/$(target) -d ./testing -f files.in +#--- Create directories +$(dir_bin) $(dir_build_sw2) $(dir_build_test): + -@mkdir -p $@ -#--- Target for GoogleTest library -# based on section 'Generic Build Instructions' at -# https://github.com/google/googletest/tree/master/googletest) -# 1) build googletest library -# 2) compile SOILWAT2 test source file -lib_test : $(lib_gtest) -$(lib_gtest) : - $(CXX) $(sw_CPPFLAGS) $(sw_CXXFLAGS) $(gtest_flags) $(set_std++_tests) \ - -isystem ${GTEST_DIR}/include -I${GTEST_DIR} \ - -pthread -c ${GTEST_DIR}/src/gtest-all.cc +#--- Include "makefiles" created by the CPPFLAG options `-MMD -MP` +#-include $(objects_bin:.o=.d) $(objects_lib:.o=.d) $(objects_lib_pcg:.o=.d) - $(AR) -r $(lib_gtest) gtest-all.o -#--- Targets for SOILWAT2 test executable -test : $(lib_gtest) $(lib_target_CC_test) - $(CXX) $(sw_CPPFLAGS) $(sw_CXXFLAGS) $(gtest_flags) $(debug_flags) $(warning_flags) \ - $(instr_flags) $(set_std++_tests) \ - -isystem ${GTEST_DIR}/include -pthread \ - test/*.cc -o $(bin_test) $(gtest_LDLIBS) $(test_LDLIBS) $(sw_LDFLAGS) +#--- Convenience targets for testing +.PHONY : bin_run +bin_run : all + $(bin_sw2) -d ./tests/example -f files.in -test_severe : $(lib_gtest) $(lib_target_CXX_testsevere) - $(CXX) $(sw_CPPFLAGS) $(sw_CXXFLAGS) $(gtest_flags) $(debug_flags) $(warning_flags_severe_cxx) \ - $(instr_flags_severe) $(set_std++_tests) \ - -isystem ${GTEST_DIR}/include -pthread \ - test/*.cc -o $(bin_test) $(gtest_LDLIBS) $(testsevere_LDLIBS) $(sw_LDFLAGS) +.PHONY : test_run +test_run : test + $(bin_test) +.PHONY : test_severe +test_severe : + ./tools/run_test_severe.sh -.PHONY : test_run -test_run : - ./$(bin_test) +.PHONY : test_leaks +test_leaks : + ./tools/run_test_leaks.sh + +.PHONY : test_reprnd +test_reprnd : test + $(bin_test) --gtest_shuffle --gtest_repeat=-1 + +.PHONY : test_rep3rnd +test_rep3rnd : test + $(bin_test) --gtest_shuffle --gtest_repeat=3 +.PHONY : bin_debug +bin_debug : + ./tools/run_debug.sh -#--- Targets for SOILWAT2 code coverage test executable -cov : cov_clean $(lib_gtest) $(lib_target_CXX_cov) - $(CXX) $(sw_CPPFLAGS) $(sw_CXXFLAGS) $(gtest_flags) $(debug_flags) $(warning_flags) \ - $(instr_flags) $(cov_flags) $(set_std++_tests) \ - -isystem ${GTEST_DIR}/include -pthread \ - test/*.cc -o $(bin_test) $(gtest_LDLIBS) $(cov_LDLIBS) $(sw_LDFLAGS) +.PHONY : bin_debug_severe +bin_debug_severe : + ./tools/run_debug_severe.sh -.PHONY : cov_run -cov_run : cov - ./$(bin_test) + +#--- Convenience targets for code coverage +.PHONY : cov +cov : ./tools/run_gcov.sh -#--- Targets for documentation +#--- Convenience targets for documentation .PHONY : doc doc : ./tools/run_doxygen.sh - .PHONY : doc_open doc_open : ./tools/doc_open.sh -#--- Targets to clean artifacts -.PHONY : clean1 -clean1 : - -@$(RM) -f $(objects_lib) $(objects_bin) $(objects_pcg) - -.PHONY : clean2 -clean2 : - -@$(RM) -f $(target) $(lib_target_CC) $(lib_target_CC_debug) $(lib_target_CC_debugsevere) - -@$(RM) -f testing/$(target) - -.PHONY : bint_clean -bint_clean : - -@$(RM) -f testing/Output/* - -.PHONY : test_clean -test_clean : - -@$(RM) -f gtest-all.o $(lib_gtest) $(bin_test) - -@$(RM) -f $(lib_target_CC_test) $(lib_target_CXX_testsevere) $(lib_target_CXX_cov) - -@$(RM) -fr *.dSYM - -@$(RM) -f $(objects_lib_test) - -.PHONY : cov_clean -cov_clean : - -@$(RM) -f $(lib_target_CXX_cov) *.gcda *.gcno *.gcov - -@$(RM) -fr *.dSYM - -.PHONY : cleaner -cleaner : clean1 clean2 bint_clean test_clean cov_clean - +#--- Clean up .PHONY : clean -clean : cleaner - -.PHONY : doc_clean -doc_clean : +clean: clean_bin clean_build clean_test + -@$(RM) -r $(dir_build) + +.PHONY : clean_bin +clean_bin: + -@$(RM) -r $(dir_bin) + +.PHONY : clean_build +clean_build: + -@$(RM) -r $(dir_build_sw2) + -@$(RM) -f $(bin_sw2) $(lib_sw2) + +.PHONY : clean_test +clean_test: + -@$(RM) -r $(dir_build_test) + -@$(RM) -f $(bin_test) + +.PHONY : clean_cov +clean_cov : clean_test + -@$(RM) -f *.gcov + -@$(RM) -fr $(dir_bin)/*.dSYM + +.PHONY : clean_doc +clean_doc : -@$(RM) -fr doc/html/ -@$(RM) -f doc/log_doxygen.log @@ -378,6 +423,7 @@ doc_clean : help : less makefile + #--- Target to print compiler information .PHONY : compiler_version compiler_version : @@ -385,3 +431,5 @@ compiler_version : @(echo `"$(CC)" --version`) @(echo CXX version:) @(echo `"$(CXX)" --version`) + @(echo make version:) + @(echo `"$(MAKE)" --version`) diff --git a/SW_Carbon.c b/src/SW_Carbon.c similarity index 89% rename from SW_Carbon.c rename to src/SW_Carbon.c index 1f62080fa..d2fdbdaa3 100644 --- a/SW_Carbon.c +++ b/src/SW_Carbon.c @@ -15,16 +15,16 @@ #include #include #include -#include "generic.h" -#include "filefuncs.h" -#include "myMemory.h" -#include "SW_Defines.h" -#include "SW_Times.h" -#include "SW_Files.h" -#include "SW_Carbon.h" // externs SW_Carbon -#include "SW_Site.h" -#include "SW_VegProd.h" // externs SW_VegProd -#include "SW_Model.h" // externs SW_Model +#include "include/generic.h" +#include "include/filefuncs.h" +#include "include/myMemory.h" +#include "include/SW_Defines.h" +#include "include/SW_Times.h" +#include "include/SW_Files.h" +#include "include/SW_Carbon.h" // externs SW_Carbon +#include "include/SW_Site.h" +#include "include/SW_VegProd.h" // externs SW_VegProd +#include "include/SW_Model.h" // externs SW_Model @@ -142,8 +142,9 @@ void SW_CBN_read(void) if (year < 0) { CloseFile(&f); - sprintf( + snprintf( errstr, + MAX_ERROR, "(SW_Carbon) Year %d in scenario '%.64s' is negative; " "only positive values are allowed.\n", year, @@ -166,8 +167,9 @@ void SW_CBN_read(void) if (existing_years[year] != 0) { CloseFile(&f); - sprintf( + snprintf( errstr, + MAX_ERROR, "(SW_Carbon) Year %d in scenario '%.64s' is entered more than once; " "only one entry is allowed.\n", year, @@ -187,8 +189,9 @@ void SW_CBN_read(void) // otherwise the empty file will be masked as not being able to find the scenario if (fileWasEmpty == 1) { - sprintf( + snprintf( errstr, + MAX_ERROR, "(SW_Carbon) carbon.in was empty; " "for debugging purposes, SOILWAT2 read in file '%s'\n", MyFileName @@ -198,8 +201,9 @@ void SW_CBN_read(void) if (EQ(ppm, -1.)) // A scenario must be found in order for ppm to have a positive value { - sprintf( + snprintf( errstr, + MAX_ERROR, "(SW_Carbon) The scenario '%.64s' was not found in carbon.in\n", c->scenario ); @@ -211,8 +215,9 @@ void SW_CBN_read(void) { if (existing_years[year] == 0) { - sprintf( + snprintf( errstr, + MAX_ERROR, "(SW_Carbon) missing CO2 data for year %d; " "ensure that ppm values for this year exist in scenario '%.64s'\n", year, @@ -257,8 +262,9 @@ void SW_CBN_init_run(void) { if (LT(ppm, 0.)) // CO2 concentration must not be negative values { - sprintf( + snprintf( errstr, + MAX_ERROR, "(SW_Carbon) No CO2 ppm data was provided for year %d\n", year ); diff --git a/SW_Control.c b/src/SW_Control.c similarity index 83% rename from SW_Control.c rename to src/SW_Control.c index 978a19e8e..3e6365b07 100644 --- a/SW_Control.c +++ b/src/SW_Control.c @@ -24,26 +24,26 @@ #include #include #include -#include "generic.h" -#include "Times.h" -#include "filefuncs.h" -#include "rands.h" -#include "SW_Defines.h" -#include "SW_Files.h" -#include "SW_Control.h" -#include "SW_Model.h" // externs SW_Model -#include "SW_Output.h" -#include "SW_Site.h" // externs SW_Site -#include "SW_Flow_lib.h" -#include "SW_Flow_lib_PET.h" -#include "SW_Flow.h" -#include "SW_SoilWater.h" -#include "SW_VegEstab.h" // externs SW_VegEstab -#include "SW_VegProd.h" // externs SW_VegProd -#include "SW_Weather.h" // externs SW_Weather -#include "SW_Markov.h" -#include "SW_Sky.h" -#include "SW_Carbon.h" +#include "include/generic.h" +#include "include/Times.h" +#include "include/filefuncs.h" +#include "include/rands.h" +#include "include/SW_Defines.h" +#include "include/SW_Files.h" +#include "include/SW_Control.h" +#include "include/SW_Model.h" // externs SW_Model +#include "include/SW_Output.h" +#include "include/SW_Site.h" // externs SW_Site +#include "include/SW_Flow_lib.h" +#include "include/SW_Flow_lib_PET.h" +#include "include/SW_Flow.h" +#include "include/SW_SoilWater.h" +#include "include/SW_VegEstab.h" // externs SW_VegEstab +#include "include/SW_VegProd.h" // externs SW_VegProd +#include "include/SW_Weather.h" // externs SW_Weather +#include "include/SW_Markov.h" +#include "include/SW_Sky.h" +#include "include/SW_Carbon.h" @@ -58,7 +58,6 @@ static void _begin_year(void) { // SW_F_new_year() not needed SW_MDL_new_year(); // call first to set up time-related arrays for this year - SW_WTH_new_year(); // SW_MKV_new_year() not needed SW_SKY_new_year(); // Update daily climate variables from monthly values //SW_SIT_new_year() not needed @@ -77,7 +76,6 @@ static void _begin_day(void) { static void _end_day(void) { _collect_values(); - SW_WTH_end_day(); SW_SWC_end_day(); } @@ -158,7 +156,7 @@ void SW_CTL_init_run(void) { SW_WTH_init_run(); // SW_MKV_init_run() not needed SW_PET_init_run(); - SW_SKY_init_run(); + // SW_SKY_init_run() not needed SW_SIT_init_run(); SW_VES_init_run(); // must run after `SW_SIT_init_run()` SW_VPD_init_run(); @@ -250,9 +248,9 @@ void SW_CTL_read_inputs_from_disk(void) { if (debug) swprintf(" > 'model'"); #endif - SW_WTH_read(); + SW_WTH_setup(); #ifdef SWDEBUG - if (debug) swprintf(" > 'weather'"); + if (debug) swprintf(" > 'weather setup'"); #endif SW_SKY_read(); @@ -260,21 +258,36 @@ void SW_CTL_read_inputs_from_disk(void) { if (debug) swprintf(" > 'climate'"); #endif - if (SW_Weather.use_weathergenerator) { + if (SW_Weather.generateWeatherMethod == 2) { SW_MKV_setup(); #ifdef SWDEBUG if (debug) swprintf(" > 'weather generator'"); #endif } + SW_WTH_read(); + #ifdef SWDEBUG + if (debug) swprintf(" > 'weather read'"); + #endif + SW_VPD_read(); #ifdef SWDEBUG if (debug) swprintf(" > 'veg'"); #endif - SW_SIT_read(); // inputs also soil layer data + SW_SIT_read(); + #ifdef SWDEBUG + if (debug) swprintf(" > 'site'"); + #endif + + SW_LYR_read(); + #ifdef RSWDEBUG + if (debug) swprintf(" > 'soils'"); + #endif + + SW_SWRC_read(); #ifdef SWDEBUG - if (debug) swprintf(" > 'site' + 'soils'"); + if (debug) swprintf(" > 'swrc parameters'"); #endif SW_VES_read(); @@ -302,7 +315,7 @@ void SW_CTL_read_inputs_from_disk(void) { #ifdef DEBUG_MEM -#include "SW_Markov.h" /* for setmemrefs function */ +#include "include/SW_Markov.h" /* for setmemrefs function */ /** @brief This routine sets the known memory refs so they can be diff --git a/SW_Files.c b/src/SW_Files.c similarity index 94% rename from SW_Files.c rename to src/SW_Files.c index 6b42a83ab..ef93b8094 100644 --- a/SW_Files.c +++ b/src/SW_Files.c @@ -24,11 +24,11 @@ #include #include #include -#include "generic.h" -#include "filefuncs.h" -#include "myMemory.h" -#include "SW_Defines.h" -#include "SW_Files.h" +#include "include/generic.h" +#include "include/filefuncs.h" +#include "include/myMemory.h" +#include "include/SW_Defines.h" +#include "include/SW_Files.h" /* =================================================== */ @@ -148,53 +148,53 @@ void SW_F_read(const char *s) { #endif switch (lineno) { - case 5: + case 6: strcpy(weather_prefix, inbuf); break; - case 13: + case 14: strcpy(output_prefix, inbuf); break; - case 15: + case 16: InFiles[eOutputDaily] = Str_Dup(inbuf); ++fileno; SW_CSV_F_INIT(InFiles[eOutputDaily]); break; - case 16: + case 17: InFiles[eOutputWeekly] = Str_Dup(inbuf); ++fileno; SW_CSV_F_INIT(InFiles[eOutputWeekly]); //printf("filename: %s \n",InFiles[eOutputWeekly]); break; - case 17: + case 18: InFiles[eOutputMonthly] = Str_Dup(inbuf); ++fileno; SW_CSV_F_INIT(InFiles[eOutputMonthly]); //printf("filename: %s \n",InFiles[eOutputMonthly]); break; - case 18: + case 19: InFiles[eOutputYearly] = Str_Dup(inbuf); ++fileno; SW_CSV_F_INIT(InFiles[eOutputYearly]); break; - case 19: + case 20: InFiles[eOutputDaily_soil] = Str_Dup(inbuf); ++fileno; SW_CSV_F_INIT(InFiles[eOutputDaily_soil]); //printf("filename: %s \n",InFiles[eOutputDaily]); break; - case 20: + case 21: InFiles[eOutputWeekly_soil] = Str_Dup(inbuf); ++fileno; SW_CSV_F_INIT(InFiles[eOutputWeekly_soil]); //printf("filename: %s \n",InFiles[eOutputWeekly]); break; - case 21: + case 22: InFiles[eOutputMonthly_soil] = Str_Dup(inbuf); ++fileno; SW_CSV_F_INIT(InFiles[eOutputMonthly_soil]); //printf("filename: %s \n",InFiles[eOutputMonthly]); break; - case 22: + case 23: InFiles[eOutputYearly_soil] = Str_Dup(inbuf); ++fileno; SW_CSV_F_INIT(InFiles[eOutputYearly_soil]); @@ -317,7 +317,7 @@ void SW_OutputPrefix(char prefix[]) { } #ifdef DEBUG_MEM -#include "myMemory.h" +#include "include/myMemory.h" /*======================================================*/ void SW_F_SetMemoryRefs( void) { /* when debugging memory problems, use the bookkeeping diff --git a/SW_Flow.c b/src/SW_Flow.c similarity index 94% rename from SW_Flow.c rename to src/SW_Flow.c index 9bcf5f6cb..5a398b0e6 100644 --- a/SW_Flow.c +++ b/src/SW_Flow.c @@ -109,20 +109,20 @@ #include #include -#include "generic.h" -#include "filefuncs.h" -#include "SW_Defines.h" -#include "SW_Model.h" // externs SW_Model -#include "SW_Site.h" // externs SW_Site -#include "SW_SoilWater.h" // externs SW_Soilwat -#include "SW_Flow_lib.h" // externs stValues, soil_temp_init -/*#include "SW_VegEstab.h" */ -#include "SW_VegProd.h" // externs SW_VegProd, key2veg -#include "SW_Weather.h" // externs SW_Weather -#include "SW_Sky.h" // externs SW_Sky - -#include "SW_Flow_lib_PET.h" -#include "SW_Flow.h" +#include "include/generic.h" +#include "include/filefuncs.h" +#include "include/SW_Defines.h" +#include "include/SW_Model.h" // externs SW_Model +#include "include/SW_Site.h" // externs SW_Site +#include "include/SW_SoilWater.h" // externs SW_Soilwat +#include "include/SW_Flow_lib.h" // externs stValues, soil_temp_init +/*#include "include/SW_VegEstab.h" */ +#include "include/SW_VegProd.h" // externs SW_VegProd, key2veg +#include "include/SW_Weather.h" // externs SW_Weather +#include "include/SW_Sky.h" // externs SW_Sky + +#include "include/SW_Flow_lib_PET.h" +#include "include/SW_Flow.h" /* =================================================== */ @@ -373,7 +373,7 @@ void SW_Water_Flow(void) { calculate soil temperature at end of each day */ SW_ST_setup_run( - w->now.temp_avg[Today], + w->now.temp_avg, lyrSWCBulk, lyrSWCBulk_Saturated, lyrbDensity, @@ -406,9 +406,10 @@ void SW_Water_Flow(void) { SW_Site.slope, SW_Site.aspect, x, - SW_Sky.cloudcov_daily[doy], - SW_Sky.r_humidity_daily[doy], - w->now.temp_avg[Today], + &w->now.cloudCover, + w->now.actualVaporPressure, + w->now.shortWaveRad, + w->desc_rsds, &sw->H_oh, &sw->H_ot, &sw->H_gh @@ -416,12 +417,12 @@ void SW_Water_Flow(void) { sw->pet = SW_Site.pet_scale * petfunc( sw->H_gt, - w->now.temp_avg[Today], + w->now.temp_avg, SW_Site.altitude, x, - SW_Sky.r_humidity_daily[doy], - SW_Sky.windspeed_daily[doy], - SW_Sky.cloudcov_daily[doy] + w->now.relHumidity, + w->now.windSpeed, + w->now.cloudCover ); @@ -444,7 +445,7 @@ void SW_Water_Flow(void) { } /* Rainfall interception */ - h2o_for_soil = w->now.rain[Today]; /* ppt is partioned into ppt = snow + rain */ + h2o_for_soil = w->now.rain; /* ppt is partioned into ppt = snow + rain */ ForEachVegType(k) { @@ -837,13 +838,13 @@ void SW_Water_Flow(void) { // soil_temperature function computes the soil temp for each layer and stores it in lyravgLyrTemp // doesn't affect SWC at all (yet), but needs it for the calculation, so therefore the temperature is the last calculation done if (SW_Site.use_soil_temp) { - soil_temperature(w->now.temp_avg[Today], sw->pet, sw->aet, x, lyrSWCBulk, + soil_temperature(w->now.temp_avg, sw->pet, sw->aet, x, lyrSWCBulk, lyrSWCBulk_Saturated, lyrbDensity, lyrWidths, lyroldavgLyrTemp, lyravgLyrTemp, surfaceAvg, SW_Site.n_layers, SW_Site.bmLimiter, SW_Site.t1Param1, SW_Site.t1Param2, SW_Site.t1Param3, SW_Site.csParam1, SW_Site.csParam2, SW_Site.shParam, sw->snowdepth, SW_Site.Tsoil_constant, SW_Site.stDeltaX, SW_Site.stMaxDepth, SW_Site.stNRGR, sw->snowpack[Today], - &SW_Soilwat.soiltempError, w->now.temp_max[Today], w->now.temp_min[Today], + &SW_Soilwat.soiltempError, w->now.temp_max, w->now.temp_min, sw->H_gt, sw->maxLyrTemperature, sw->minLyrTemperature, &w->surfaceMax, &w->surfaceMin); } diff --git a/SW_Flow_lib.c b/src/SW_Flow_lib.c similarity index 95% rename from SW_Flow_lib.c rename to src/SW_Flow_lib.c index 8184a0fee..c818162bd 100644 --- a/SW_Flow_lib.c +++ b/src/SW_Flow_lib.c @@ -91,16 +91,16 @@ #include #include #include -#include "generic.h" -#include "filefuncs.h" -#include "SW_Defines.h" -#include "SW_Site.h" // externs SW_Site -#include "SW_Flow_lib.h" -#include "SW_SoilWater.h" // externs SW_Soilwat -#include "SW_Carbon.h" // externs SW_Carbon -#include "Times.h" +#include "include/generic.h" +#include "include/filefuncs.h" +#include "include/SW_Defines.h" +#include "include/SW_Site.h" // externs SW_Site +#include "include/SW_Flow_lib.h" +#include "include/SW_SoilWater.h" // externs SW_Soilwat +#include "include/SW_Carbon.h" // externs SW_Carbon +#include "include/Times.h" -#include "SW_Model.h" // externs SW_Model +#include "include/SW_Model.h" // externs SW_Model @@ -372,7 +372,7 @@ void transp_weighted_avg(double *swp_avg, unsigned int n_tr_rgns, unsigned int n for (i = 0; i < n_layers; i++) { if (tr_regions[i] == r) { - swp += tr_coeff[i] * SW_SWCbulk2SWPmatric(SW_Site.lyr[i]->fractionVolBulk_gravel, swc[i], i); + swp += tr_coeff[i] * SW_SWRC_SWCtoSWP(swc[i], SW_Site.lyr[i]); sumco += tr_coeff[i]; } } @@ -484,10 +484,10 @@ void pot_soil_evap(double *bserate, unsigned int nelyrs, double ecoeff[], double } x = width[i] * ecoeff[i]; sumwidth += x; - avswp += x * SW_SWCbulk2SWPmatric(SW_Site.lyr[i]->fractionVolBulk_gravel, swc[i], i); + avswp += x * SW_SWRC_SWCtoSWP(swc[i], SW_Site.lyr[i]); } - // Note: avswp = 0 if swc = 0 because that is the return value of SW_SWCbulk2SWPmatric + // Note: avswp = 0 if swc = 0 because that is the return value of SW_SWRC_SWCtoSWP avswp /= (ZRO(sumwidth)) ? 1 : sumwidth; /* 8/27/92 (SLC) if totagb > Es_param_limit, assume soil surface is @@ -542,7 +542,7 @@ void pot_soil_evap_bs(double *bserate, unsigned int nelyrs, double ecoeff[], dou for (i = 0; i < nelyrs; i++) { x = width[i] * ecoeff[i]; sumwidth += x; - avswp += x * SW_SWCbulk2SWPmatric(SW_Site.lyr[i]->fractionVolBulk_gravel, swc[i], i); + avswp += x * SW_SWRC_SWCtoSWP(swc[i], SW_Site.lyr[i]); } avswp /= sumwidth; @@ -742,12 +742,18 @@ void remove_from_soil(double swc[], double qty[], double *aet, unsigned int nlyr **********************************************************************/ unsigned int i; - double swpfrac[MAX_LAYERS], sumswp = 0.0, swc_avail, q; + double swpfrac[MAX_LAYERS], sumswp = 0.0, swc_avail, q, tmpswp; SW_SOILWAT *st = &SW_Soilwat; for (i = 0; i < nlyrs; i++) { - swpfrac[i] = coeff[i] / SW_SWCbulk2SWPmatric(SW_Site.lyr[i]->fractionVolBulk_gravel, swc[i], i); + tmpswp = SW_SWRC_SWCtoSWP(swc[i], SW_Site.lyr[i]); + if (GT(tmpswp, 0.)) { + swpfrac[i] = coeff[i] / tmpswp; + } else { + // saturated conditions -> divide by SWP [-bar] at field capacity + swpfrac[i] = coeff[i] / 0.333; + } sumswp += swpfrac[i]; } @@ -969,7 +975,7 @@ void hydraulic_redistribution( swc[i] - fmin(lyr[i]->swcBulk_wiltpt, lyr[i]->swcBulk_atSWPcrit[vegk]) ); - swp[i] = SW_SWCbulk2SWPmatric(lyr[i]->fractionVolBulk_gravel, swc[i], i); + swp[i] = SW_SWRC_SWCtoSWP(swc[i], SW_Site.lyr[i]); /* Ryel et al. 2002: eq. 7 relative soil-root conductance */ relCondroot[i] = fmin( @@ -1797,25 +1803,26 @@ temp += temp; The algorithm selects a shorter time step if required for a stable solution (@cite Parton1978, @cite Parton1984). -@param ptr_dTime Yesterday's successful time step in seconds. -@param deltaX The depth increment for the soil temperature (regression) calculations (cm). -@param sT1 The soil surface temperature as upper boundary condition (°C). -@param sTconst The soil temperature at a soil depth where it stays constant as +@param[in,out] ptr_dTime Yesterday's successful time step in seconds. +@param[in] deltaX The depth increment for the soil temperature (regression) calculations (cm). +@param[in] sT1 The soil surface temperature as upper boundary condition (°C). +@param[in] sTconst The soil temperature at a soil depth where it stays constant as lower boundary condition (°C). -@param nRgr The number of regressions (1 extra value is needed for the avgLyrTempR and oldavgLyrTempR for the last layer). -@param avgLyrTempR An array of today's (regression)-layer soil temperature values (°C). -@param oldavgLyrTempR An array of yesterday's (regression)-layer soil temperature value (°C). -@param vwcR An array of temperature-layer VWC values (cm/layer). -@param wpR An array of temperature-layer wilting point values (cm/layer). -@param fcR An array of temperature-layer field capacity values (cm/layer). -@param bDensityR temperature-layer bulk density of the whole soil +@param[in] nRgr The number of regressions (1 extra value is needed for the avgLyrTempR and oldavgLyrTempR for the last layer). +@param[in,out] avgLyrTempR An array of today's (regression)-layer soil temperature values (°C). +@param[in] oldavgLyrTempR An array of yesterday's (regression)-layer soil temperature value (°C). +@param[in] vwcR An array of temperature-layer VWC values (cm/layer). +@param[in] wpR An array of temperature-layer wilting point values (cm/layer). +@param[in] fcR An array of temperature-layer field capacity values (cm/layer). +@param[in] bDensityR temperature-layer bulk density of the whole soil (g/cm3). -@param csParam1 A constant for the soil thermal conductivity equation. -@param csParam2 A constant for the soil thermal conductivity equation. -@param shParam A constant for specific heat capacity equation. -@param *ptr_stError A boolean indicating whether there was an error. -@param surface_range Temperature range at the surface (°C) -@param temperatureRangeR An array of temperature ranges at each (regression)-layer to be interpolated (°C) +@param[in] csParam1 A constant for the soil thermal conductivity equation. +@param[in] csParam2 A constant for the soil thermal conductivity equation. +@param[in] shParam A constant for specific heat capacity equation. +@param[in,out] *ptr_stError A boolean indicating whether there was an error. +@param[in] surface_range Temperature range at the surface (°C) +@param[in,out] temperatureRangeR An array of temperature ranges at each (regression)-layer to be interpolated (°C) +@param[in] depthsR Evenly spaced depths of the soil temperature profile (cm). @note avgLyrTempR[0] and temperatureRangeR[0] represent soil surface conditions. diff --git a/SW_Flow_lib_PET.c b/src/SW_Flow_lib_PET.c similarity index 74% rename from SW_Flow_lib_PET.c rename to src/SW_Flow_lib_PET.c index e35c8ae49..e051e46c1 100644 --- a/SW_Flow_lib_PET.c +++ b/src/SW_Flow_lib_PET.c @@ -14,10 +14,11 @@ /* --------------------------------------------------- */ #include -#include "generic.h" -#include "SW_Defines.h" +#include "include/generic.h" +#include "include/SW_Defines.h" +#include "include/filefuncs.h" -#include "SW_Flow_lib_PET.h" +#include "include/SW_Flow_lib_PET.h" @@ -731,6 +732,34 @@ double clearsky_directbeam(double P, double e_a, double int_sin_beta) return fmax(0., fmin(1., K_b)); } + +/** + @brief Transmissivity index for actual direct beam radiation + on the horizontal surface + + Based on Allen et al. 2006 @cite allen2006AaFM + + @param tau Observed atmospheric transmissivity (direct + diffuse) + for the horizontal surface [-] +*/ +double actual_horizontal_transmissivityindex(double tau) { + double K_bh; + + // Allen et al. 2006: eq. 41a-41c + if (tau >= 0.42) { + K_bh = 1.56 * tau - 0.55; + + } else if (tau <= 0.175) { + K_bh = 0.016 * tau; + + } else { + K_bh = 0.022 - 0.280 * tau + 0.828 * squared(tau) + 0.765 * powe(tau, 3.); + } + + return fmax(0., fmin(1., K_bh)); +} + + /** @brief Clearness index of diffuse radiation Based on relationships developed by Boes (1981) and ASCE-EWRI 2005 updated @@ -773,6 +802,20 @@ double clearnessindex_diffuse(double K_b) (Reindl et al. 1990 @cite reindl1990SE) to transpose direct and diffuse radiation to a tilted surface. + @note + - Surface irradiation is estimated from extraterrestrial radiation + if observed downward surface shortwave radiation `rsds` is not available, + i.e., equal to \ref SW_MISSING. + - If observed `cloud_cover` is available, then additional cloud effects + are estimated as part of atmospheric attenuation; otherwise, they are + ignored. + - Surface irradiation is derived from observed downward surface shortwave + radiation `rsds`, if available. + - If observed `cloud_cover` is not available, + i.e., equal to \ref SW_MISSING, then `cloud_cover` is estimated + from both observed radiation and expected cloud-less radiation. + + @param[in] doy Day of year [1-365]. @param[in] lat Latitude of the site [radians]. @param[in] elev Elevation of the site [m a.s.l.]. @@ -783,27 +826,42 @@ double clearnessindex_diffuse(double K_b) South facing slope: aspect = 0, East = -pi / 2, West = pi / 2, North = ±pi. @param[in] albedo Average albedo of the surrounding ground surface below the inclined surface [0-1]. - @param[in] cloud_cover Fraction of sky covered by clouds [0-1]. - @param[in] rel_humidity Daily mean relative humidity [%] - @param[in] air_temp_mean Daily mean air temperature [C] + @param[in,out] cloud_cover Percent of sky covered by clouds [0-100]; + optional, see notes + @param[in] e_a Actual vapor pressure [kPa] + + @param[in] rsds Observed downward surface shortwave radiation; + optional, see notes and `type_rsds` for units and definitions + @param[in] desc_rsds Description of the exact type and units of `rsds` with + - 0: `rsds` represents daily global horizontal irradiation [MJ / m2] + - 1: `rsds` represents flux density [W / m2] for a + (hypothetical) flat horizon averaged over an entire day (24 hour period) + - 2: `rsds` represents flux density [W / m2] for a + (hypothetical) flat horizon averaged over the daylight period of the day @param[out] H_oh Daily extraterrestrial horizontal irradiation [MJ / m2] @param[out] H_ot Daily extraterrestrial tilted irradiation [MJ / m2] @param[out] H_gh Daily global horizontal irradiation [MJ / m2] @return H_gt Daily global (tilted) irradiation [MJ / m2] */ -double solar_radiation(unsigned int doy, +double solar_radiation( + unsigned int doy, double lat, double elev, double slope, double aspect, - double albedo, double cloud_cover, double rel_humidity, double air_temp_mean, - double *H_oh, double *H_ot, double *H_gh) -{ + double albedo, double *cloud_cover, double e_a, + double rsds, unsigned int desc_rsds, + double *H_oh, double *H_ot, double *H_gh +) { double - P, e_a, + P, sun_angles[7], int_cos_theta[2], int_sin_beta[2], H_o[2], - k_c, - H_bh, H_dh, K_bh, K_dh, - H_bt, H_dt, H_rt, K_bt, f_ia, f_i, f_B, + k_c = SW_MISSING, + dl, + convert_rsds_to_H_gh = 1., + tau_h_obs, + H_bh_calc, H_dh_calc, K_bh_calc, K_dh_calc, + K_bh_obs, K_dh_obs, + H_bt, H_dt, H_rt, K_bt_calc, f_ia, f_i, f_B, H_g; @@ -827,63 +885,170 @@ double solar_radiation(unsigned int doy, // Calculate atmospheric pressure P = atmospheric_pressure(elev); - // Actual vapor pressure [kPa] estimated from daily mean air temperature and - // mean monthly relative humidity - // Allen et al. 2005: eqs 7 and 14 - e_a = rel_humidity / 100. * - 0.6108 * exp((17.27 * air_temp_mean) / (air_temp_mean + 237.3)); + //--- Calculate expected H_gh components from extraterrestrial radiation + //--- Separation/decomposition: separate global horizontal irradiation H_gh + // into direct and diffuse radiation components + // Atmospheric attenuation: clear-sky model for direct beam irradiation + K_bh_calc = clearsky_directbeam(P, e_a, int_sin_beta[0]); + H_bh_calc = K_bh_calc * (*H_oh); // Allen et al. 2006: eq. 24 - // Atmospheric attenuation: additional cloud effects - //k_c = overcast_attenuation_KastenCzeplak1980(cloud_cover / 100.); + // Diffuse irradiation + K_dh_calc = clearnessindex_diffuse(K_bh_calc); + H_dh_calc = K_dh_calc * (*H_oh); // Allen et al. 2006: eq. 25 - // TODO: improve estimation of sunshine_fraction n/N - // e.g., Essa and Etman 2004 (Meteorol Atmos Physics) and - // Matuszko 2012 (Int J Climatol) suggest that n/N != 1 - C - k_c = overcast_attenuation_Angstrom1924(1. - cloud_cover / 100.); + //--- Global horizontal irradiation + if (missing(rsds)) { + // Atmospheric attenuation: additional cloud effects + if (missing(*cloud_cover)) { + k_c = 1.; // ignore additional cloud cover effects - //--- Separation/decomposition: separate global horizontal irradiation H_gh - // into direct and diffuse radiation components + } else { + //k_c = overcast_attenuation_KastenCzeplak1980(*cloud_cover / 100.); - // Atmospheric attenuation: clear-sky model for direct beam irradiation - K_bh = clearsky_directbeam(P, e_a, int_sin_beta[0]); - H_bh = K_bh * k_c * (*H_oh); // Allen et al. 2006: eq. 24 + k_c + // TODO: improve estimation of sunshine_fraction n/N + // e.g., Essa and Etman 2004 (Meteorol Atmos Physics) and + // Matuszko 2012 (Int J Climatol) suggest that n/N != 1 - C - // Diffuse irradiation - K_dh = clearnessindex_diffuse(K_bh); - H_dh = K_dh * k_c * (*H_oh); // Allen et al. 2006: eq. 25 + k_c + // Note: if `overcast_attenuation_Angstrom1924()` is changed, then + // reflect updates in section `derive cloud cover if missing` + k_c = overcast_attenuation_Angstrom1924(1. - *cloud_cover / 100.); + } - // Global horizontal irradiation: Allen et al. 2006: eq. 23 - *H_gh = H_bh + H_dh; + // Use calculated expected radiation for H_gh + *H_gh = k_c * (H_bh_calc + H_dh_calc); // Allen et al. 2006: eq. 23 + k_c + + } else { + // Use observed radiation for H_gh + + //--- Deal with specific type and units of observed `rsds` + switch (desc_rsds) { + // 0: `rsds` represents daily global horizontal irradiation [MJ / m2] + case 0: + convert_rsds_to_H_gh = 1.; + break; + + // 1: `rsds` represents flux density [W / m2] for a (hypothetical) + // flat horizon averaged over an entire day (24 hour period) + case 1: + // Daily global horizontal irradiation [MJ/m2] from observed rsds [W/m2] + // 1e-6 [M] * (24 * 60 * 60) [s/day] * rsds [W/m2] + convert_rsds_to_H_gh = 0.0864; + break; + + // 2: `rsds` represents flux density [W / m2] for a (hypothetical) + // flat horizon averaged over the daylight period of the day + case 2: + // Day length [radian] when sun is above a hypothetical flat horizon + dl = sun_angles[6] - sun_angles[1]; + + // Daily global horizontal irradiation [MJ/m2] from observed rsds [W/m2] + // 1e-6 [M] * dl [radian] * (12 * 60 * 60 / pi) [s/radian] * rsds [W/m2] + convert_rsds_to_H_gh = 0.01375099 * dl; + break; + + default: + LogError( + logfp, + LOGFATAL, + "`desc_rsds` has an unrecognized value: %u", + desc_rsds + ); + } + + *H_gh = convert_rsds_to_H_gh * rsds; + if (!(*H_gh >= 0. && *H_gh <= *H_oh)) { + LogError( + logfp, + LOGWARN, + "\nInput global horizontal irradiation (%f) reset to be equal to " + "theoretical extraterrestrial radiation (%.0f MJ m-2) " + "because it was larger.\n", + *H_gh, + *H_oh + ); + + *H_gh = *H_oh; + } + + + //--- Derive cloud cover if missing + if (missing(*cloud_cover)) { + // Estimate cloud cover from calculated cloud-less H_gh and observed H_gh + k_c = fmax(0., fmin(1., *H_gh / (H_bh_calc + H_dh_calc))); + + // Inverse of `overcast_attenuation_Angstrom1924(1. - cloud_cover / 100.)` + *cloud_cover = fmax(0., fmin(100., 100. * (1. - (k_c - 0.25) / 0.75))); + } + } + + + + //--- Global tilted irradiation ------ //--- Transposition: transpose direct and diffuse radiation to tilted surface if (has_tilted_surface(slope, aspect)) { - // Direct beam irradiation - K_bt = clearsky_directbeam(P, e_a, int_sin_beta[1]); - H_bt = K_bt * k_c * (*H_ot); // Allen et al. 2006: eq. 30 + k_c + // Tilted direct beam transmissivity index + K_bt_calc = clearsky_directbeam(P, e_a, int_sin_beta[1]); - // Diffuse irradiation (isotropic) + // Ratio of expected direct beam radiation on slope vs horizontal surface + f_B = K_bt_calc / K_bh_calc * (*H_ot) / (*H_oh); // Allen et al. 2006: eq. 34 + + // Factor for diffuse irradiation (isotropic) f_i = 0.75 + 0.25 * cos(slope) - slope / swPI2; // Allen et al. 2006: eq. 32 - // Diffuse irradiation (anisotropic): HDKR model (Reindl et al. 1990) - f_B = K_bt / K_bh * (*H_ot) / (*H_oh); // Allen et al. 2006: eq. 34 + if (missing(rsds)) { + //--- Estimate (expected) H_gt components from extraterrestrial radiation - f_ia = f_i * (1. - K_bh) \ - * (1. + sqrt(K_bh / (K_bh + K_dh)) * pow(sin(slope / 2.), 3.)) \ - + f_B * K_bh; // Allen et al. 2006: eq. 33 + // Direct beam irradiation + H_bt = K_bt_calc * k_c * (*H_ot); // Allen et al. 2006: eq. 30 + k_c - H_dt = f_ia * H_dh; // Allen et al. 2006: eq. 31 + // Diffuse irradiation (anisotropic): HDKR model (Reindl et al. 1990) + f_ia = f_i * (1. - K_bh_calc) \ + * (1. + sqrt(K_bh_calc / (K_bh_calc + K_dh_calc)) * pow(sin(slope / 2.), 3.)) \ + + f_B * K_bh_calc; // Allen et al. 2006: eq. 33 + H_dt = f_ia * k_c * H_dh_calc; // Allen et al. 2006: eq. 31 + + } else { + //--- Estimate H_gt components from observed radiation + + // Observed atmospheric transmissivity for the horizontal surface + + // direct + diffuse transmissivity + tau_h_obs = (*H_gh) / (*H_oh); // Allen et al. 2006: eq. 39 + + // direct beam transmissivity index: Allen et al. 2006: eq. 41 + K_bh_obs = actual_horizontal_transmissivityindex(tau_h_obs); + + // diffuse transmissivity index + K_dh_obs = tau_h_obs - K_bh_obs; // Allen et al. 2006: eq. 42 + + + // Direct beam irradiation + H_bt = f_B * K_bh_obs * (*H_oh); // Allen et al. 2006: eq. 38 part 1 + + // Diffuse irradiation + f_ia = f_i * (1. - K_bh_obs) \ + * (1. + sqrt(K_bh_obs / (K_bh_obs + K_dh_obs)) * pow(sin(slope / 2.), 3.)) \ + + f_B * K_bh_obs; // Allen et al. 2006: eq. 40 (see eq. 33) + + H_dt = f_ia * K_dh_obs * (*H_oh); // Allen et al. 2006: eq. 38 part 2 + } - // Reflected irradiation; Allen et al. 2006: eq. 36 - H_rt = albedo * (1. - f_i) * (*H_gh); + // Reflected irradiation + // based on Allen et al. 2006: eq. 36 if H_gh derived from H_oh + // based on Allen et al. 2006: eq. 38 part 3 if observed H_gh + H_rt = albedo * (1. - f_i) * (*H_gh); - //--- Daily global titled irradiation: Allen et al. 2006: eq. 29 + // Daily global tilted irradiation H_gt + // if H_* are derived from H_oh: Allen et al. 2006: eq. 29 + // if H_* are derived from observed: Allen et al. 2006: eq. 38 H_g = H_bt + H_dt + H_rt; } else { @@ -892,6 +1057,18 @@ double solar_radiation(unsigned int doy, } + // Check for reasonable range of radiation [MJ/m2] + // - 50 MJ/m2 estimated as upper limit of H_oh from + // Duffie & Beckman 2013: Table 1.10.1 + if (!(H_g >= 0. && H_g <= 50)) { + LogError( + logfp, + LOGFATAL, + "\nSolar radiation (%f) out of valid range (0-50 MJ m-2)\n", + H_g + ); + } + return H_g; } @@ -988,6 +1165,67 @@ double svp(double T, double *slope_svp_to_t) { return 1e-3 * svp; } +/** + @brief Saturation vapor pressure for calculation of actual vapor pressure + + Implements equation 7 by Allen et al. (2005) @cite ASCE2005 + + @param[in] temp Daily mean, minimum, or maximum temperature or dewpoint temperature [C] + + @return Saturation vapor pressure [kPa] +*/ +double svp2(double temp) { + // Allen et al. 2005 eq 7 + return .6108 * exp((17.27 * temp) / (temp + 237.3)); +} + +/** + @brief Calculate actual vapor pressure based on relative humidity and mean temperature + + Implements equation 7 and 14 by Allen et al. (2005) @cite ASCE2005 + + @param hurs Daily mean relative humidity [%] + @param meanTemp Daily mean air temperature [C] + + @return Calculated actual vapor pressure [kPa] + */ +double actualVaporPressure1(double hurs, double meanTemp) { + // Allen et al. 2005 eqs 7 and 14 + return (hurs / 100.) * svp2(meanTemp); +} + +/** + @brief Calculate actual vapor pressure from daily minimum and maximum of + air temperature and relative humidity + + Implements equation 7 and 11 by Allen et al. (2005) @cite ASCE2005 + + @param maxHurs Daily maximum relative humidity [%] + @param minHurs Daily minimum relative humidity [%] + @param maxTemp Daily minimum air temperature [C] + @param minTemp Daily maximum air temperature [C] + + @return Calculated actual vapor pressure [kPa] + */ +double actualVaporPressure2(double maxHurs, double minHurs, double maxTemp, double minTemp) { + // Allen et al. 2005 eqs 7 and 11 + return (actualVaporPressure1(minHurs, maxTemp) + actualVaporPressure1(maxHurs, minTemp)) / 2; +} + +/** + @brief Calculate actual vapor pressure based on dew point temperature + + Implements equation 7 and 8 by Allen et al. (2005) @cite ASCE2005 + + @param dewpointTemp 2m dew point temperature [C] + + @return Calculated actual vapor pressure [kPa] + */ +double actualVaporPressure3(double dewpointTemp) { + // Allen et al. 2005 eqs 7 and 8 + return svp2(dewpointTemp); +} + /** @@ -1051,6 +1289,15 @@ double petfunc(double H_g, double avgtemp, double elev, // Penman (1948): n/N = clrsky = // = Ratio of actual/possible hours of sunshine = // approximate with 1 - m/10 = 1 - fraction of sky covered by cloud + + if (missing(cloudcov)) { + LogError( + logfp, + LOGFATAL, + "Cloud cover is missing." + ); + } + clrsky = 1. - cloudcov / 100.; // Wind speed (2 meters above ground) diff --git a/SW_Main.c b/src/SW_Main.c similarity index 73% rename from SW_Main.c rename to src/SW_Main.c index b4b71cabc..a15d44e73 100644 --- a/SW_Main.c +++ b/src/SW_Main.c @@ -23,15 +23,15 @@ #else #include #endif -#include "generic.h" // externs `QuietMode`, `EchoInits` -#include "filefuncs.h" // externs `_firstfile`, `inbuf` -#include "SW_Defines.h" -#include "SW_Control.h" -#include "SW_Site.h" -#include "SW_Weather.h" -#include "SW_Output.h" -#include "SW_Output_outtext.h" -#include "SW_Main_lib.h" +#include "include/generic.h" // externs `QuietMode`, `EchoInits` +#include "include/filefuncs.h" // externs `_firstfile`, `inbuf` +#include "include/SW_Defines.h" +#include "include/SW_Control.h" +#include "include/SW_Site.h" +#include "include/SW_Weather.h" +#include "include/SW_Output.h" +#include "include/SW_Output_outtext.h" +#include "include/SW_Main_lib.h" @@ -39,19 +39,6 @@ /* Local Function Definitions */ /* --------------------------------------------------- */ -static void check_log(void) { - /* =================================================== */ - /* function to be called by atexit() so it's the last - * to execute before termination. This is the place to - * do any cleanup or progress reporting. - */ - if (logfp != stdout && logfp != stderr) { - if (logged && !QuietMode) - sw_error(0, "\nCheck logfile for error or status messages.\n"); - CloseFile(&logfp); - } - -} /* =================================================== */ @@ -67,7 +54,7 @@ int main(int argc, char **argv) { /* =================================================== */ logged = swFALSE; - atexit(check_log); + atexit(sw_check_log); logfp = stdout; sw_init_args(argc, argv); @@ -83,6 +70,9 @@ int main(int argc, char **argv) { // read user inputs SW_CTL_read_inputs_from_disk(); + // finalize daily weather + SW_WTH_finalize_all_weather(); + // initialize simulation run (based on user inputs) SW_CTL_init_run(); diff --git a/SW_Main_lib.c b/src/SW_Main_lib.c similarity index 65% rename from SW_Main_lib.c rename to src/SW_Main_lib.c index 307ce56e4..972f20030 100644 --- a/SW_Main_lib.c +++ b/src/SW_Main_lib.c @@ -23,13 +23,13 @@ #else #include #endif -#include "generic.h" // externs `QuietMode`, `EchoInits` -#include "filefuncs.h" // externs `_firstfile`, `inbuf` -#include "SW_Defines.h" -#include "SW_Control.h" -#include "SW_Site.h" -#include "SW_Weather.h" -#include "SW_Main_lib.h" +#include "include/generic.h" // externs `QuietMode`, `EchoInits` +#include "include/filefuncs.h" // externs `_firstfile`, `inbuf` +#include "include/SW_Defines.h" +#include "include/SW_Control.h" +#include "include/SW_Site.h" +#include "include/SW_Weather.h" +#include "include/SW_Main_lib.h" /* =================================================== */ @@ -139,67 +139,87 @@ void sw_init_args(int argc, char **argv) { if (strncmp(opts[op], argv[a], 2) == 0) break; /* found it, move on */ } - if (op == nopts) { + + if (op >= nopts) { sw_print_usage(); sw_error(-1, "\nInvalid option %s\n", argv[a]); - } - - *str = '\0'; - /* extract value part of option-value pair */ - if (valopts[op]) { - if ('\0' != argv[a][2]) { /* no space betw opt-value */ - strcpy(str, (argv[a] + 2)); - } else if ('-' != *argv[a + 1]) { /* space betw opt-value */ - strcpy(str, argv[++a]); - - } else if (0 < valopts[op]) { /* required opt-val not found */ - sw_print_usage(); - sw_error(-1, "\nIncomplete option %s\n", opts[op]); - } /* opt-val not required */ + } else { + // Use `valopts[op]` in else-branch to avoid + // `warning: array subscript 6 is above array bounds of 'int[6]' [-Warray-bounds]` + + *str = '\0'; + /* extract value part of option-value pair */ + if (valopts[op]) { + if ('\0' != argv[a][2]) { /* no space betw opt-value */ + strcpy(str, (argv[a] + 2)); + + } else if ('-' != *argv[a + 1]) { /* space betw opt-value */ + strcpy(str, argv[++a]); + + } else if (0 < valopts[op]) { /* required opt-val not found */ + sw_print_usage(); + sw_error(-1, "\nIncomplete option %s\n", opts[op]); + } /* opt-val not required */ + } + + /* tell us what to do here */ + /* set indicators/variables based on results */ + switch (op) { + case 0: /* -d */ + if (!ChDir(str)) { + LogError(logfp, LOGFATAL, "Invalid project directory (%s)", str); + } + break; + + case 1: /* -f */ + strcpy(_firstfile, str); + break; + + case 2: /* -e */ + EchoInits = swTRUE; + break; + + case 3: /* -q */ + QuietMode = swTRUE; + break; + + case 4: /* -v */ + sw_print_version(); + sw_error(-1, ""); + break; + + case 5: /* -h */ + sw_print_usage(); + sw_error(-1, ""); + break; + + default: + LogError( + logfp, + LOGFATAL, + "Programmer: bad option in main:sw_init_args:switch" + ); + } + + a++; /* move to next valid arg-value position */ } + } /* end for(i) */ - /* tell us what to do here */ - /* set indicators/variables based on results */ - switch (op) { - case 0: /* -d */ - if (!ChDir(str)) { - LogError(logfp, LOGFATAL, "Invalid project directory (%s)", str); - } - break; - - case 1: /* -f */ - strcpy(_firstfile, str); - break; - - case 2: /* -e */ - EchoInits = swTRUE; - break; - - case 3: /* -q */ - QuietMode = swTRUE; - break; - - case 4: /* -v */ - sw_print_version(); - sw_error(-1, ""); - break; - - case 5: /* -h */ - sw_print_usage(); - sw_error(-1, ""); - break; - - default: - LogError( - logfp, - LOGFATAL, - "Programmer: bad option in main:sw_init_args:switch" - ); - } +} - a++; /* move to next valid arg-value position */ - } /* end for(i) */ +void sw_check_log(void) { + /* =================================================== */ + /* function to be called by atexit() so it's the last + * to execute before termination. This is the place to + * do any cleanup or progress reporting. + */ + if (logfp != stdout && logfp != stderr) { + CloseFile(&logfp); + if (logged && !QuietMode) { + sw_error(0, "\nCheck logfile for error or status messages.\n"); + } + } } diff --git a/SW_Markov.c b/src/SW_Markov.c similarity index 82% rename from SW_Markov.c rename to src/SW_Markov.c index 7b3d83b61..97299a619 100644 --- a/SW_Markov.c +++ b/src/SW_Markov.c @@ -23,17 +23,17 @@ #include #include #include -#include "generic.h" -#include "filefuncs.h" -#include "rands.h" -#include "Times.h" -#include "myMemory.h" -#include "SW_Defines.h" -#include "SW_Files.h" -#include "SW_Weather.h" -#include "SW_Model.h" // externs SW_Model -#include "SW_Markov.h" -#include "pcg/pcg_basic.h" +#include "include/generic.h" +#include "include/filefuncs.h" +#include "include/rands.h" +#include "include/Times.h" +#include "include/myMemory.h" +#include "include/SW_Defines.h" +#include "include/SW_Files.h" +#include "include/SW_Weather.h" +#include "include/SW_Model.h" // externs SW_Model +#include "include/SW_Markov.h" +#include "external/pcg/pcg_basic.h" /* =================================================== */ @@ -179,7 +179,12 @@ static void mvnorm(RealD *tmax, RealD *tmin, RealD wTmax, RealD wTmin, LogError(logfp, LOGFATAL, "\nBad covariance matrix in mvnorm()"); } - vc11 = (EQ(wTmin_var, s)) ? 0. : sqrt(wTmin_var - s); + /* Apparently, it's possible for some but not all setups that + for some values of `s` and `wTmin_var` (e.g., 99.264050000, 99.264050000) + that both `GT(s, wTmin_var)` and `EQ(wTmin_var, s)` are FALSE; + and thus, `vc11` becomes `NaN` + */ + vc11 = (LE(wTmin_var, s)) ? 0. : sqrt(wTmin_var - s); // mvnorm = mean + A * z *tmax = wTmax_sd * z1 + wTmax; @@ -297,6 +302,15 @@ void SW_MKV_today(TimeInt doy0, RealD *tmax, RealD *tmin, RealD *rain) { short debug = 0; #endif + #ifdef SWDEBUG + if (debug) { + swprintf( + "mkv(before): yr=%u/doy0=%u: ppt=%.3f, tmax=%.3f, tmin=%.3f\n", + SW_Model.year, doy0, *rain, *tmax, *tmin + ); + } + #endif + /* Calculate Precipitation: prop = probability that it precipitates today depending on whether it was wet (precipitated) yesterday `wetprob` or @@ -335,8 +349,10 @@ void SW_MKV_today(TimeInt doy0, RealD *tmax, RealD *tmin, RealD *rain) { #ifdef SWDEBUG if (debug) { - swprintf("mkv: yr=%d/doy0=%d/week=%d: ppt=%.3f, tmax=%.3f, tmin=%.3f\n", - SW_Model.year, doy0, week, *rain, *tmax, *tmin); + swprintf( + "mkv(after): yr=%u/doy0=%u/week=%u: ppt=%.3f, tmax=%.3f, tmin=%.3f\n", + SW_Model.year, doy0, week, *rain, *tmax, *tmin + ); } #endif @@ -371,8 +387,13 @@ Bool SW_MKV_read_prob(void) { // Check that text file is ok: if (x < nitems) { msg_type = LOGFATAL; - sprintf(msg, "Too few values in line %d of file %s\n", - lineno, MyFileName); + snprintf( + msg, + sizeof msg, + "Too few values in line %d of file %s\n", + lineno, + MyFileName + ); } // Check that input values meet requirements: @@ -381,8 +402,14 @@ Bool SW_MKV_read_prob(void) { if (!isfinite((float) day) || day < 1 || day > MAX_DAYS) { msg_type = LOGFATAL; - sprintf(msg, "'day' = %d is out of range in line %d of file %s\n", - day, lineno, MyFileName); + snprintf( + msg, + sizeof msg, + "'day' = %d is out of range in line %d of file %s\n", + day, + lineno, + MyFileName + ); } // Probabilities are in [0, 1] @@ -390,18 +417,32 @@ Bool SW_MKV_read_prob(void) { !isfinite(dry) || LT(dry, 0.) || GT(dry, 1.)) { msg_type = LOGFATAL; - sprintf(msg, "Probabilities of being wet = %f and/or of being dry = %f "\ + snprintf( + msg, + sizeof msg, + "Probabilities of being wet = %f and/or of being dry = %f "\ "are out of range in line %d of file %s\n", - wet, dry, lineno, MyFileName); + wet, + dry, + lineno, + MyFileName + ); } // Mean and SD of daily precipitation are >= 0 if (!isfinite(avg) || LT(avg, 0.) || !isfinite(std) || LT(std, 0.)) { msg_type = LOGFATAL; - sprintf(msg, "Mean daily precipitation = %f and/or SD = %f "\ + snprintf( + msg, + sizeof msg, + "Mean daily precipitation = %f and/or SD = %f "\ "are out of range in line %d of file %s\n", - avg, std, lineno, MyFileName); + avg, + std, + lineno, + MyFileName + ); } // If any input is bad, then close file and fail with message: @@ -454,34 +495,61 @@ Bool SW_MKV_read_cov(void) { // Check that text file is ok: if (x < nitems) { msg_type = LOGFATAL; - sprintf(msg, "Too few values in line %d of file %s\n", - lineno, MyFileName); + snprintf( + msg, + sizeof msg, + "Too few values in line %d of file %s\n", + lineno, + MyFileName + ); } // week is a real calendar week if (!isfinite((float) week) || week < 1 || week > MAX_WEEKS) { msg_type = LOGFATAL; - sprintf(msg, "'week' = %d is out of range in line %d of file %s\n", - week, lineno, MyFileName); + snprintf( + msg, + sizeof msg, + "'week' = %d is out of range in line %d of file %s\n", + week, + lineno, + MyFileName + ); } // Mean weekly temperature values are real numbers if (!isfinite(t1) || !isfinite(t2)) { msg_type = LOGFATAL; - sprintf(msg, "Mean weekly temperature (max = %f and/or min = %f) "\ + snprintf( + msg, + sizeof msg, + "Mean weekly temperature (max = %f and/or min = %f) "\ "are not real numbers in line %d of file %s\n", - t1, t2, lineno, MyFileName); + t1, + t2, + lineno, + MyFileName + ); } // Covariance values are finite if (!isfinite(t3) || !isfinite(t4) || !isfinite(t5) || !isfinite(t6)) { msg_type = LOGFATAL; - sprintf(msg, "One of the covariance values is not a real number "\ + snprintf( + msg, + sizeof msg, + "One of the covariance values is not a real number "\ "(t3 = %f; t4 = %f; t5 = %f; t6 = %f) in line %d of file %s\n", - t3, t4, t5, t6, lineno, MyFileName); + t3, + t4, + t5, + t6, + lineno, + MyFileName + ); } // Correction factors are real numbers @@ -489,9 +557,18 @@ Bool SW_MKV_read_cov(void) { !isfinite(cfnw) || !isfinite(cfnd)) { msg_type = LOGFATAL; - sprintf(msg, "One of the correction factor is not a real number "\ + snprintf( + msg, + sizeof msg, + "One of the correction factor is not a real number "\ "(cfxw = %f; cfxd = %f; cfnw = %f; cfnd = %f) in line %d of file %s\n", - cfxw, cfxd, cfnw, cfnd, lineno, MyFileName); + cfxw, + cfxd, + cfnw, + cfnd, + lineno, + MyFileName + ); } // If any input is bad, then close file and fail with message: @@ -525,20 +602,28 @@ Bool SW_MKV_read_cov(void) { void SW_MKV_setup(void) { SW_MKV_construct(); - if (!SW_MKV_read_prob()) { - LogError(logfp, LOGFATAL, "Markov weather requested but could not open %s", - SW_F_name(eMarkovProb)); + if (!SW_MKV_read_prob() && SW_Weather.generateWeatherMethod == 2) { + LogError( + logfp, + LOGFATAL, + "Weather generator requested but could not open %s", + SW_F_name(eMarkovProb) + ); } - if (!SW_MKV_read_cov()) { - LogError(logfp, LOGFATAL, "Markov weather requested but could not open %s", - SW_F_name(eMarkovCov)); + if (!SW_MKV_read_cov() && SW_Weather.generateWeatherMethod == 2) { + LogError( + logfp, + LOGFATAL, + "Weather generator requested but could not open %s", + SW_F_name(eMarkovCov) + ); } } #ifdef DEBUG_MEM -#include "myMemory.h" +#include "include/myMemory.h" /*======================================================*/ void SW_MKV_SetMemoryRefs( void) { /* when debugging memory problems, use the bookkeeping diff --git a/SW_Model.c b/src/SW_Model.c similarity index 91% rename from SW_Model.c rename to src/SW_Model.c index bffaf0d6c..01b034bf3 100644 --- a/SW_Model.c +++ b/src/SW_Model.c @@ -35,19 +35,18 @@ #include #include #include -#include "generic.h" -#include "filefuncs.h" -#include "rands.h" -#include "Times.h" -#include "myMemory.h" +#include "include/generic.h" +#include "include/filefuncs.h" +#include "include/rands.h" +#include "include/Times.h" +#include "include/myMemory.h" -#include "SW_Defines.h" -#include "SW_Files.h" -#include "SW_Weather.h" -#include "SW_Site.h" // externs SW_Site -#include "SW_SoilWater.h" /* for setup_new_year() */ -#include "SW_Times.h" -#include "SW_Model.h" // externs SW_Model +#include "include/SW_Defines.h" +#include "include/SW_Files.h" +#include "include/SW_Site.h" // externs SW_Site +#include "include/SW_SoilWater.h" /* for setup_new_year() */ +#include "include/SW_Times.h" +#include "include/SW_Model.h" // externs SW_Model /* =================================================== */ @@ -195,7 +194,7 @@ void SW_MDL_read(void) { } if (!(fstartdy && fenddy && fhemi)) { - sprintf(errstr, "\nNot found in %s:\n", MyFileName); + snprintf(errstr, MAX_ERROR, "\nNot found in %s:\n", MyFileName); if (!fstartdy) { strcat(errstr, "\tStart Day - using 1\n"); m->startstart = 1; diff --git a/SW_Output.c b/src/SW_Output.c similarity index 94% rename from SW_Output.c rename to src/SW_Output.c index dc614b0dd..363621667 100644 --- a/SW_Output.c +++ b/src/SW_Output.c @@ -33,35 +33,35 @@ #include #include #include -#include "generic.h" // externs `QuietMode`, `EchoInits` -#include "filefuncs.h" // externs `_firstfile`, `inbuf` -#include "myMemory.h" -#include "Times.h" - -#include "SW_Carbon.h" // externs SW_Carbon -#include "SW_Defines.h" -#include "SW_Files.h" -#include "SW_Model.h" // externs SW_Model -#include "SW_Site.h" // externs SW_Site -#include "SW_SoilWater.h" // externs SW_Soilwat -#include "SW_Times.h" -#include "SW_Weather.h" // externs SW_Weather -#include "SW_VegEstab.h" // externs SW_VegEstab -#include "SW_VegProd.h" // externs SW_VegProd -#include "SW_Flow_lib.h" // externs stValues - -#include "SW_Output.h" +#include "include/generic.h" // externs `QuietMode`, `EchoInits` +#include "include/filefuncs.h" // externs `_firstfile`, `inbuf` +#include "include/myMemory.h" +#include "include/Times.h" + +#include "include/SW_Carbon.h" // externs SW_Carbon +#include "include/SW_Defines.h" +#include "include/SW_Files.h" +#include "include/SW_Model.h" // externs SW_Model +#include "include/SW_Site.h" // externs SW_Site +#include "include/SW_SoilWater.h" // externs SW_Soilwat +#include "include/SW_Times.h" +#include "include/SW_Weather.h" // externs SW_Weather +#include "include/SW_VegEstab.h" // externs SW_VegEstab +#include "include/SW_VegProd.h" // externs SW_VegProd +#include "include/SW_Flow_lib.h" // externs stValues + +#include "include/SW_Output.h" // Array-based output declarations: #ifdef SW_OUTARRAY - #include "SW_Output_outarray.h" + #include "include/SW_Output_outarray.h" #endif // Text-based output declarations: #ifdef SW_OUTTEXT // externs `SW_OutFiles`, `print_IterationSummary`, `print_SW_Output`, // `sw_outstr`, `sw_outstr_agg` -#include "SW_Output_outtext.h" +#include "include/SW_Output_outtext.h" #endif /* Note: `get_XXX` functions are declared in `SW_Output.h` @@ -354,17 +354,17 @@ static void sumof_wth(SW_WEATHER *v, SW_WEATHER_OUTPUTS *s, OutKey k) { case eSW_Temp: - s->temp_max += v->now.temp_max[Today]; - s->temp_min += v->now.temp_min[Today]; - s->temp_avg += v->now.temp_avg[Today]; + s->temp_max += v->now.temp_max; + s->temp_min += v->now.temp_min; + s->temp_avg += v->now.temp_avg; //added surfaceAvg for sum s->surfaceAvg += v->surfaceAvg; s->surfaceMax += v->surfaceMax; s->surfaceMin += v->surfaceMin; break; case eSW_Precip: - s->ppt += v->now.ppt[Today]; - s->rain += v->now.rain[Today]; + s->ppt += v->now.ppt; + s->rain += v->now.rain; s->snow += v->snow; s->snowmelt += v->snowmelt; s->snowloss += v->snowloss; @@ -530,7 +530,7 @@ static void sumof_swc(SW_SOILWAT *v, SW_SOILWAT_OUTPUTS *s, OutKey k) s->maxLyrTemperature[i] += v->maxLyrTemperature[i]; } break; - + case eSW_Frozen: ForEachSoilLayer(i) s->lyrFrozen[i] += v->lyrFrozen[i]; @@ -670,7 +670,7 @@ static void average_for(ObjType otyp, OutPeriod pd) { s->p_accu[pd]->minLyrTemperature[i] / div; } break; - + case eSW_Frozen: ForEachSoilLayer(i) { s->p_oagg[pd]->lyrFrozen[i] = (SW_Output[k].sumtype == eSW_Fnl) ? @@ -1677,9 +1677,9 @@ void SW_OUT_set_colnames(void) { strcat(ctemp, "_"); strcat(ctemp, cnames_eSW_Temp[i % 3]); } - + colnames_OUT[eSW_Temp][i] = Str_Dup(ctemp); - + } #ifdef SWDEBUG if (debug) swprintf(" 'eSW_Precip' ..."); @@ -1844,20 +1844,20 @@ void SW_OUT_set_colnames(void) { #endif j = 0; // Layer variable for the next for-loop, 0 is first layer not surface for (i = 0; i < ncol_OUT[eSW_SoilTemp]; i++) { - + // Check if ready to go onto next layer (added all min/max/avg headers for layer) if(i % 3 == 0 && i > 1) j++; - + // For layers 1 through ncol_OUT[eSW_SoilTemp] strcpy(ctemp, Layers_names[j]); - + strcat(ctemp, "_"); strcat(ctemp, cnames_eSW_Temp[i % 3]); - + colnames_OUT[eSW_SoilTemp][i] = Str_Dup(ctemp); } - + #ifdef SWDEBUG if (debug) swprintf(" 'eSW_Frozen' ..."); #endif @@ -1881,7 +1881,7 @@ void SW_OUT_set_colnames(void) { colnames_OUT[eSW_CO2Effects][j + i * NVEGTYPES] = Str_Dup(ctemp); } } - + #ifdef SWDEBUG if (debug) swprintf(" 'eSW_Biomass' ..."); #endif @@ -1946,7 +1946,7 @@ void SW_OUT_new_year(void) -int SW_OUT_read_onekey(OutKey k, OutSum sumtype, int first, int last, char msg[]) +int SW_OUT_read_onekey(OutKey k, OutSum sumtype, int first, int last, char msg[], size_t sizeof_msg) { int res = 0; // return value indicating type of message if any @@ -1969,8 +1969,14 @@ int SW_OUT_read_onekey(OutKey k, OutSum sumtype, int first, int last, char msg[] { SW_Output[k].sumtype = eSW_Avg; - sprintf(msg, "%s : Summary Type FIN with key %s is meaningless.\n" \ - " Using type AVG instead.", MyFileName, key2str[k]); + snprintf( + msg, + sizeof_msg, + "%s : Summary Type FIN with key %s is meaningless.\n" \ + " Using type AVG instead.", + MyFileName, + key2str[k] + ); res = LOGWARN; } @@ -1991,8 +1997,13 @@ int SW_OUT_read_onekey(OutKey k, OutSum sumtype, int first, int last, char msg[] { SW_Output[k].use = swFALSE; - sprintf(msg, "%s : Output key %s is currently unimplemented.", - MyFileName, key2str[k]); + snprintf( + msg, + sizeof_msg, + "%s : Output key %s is currently unimplemented.", + MyFileName, + key2str[k] + ); return(LOGNOTE); } @@ -2001,9 +2012,14 @@ int SW_OUT_read_onekey(OutKey k, OutSum sumtype, int first, int last, char msg[] { SW_Output[k].use = swFALSE; - sprintf(msg, "%s : DEEPSWC cannot produce output if deep drainage is " \ + snprintf( + msg, + sizeof_msg, + "%s : DEEPSWC cannot produce output if deep drainage is " \ "not simulated (flag not set in %s).", - MyFileName, SW_F_name(eSite)); + MyFileName, + SW_F_name(eSite) + ); return(LOGWARN); } @@ -2013,8 +2029,14 @@ int SW_OUT_read_onekey(OutKey k, OutSum sumtype, int first, int last, char msg[] if (SW_Output[k].last_orig == 0) { - sprintf(msg, "%s : Invalid ending day (%d), key=%s.", - MyFileName, last, key2str[k]); + snprintf( + msg, + sizeof_msg, + "%s : Invalid ending day (%d), key=%s.", + MyFileName, + last, + key2str[k] + ); return(LOGFATAL); } @@ -2158,7 +2180,8 @@ void SW_OUT_read(void) str2stype(Str_ToUpper(sumtype, upsum)), first, !Str_CompareI("END", (char *)last) ? 366 : atoi(last), - msg + msg, + sizeof msg ); if (msg_type != 0) { @@ -2474,7 +2497,7 @@ void SW_OUT_write_today(void) { if (use_OutPeriod[p] && writeit[p]) { - get_outstrleader(p, str_time); + get_outstrleader(p, str_time, sizeof str_time); if (SW_OutFiles.make_regular[p]) { @@ -2545,9 +2568,9 @@ void _echo_outputs(void) strcat(errstr, key2str[k]); strcat(errstr, "\n\tSummary Type: "); strcat(errstr, styp2str[SW_Output[k].sumtype]); - sprintf(str, "\n\tStart period: %d", SW_Output[k].first_orig); + snprintf(str, OUTSTRLEN, "\n\tStart period: %d", SW_Output[k].first_orig); strcat(errstr, str); - sprintf(str, "\n\tEnd period : %d", SW_Output[k].last_orig); + snprintf(str, OUTSTRLEN, "\n\tEnd period : %d", SW_Output[k].last_orig); strcat(errstr, str); strcat(errstr, "\n"); } @@ -2559,7 +2582,7 @@ void _echo_outputs(void) #ifdef DEBUG_MEM -#include "myMemory.h" +#include "include/myMemory.h" /** when debugging memory problems, use the bookkeeping code in myMemory.c This routine sets the known memory refs in this module diff --git a/SW_Output_get_functions.c b/src/SW_Output_get_functions.c similarity index 87% rename from SW_Output_get_functions.c rename to src/SW_Output_get_functions.c index 38b8de7d0..ca4dbd09e 100644 --- a/SW_Output_get_functions.c +++ b/src/SW_Output_get_functions.c @@ -24,23 +24,23 @@ #include #include -#include "generic.h" -#include "filefuncs.h" -#include "myMemory.h" -#include "Times.h" - -#include "SW_Carbon.h" // externs SW_Carbon -#include "SW_Defines.h" -#include "SW_Files.h" -#include "SW_Model.h" // externs SW_Model -#include "SW_Site.h" // externs SW_Site -#include "SW_SoilWater.h" // externs SW_Soilwat -#include "SW_Times.h" -#include "SW_Weather.h" // externs SW_Weather -#include "SW_VegEstab.h" // externs SW_VegEstab -#include "SW_VegProd.h" // externs SW_VegProd - -#include "SW_Output.h" // externs `_Sep`, `tOffset`, `ncol_OUT` +#include "include/generic.h" +#include "include/filefuncs.h" +#include "include/myMemory.h" +#include "include/Times.h" + +#include "include/SW_Carbon.h" // externs SW_Carbon +#include "include/SW_Defines.h" +#include "include/SW_Files.h" +#include "include/SW_Model.h" // externs SW_Model +#include "include/SW_Site.h" // externs SW_Site +#include "include/SW_SoilWater.h" // externs SW_Soilwat +#include "include/SW_Times.h" +#include "include/SW_Weather.h" // externs SW_Weather +#include "include/SW_VegEstab.h" // externs SW_VegEstab +#include "include/SW_VegProd.h" // externs SW_VegProd + +#include "include/SW_Output.h" // externs `_Sep`, `tOffset`, `ncol_OUT` #ifdef RSOILWAT #include @@ -50,20 +50,20 @@ #ifdef STEPWAT #include -#include "../sxw.h" // externs `*SXW` -#include "../ST_globals.h" // externs `*Globals`, `SuperGlobals` +#include "sxw.h" // externs `*SXW` +#include "ST_globals.h" // externs `*Globals`, `SuperGlobals` #endif // Array-based output declarations: #ifdef SW_OUTARRAY // externs `ncol_TimeOUT`, `nrow_OUT`, `irow_OUT`, `*p_OUT`, `*p_OUTsd` -#include "SW_Output_outarray.h" +#include "include/SW_Output_outarray.h" #endif // Text-based output declarations: #ifdef SW_OUTTEXT // externs `print_IterationSummary`, `sw_outstr`, `sw_outstr_agg` -#include "SW_Output_outtext.h" +#include "include/SW_Output_outtext.h" #endif @@ -85,8 +85,12 @@ static void format_IterationSummary(RealD *p, RealD *psd, OutPeriod pd, IntUS N) n = iOUT(i, pd); sd = final_running_sd(SuperGlobals.runModelIterations, psd[n]); - sprintf(str, "%c%.*f%c%.*f", - _Sep, OUT_DIGITS, p[n], _Sep, OUT_DIGITS, sd); + snprintf( + str, + OUTSTRLEN, + "%c%.*f%c%.*f", + _Sep, OUT_DIGITS, p[n], _Sep, OUT_DIGITS, sd + ); strcat(sw_outstr_agg, str); } } @@ -106,7 +110,7 @@ static void format_IterationSummary2(RealD *p, RealD *psd, OutPeriod pd, n = iOUT2(i, k + offset, pd); sd = final_running_sd(SuperGlobals.runModelIterations, psd[n]); - sprintf(str, "%c%.*f%c%.*f", + snprintf(str, OUTSTRLEN, "%c%.*f%c%.*f", _Sep, OUT_DIGITS, p[n], _Sep, OUT_DIGITS, sd); strcat(sw_outstr_agg, str); } @@ -153,12 +157,12 @@ void get_co2effects_text(OutPeriod pd) { if (pd) {} // hack to silence "-Wunused-parameter" ForEachVegType(k) { - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, v->veg[k].co2_multipliers[BIO_INDEX][SW_Model.simyear]); strcat(sw_outstr, str); } ForEachVegType(k) { - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, v->veg[k].co2_multipliers[WUE_INDEX][SW_Model.simyear]); strcat(sw_outstr, str); } @@ -217,33 +221,33 @@ void get_biomass_text(OutPeriod pd) { sw_outstr[0] = '\0'; // fCover for NVEGTYPES plus bare-ground - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, v->bare_cov.fCover); + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, v->bare_cov.fCover); strcat(sw_outstr, str); ForEachVegType(k) { - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, v->veg[k].cov.fCover); + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, v->veg[k].cov.fCover); strcat(sw_outstr, str); } // biomass (g/m2 as component of total) for NVEGTYPES plus totals and litter - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, vo->biomass_total); + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, vo->biomass_total); strcat(sw_outstr, str); ForEachVegType(k) { - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, vo->veg[k].biomass_inveg); + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, vo->veg[k].biomass_inveg); strcat(sw_outstr, str); } - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, vo->litter_total); + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, vo->litter_total); strcat(sw_outstr, str); // biolive (g/m2 as component of total) for NVEGTYPES plus totals - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, vo->biolive_total); + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, vo->biolive_total); strcat(sw_outstr, str); ForEachVegType(k) { - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, vo->veg[k].biolive_inveg); + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, vo->veg[k].biolive_inveg); strcat(sw_outstr, str); } // leaf area index [m2/m2] - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, vo->LAI); + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, vo->LAI); strcat(sw_outstr, str); } #endif @@ -363,7 +367,7 @@ void get_estab_text(OutPeriod pd) for (i = 0; i < v->count; i++) { - sprintf(str, "%c%d", _Sep, v->parms[i]->estab_doy); + snprintf(str, OUTSTRLEN, "%c%d", _Sep, v->parms[i]->estab_doy); strcat(sw_outstr, str); } } @@ -441,7 +445,7 @@ void get_temp_text(OutPeriod pd) SW_WEATHER_OUTPUTS *vo = SW_Weather.p_oagg[pd]; sw_outstr[0] = '\0'; - sprintf(sw_outstr, "%c%.*f%c%.*f%c%.*f%c%.*f%c%.*f%c%.*f", + snprintf(sw_outstr, sizeof sw_outstr,"%c%.*f%c%.*f%c%.*f%c%.*f%c%.*f%c%.*f", _Sep, OUT_DIGITS, vo->temp_max, _Sep, OUT_DIGITS, vo->temp_min, _Sep, OUT_DIGITS, vo->temp_avg, @@ -535,7 +539,7 @@ void get_precip_text(OutPeriod pd) SW_WEATHER_OUTPUTS *vo = SW_Weather.p_oagg[pd]; sw_outstr[0] = '\0'; - sprintf(sw_outstr, "%c%.*f%c%.*f%c%.*f%c%.*f%c%.*f", + snprintf(sw_outstr, sizeof sw_outstr,"%c%.*f%c%.*f%c%.*f%c%.*f%c%.*f", _Sep, OUT_DIGITS, vo->ppt, _Sep, OUT_DIGITS, vo->rain, _Sep, OUT_DIGITS, vo->snow, @@ -630,7 +634,7 @@ void get_vwcBulk_text(OutPeriod pd) ForEachSoilLayer(i) { /* vwcBulk at this point is identical to swcBulk */ - sprintf(str, "%c%.*f", + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, vo->vwcBulk[i] / SW_Site.lyr[i]->width); strcat(sw_outstr, str); } @@ -709,7 +713,7 @@ void get_vwcMatric_text(OutPeriod pd) /* vwcMatric at this point is identical to swcBulk */ convert = 1. / (1. - SW_Site.lyr[i]->fractionVolBulk_gravel) / SW_Site.lyr[i]->width; - sprintf(str, "%c%.*f", + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, vo->vwcMatric[i] * convert); strcat(sw_outstr, str); } @@ -794,7 +798,7 @@ void get_swa_text(OutPeriod pd) { ForEachSoilLayer(i) { - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, vo->SWA_VegType[k][i]); + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, vo->SWA_VegType[k][i]); strcat(sw_outstr, str); } } @@ -878,7 +882,7 @@ void get_swcBulk_text(OutPeriod pd) ForEachSoilLayer(i) { - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, vo->swcBulk[i]); + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, vo->swcBulk[i]); strcat(sw_outstr, str); } } @@ -975,10 +979,10 @@ void get_swpMatric_text(OutPeriod pd) ForEachSoilLayer(i) { /* swpMatric at this point is identical to swcBulk */ - val = SW_SWCbulk2SWPmatric(SW_Site.lyr[i]->fractionVolBulk_gravel, - vo->swpMatric[i], i); + val = SW_SWRC_SWCtoSWP(vo->swpMatric[i], SW_Site.lyr[i]); - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, val); + + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, val); strcat(sw_outstr, str); } } @@ -1002,8 +1006,7 @@ void get_swpMatric_mem(OutPeriod pd) ForEachSoilLayer(i) { /* swpMatric at this point is identical to swcBulk */ - p[iOUT(i, pd)] = SW_SWCbulk2SWPmatric( - SW_Site.lyr[i]->fractionVolBulk_gravel, vo->swpMatric[i], i); + p[iOUT(i, pd)] = SW_SWRC_SWCtoSWP(vo->swpMatric[i], SW_Site.lyr[i]); } } @@ -1027,8 +1030,7 @@ void get_swpMatric_agg(OutPeriod pd) ForEachSoilLayer(i) { /* swpMatric at this point is identical to swcBulk */ - val = SW_SWCbulk2SWPmatric( - SW_Site.lyr[i]->fractionVolBulk_gravel, vo->swpMatric[i], i); + val = SW_SWRC_SWCtoSWP(vo->swpMatric[i], SW_Site.lyr[i]); do_running_agg(p, psd, iOUT(i, pd), Globals->currIter, val); } @@ -1058,7 +1060,7 @@ void get_swaBulk_text(OutPeriod pd) ForEachSoilLayer(i) { - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, vo->swaBulk[i]); + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, vo->swaBulk[i]); strcat(sw_outstr, str); } } @@ -1135,7 +1137,7 @@ void get_swaMatric_text(OutPeriod pd) /* swaMatric at this point is identical to swaBulk */ convert = 1. / (1. - SW_Site.lyr[i]->fractionVolBulk_gravel); - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, vo->swaMatric[i] * convert); + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, vo->swaMatric[i] * convert); strcat(sw_outstr, str); } } @@ -1211,7 +1213,7 @@ void get_surfaceWater_text(OutPeriod pd) SW_SOILWAT_OUTPUTS *vo = SW_Soilwat.p_oagg[pd]; sw_outstr[0] = '\0'; - sprintf(sw_outstr, "%c%.*f", _Sep, OUT_DIGITS, vo->surfaceWater); + snprintf(sw_outstr, sizeof sw_outstr,"%c%.*f", _Sep, OUT_DIGITS, vo->surfaceWater); } #endif @@ -1273,7 +1275,7 @@ void get_runoffrunon_text(OutPeriod pd) net = vo->surfaceRunoff + vo->snowRunoff - vo->surfaceRunon; sw_outstr[0] = '\0'; - sprintf(sw_outstr, "%c%.*f%c%.*f%c%.*f%c%.*f", + snprintf(sw_outstr, sizeof sw_outstr,"%c%.*f%c%.*f%c%.*f%c%.*f", _Sep, OUT_DIGITS, net, _Sep, OUT_DIGITS, vo->surfaceRunoff, _Sep, OUT_DIGITS, vo->snowRunoff, @@ -1354,7 +1356,7 @@ void get_transp_text(OutPeriod pd) /* total transpiration */ ForEachSoilLayer(i) { - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, vo->transp_total[i]); + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, vo->transp_total[i]); strcat(sw_outstr, str); } @@ -1363,7 +1365,7 @@ void get_transp_text(OutPeriod pd) { ForEachSoilLayer(i) { - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, vo->transp[k][i]); + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, vo->transp[k][i]); strcat(sw_outstr, str); } } @@ -1496,7 +1498,7 @@ void get_evapSoil_text(OutPeriod pd) ForEachEvapLayer(i) { - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, vo->evap[i]); + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, vo->evap[i]); strcat(sw_outstr, str); } } @@ -1568,14 +1570,14 @@ void get_evapSurface_text(OutPeriod pd) char str[OUTSTRLEN]; sw_outstr[0] = '\0'; - sprintf(sw_outstr, "%c%.*f", _Sep, OUT_DIGITS, vo->total_evap); + snprintf(sw_outstr, sizeof sw_outstr,"%c%.*f", _Sep, OUT_DIGITS, vo->total_evap); ForEachVegType(k) { - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, vo->evap_veg[k]); + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, vo->evap_veg[k]); strcat(sw_outstr, str); } - sprintf(str, "%c%.*f%c%.*f", + snprintf(str, OUTSTRLEN, "%c%.*f%c%.*f", _Sep, OUT_DIGITS, vo->litter_evap, _Sep, OUT_DIGITS, vo->surfaceWater_evap); strcat(sw_outstr, str); @@ -1658,14 +1660,14 @@ void get_interception_text(OutPeriod pd) char str[OUTSTRLEN]; sw_outstr[0] = '\0'; - sprintf(sw_outstr, "%c%.*f", _Sep, OUT_DIGITS, vo->total_int); + snprintf(sw_outstr, sizeof sw_outstr,"%c%.*f", _Sep, OUT_DIGITS, vo->total_int); ForEachVegType(k) { - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, vo->int_veg[k]); + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, vo->int_veg[k]); strcat(sw_outstr, str); } - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, vo->litter_int); + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, vo->litter_int); strcat(sw_outstr, str); } #endif @@ -1742,7 +1744,7 @@ void get_soilinf_text(OutPeriod pd) SW_WEATHER_OUTPUTS *vo = SW_Weather.p_oagg[pd]; sw_outstr[0] = '\0'; - sprintf(sw_outstr, "%c%.*f", _Sep, OUT_DIGITS, vo->soil_inf); + snprintf(sw_outstr, sizeof sw_outstr,"%c%.*f", _Sep, OUT_DIGITS, vo->soil_inf); } #endif @@ -1807,7 +1809,7 @@ void get_lyrdrain_text(OutPeriod pd) for (i = 0; i < SW_Site.n_layers - 1; i++) { - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, vo->lyrdrain[i]); + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, vo->lyrdrain[i]); strcat(sw_outstr, str); } } @@ -1884,7 +1886,7 @@ void get_hydred_text(OutPeriod pd) /* total hydraulic redistribution */ ForEachSoilLayer(i) { - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, vo->hydred_total[i]); + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, vo->hydred_total[i]); strcat(sw_outstr, str); } @@ -1893,7 +1895,7 @@ void get_hydred_text(OutPeriod pd) { ForEachSoilLayer(i) { - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, vo->hydred[k][i]); + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, vo->hydred[k][i]); strcat(sw_outstr, str); } } @@ -1992,8 +1994,9 @@ void get_aet_text(OutPeriod pd) SW_WEATHER_OUTPUTS *vo2 = SW_Weather.p_oagg[pd]; sw_outstr[0] = '\0'; - sprintf( + snprintf( sw_outstr, + sizeof sw_outstr, "%c%.*f%c%.*f%c%.*f%c%.*f%c%.*f%c%.*f", _Sep, OUT_DIGITS, vo->aet, _Sep, OUT_DIGITS, vo->tran, @@ -2089,8 +2092,9 @@ void get_pet_text(OutPeriod pd) SW_SOILWAT_OUTPUTS *vo = SW_Soilwat.p_oagg[pd]; sw_outstr[0] = '\0'; - sprintf( + snprintf( sw_outstr, + sizeof sw_outstr, "%c%.*f%c%.*f%c%.*f%c%.*f%c%.*f", _Sep, OUT_DIGITS, vo->pet, _Sep, OUT_DIGITS, vo->H_oh, @@ -2169,7 +2173,7 @@ void get_wetdays_text(OutPeriod pd) if (pd == eSW_Day) { ForEachSoilLayer(i) { - sprintf(str, "%c%i", _Sep, (SW_Soilwat.is_wet[i]) ? 1 : 0); + snprintf(str, OUTSTRLEN, "%c%i", _Sep, (SW_Soilwat.is_wet[i]) ? 1 : 0); strcat(sw_outstr, str); } @@ -2178,7 +2182,7 @@ void get_wetdays_text(OutPeriod pd) SW_SOILWAT_OUTPUTS *vo = SW_Soilwat.p_oagg[pd]; ForEachSoilLayer(i) { - sprintf(str, "%c%i", _Sep, (int) vo->wetdays[i]); + snprintf(str, OUTSTRLEN, "%c%i", _Sep, (int) vo->wetdays[i]); strcat(sw_outstr, str); } } @@ -2267,7 +2271,7 @@ void get_snowpack_text(OutPeriod pd) SW_SOILWAT_OUTPUTS *vo = SW_Soilwat.p_oagg[pd]; sw_outstr[0] = '\0'; - sprintf(sw_outstr, "%c%.*f%c%.*f", + snprintf(sw_outstr, sizeof sw_outstr,"%c%.*f%c%.*f", _Sep, OUT_DIGITS, vo->snowpack, _Sep, OUT_DIGITS, vo->snowdepth); } @@ -2330,7 +2334,7 @@ void get_deepswc_text(OutPeriod pd) SW_SOILWAT_OUTPUTS *vo = SW_Soilwat.p_oagg[pd]; sw_outstr[0] = '\0'; - sprintf(sw_outstr, "%c%.*f", _Sep, OUT_DIGITS, vo->deep); + snprintf(sw_outstr, sizeof sw_outstr,"%c%.*f", _Sep, OUT_DIGITS, vo->deep); } #endif @@ -2391,16 +2395,16 @@ void get_soiltemp_text(OutPeriod pd) char str[OUTSTRLEN]; sw_outstr[0] = '\0'; - + ForEachSoilLayer(i) { - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, vo->maxLyrTemperature[i]); + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, vo->maxLyrTemperature[i]); strcat(sw_outstr, str); - - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, vo->minLyrTemperature[i]); + + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, vo->minLyrTemperature[i]); strcat(sw_outstr, str); - - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, vo->avgLyrTemp[i]); + + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, vo->avgLyrTemp[i]); strcat(sw_outstr, str); } } @@ -2420,7 +2424,7 @@ void get_soiltemp_mem(OutPeriod pd) RealD *p = p_OUT[eSW_SoilTemp][pd]; get_outvalleader(p, pd); - + ForEachSoilLayer(i) { p[iOUT((i * 3), pd)] = vo->maxLyrTemperature[i]; @@ -2444,14 +2448,14 @@ void get_soiltemp_agg(OutPeriod pd) RealD *p = p_OUT[eSW_SoilTemp][pd], *psd = p_OUTsd[eSW_SoilTemp][pd]; - + ForEachSoilLayer(i) { do_running_agg(p, psd, iOUT((i * 3), pd), Globals->currIter, vo->maxLyrTemperature[i]); do_running_agg(p, psd, iOUT((i * 3) + 1, pd), Globals->currIter, vo->minLyrTemperature[i]); do_running_agg(p, psd, iOUT((i * 3) + 2, pd), Globals->currIter, vo->avgLyrTemp[i]); } - + if (print_IterationSummary) { sw_outstr_agg[0] = '\0'; format_IterationSummary(p, psd, pd, ncol_OUT[eSW_SoilTemp]); @@ -2477,7 +2481,7 @@ void get_frozen_text(OutPeriod pd) ForEachSoilLayer(i) { - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, vo->lyrFrozen[i]); + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, vo->lyrFrozen[i]); strcat(sw_outstr, str); } } diff --git a/SW_Output_mock.c b/src/SW_Output_mock.c similarity index 84% rename from SW_Output_mock.c rename to src/SW_Output_mock.c index b6264c57f..d5c3e623f 100644 --- a/SW_Output_mock.c +++ b/src/SW_Output_mock.c @@ -17,22 +17,22 @@ #include #include #include -#include "generic.h" // externs `QuietMode`, `EchoInits` -#include "filefuncs.h" // externs `_firstfile`, `inbuf` -#include "myMemory.h" -#include "Times.h" - -#include "SW_Carbon.h" // externs SW_Carbon -#include "SW_Defines.h" -#include "SW_Files.h" -#include "SW_Model.h" // externs SW_Model -#include "SW_Site.h" // externs SW_Site -#include "SW_SoilWater.h" // externs SW_Soilwat -#include "SW_Times.h" -#include "SW_Output.h" -#include "SW_Weather.h" // externs SW_Weather -#include "SW_VegEstab.h" // externs SW_VegEstab -#include "SW_VegProd.h" // externs SW_VegProd +#include "include/generic.h" // externs `QuietMode`, `EchoInits` +#include "include/filefuncs.h" // externs `_firstfile`, `inbuf` +#include "include/myMemory.h" +#include "include/Times.h" + +#include "include/SW_Carbon.h" // externs SW_Carbon +#include "include/SW_Defines.h" +#include "include/SW_Files.h" +#include "include/SW_Model.h" // externs SW_Model +#include "include/SW_Site.h" // externs SW_Site +#include "include/SW_SoilWater.h" // externs SW_Soilwat +#include "include/SW_Times.h" +#include "include/SW_Output.h" +#include "include/SW_Weather.h" // externs SW_Weather +#include "include/SW_VegEstab.h" // externs SW_VegEstab +#include "include/SW_VegProd.h" // externs SW_VegProd diff --git a/SW_Output_outarray.c b/src/SW_Output_outarray.c similarity index 90% rename from SW_Output_outarray.c rename to src/SW_Output_outarray.c index 14d76d23d..c39ba5d3d 100644 --- a/SW_Output_outarray.c +++ b/src/SW_Output_outarray.c @@ -21,21 +21,21 @@ #include #include #include -#include "generic.h" -#include "filefuncs.h" -#include "myMemory.h" -#include "Times.h" +#include "include/generic.h" +#include "include/filefuncs.h" +#include "include/myMemory.h" +#include "include/Times.h" -#include "SW_Defines.h" -#include "SW_Model.h" // externs SW_Model +#include "include/SW_Defines.h" +#include "include/SW_Model.h" // externs SW_Model // externs `SW_Output`, `tOffset` `use_OutPeriod`, `used_OUTNPERIODS`, // `timeSteps`, `ncol_OUT` -#include "SW_Output.h" -#include "SW_Output_outarray.h" +#include "include/SW_Output.h" +#include "include/SW_Output_outarray.h" #ifdef STEPWAT -#include "../ST_defines.h" +#include "ST_defines.h" #endif @@ -135,9 +135,11 @@ void SW_OUT_set_nrow(void) #ifdef SWDEBUG if (debug) { - swprintf("n(year) = %ld, n(month) = %ld, n(week) = %ld, n(day) = %ld\n", + swprintf( + "n(year) = %zu, n(month) = %zu, n(week) = %zu, n(day) = %zu\n", nrow_OUT[eSW_Year], nrow_OUT[eSW_Month], nrow_OUT[eSW_Week], - nrow_OUT[eSW_Day]); + nrow_OUT[eSW_Day] + ); } #endif } diff --git a/SW_Output_outtext.c b/src/SW_Output_outtext.c similarity index 84% rename from SW_Output_outtext.c rename to src/SW_Output_outtext.c index 83e6d9411..a7f729ade 100644 --- a/SW_Output_outtext.c +++ b/src/SW_Output_outtext.c @@ -22,21 +22,21 @@ #include #include #include -#include "generic.h" -#include "filefuncs.h" -#include "myMemory.h" -#include "Times.h" +#include "include/generic.h" +#include "include/filefuncs.h" +#include "include/myMemory.h" +#include "include/Times.h" -#include "SW_Defines.h" -#include "SW_Files.h" -#include "SW_Model.h" // externs SW_Model -#include "SW_Site.h" // externs SW_Site +#include "include/SW_Defines.h" +#include "include/SW_Files.h" +#include "include/SW_Model.h" // externs SW_Model +#include "include/SW_Site.h" // externs SW_Site // externs `SW_Output`, `_Sep`, `tOffset`, `use_OutPeriod`, `used_OUTNPERIODS`, // `timeSteps`, `colnames_OUT`, `ncol_OUT`, `key2str`, `pd2longstr`, // `prepare_IterationSummary`, `storeAllIterations` -#include "SW_Output.h" -#include "SW_Output_outtext.h" +#include "include/SW_Output.h" +#include "include/SW_Output_outtext.h" @@ -118,11 +118,15 @@ static void _create_csv_headers(OutPeriod pd, char *str_reg, char *str_soil, Boo for (i = 0; i < ncol_OUT[k]; i++) { if (does_agg) { - sprintf(str_help1, "%c%s_%s_Mean%c%s_%s_SD", - _Sep, key, colnames_OUT[k][i], _Sep, key, colnames_OUT[k][i]); + snprintf( + str_help1, + sizeof str_help1, + "%c%s_%s_Mean%c%s_%s_SD", + _Sep, key, colnames_OUT[k][i], _Sep, key, colnames_OUT[k][i] + ); strcat(str_help2, str_help1); } else { - sprintf(str_help1, "%c%s_%s", _Sep, key, colnames_OUT[k][i]); + snprintf(str_help1, sizeof str_help1, "%c%s_%s", _Sep, key, colnames_OUT[k][i]); strcat(str_help2, str_help1); } } @@ -162,22 +166,22 @@ static void _create_csv_files(OutPeriod pd) #endif -static void get_outstrheader(OutPeriod pd, char *str) { +static void get_outstrheader(OutPeriod pd, char *str, size_t sizeof_str) { switch (pd) { case eSW_Day: - sprintf(str, "%s%c%s", "Year", _Sep, pd2longstr[eSW_Day]); + snprintf(str, sizeof_str, "%s%c%s", "Year", _Sep, pd2longstr[eSW_Day]); break; case eSW_Week: - sprintf(str, "%s%c%s", "Year", _Sep, pd2longstr[eSW_Week]); + snprintf(str, sizeof_str, "%s%c%s", "Year", _Sep, pd2longstr[eSW_Week]); break; case eSW_Month: - sprintf(str, "%s%c%s", "Year", _Sep, pd2longstr[eSW_Month]); + snprintf(str, sizeof_str, "%s%c%s", "Year", _Sep, pd2longstr[eSW_Month]); break; case eSW_Year: - sprintf(str, "%s", "Year"); + snprintf(str, sizeof_str, "%s", "Year"); break; } } @@ -190,7 +194,7 @@ static void get_outstrheader(OutPeriod pd, char *str) { \return `name_flagiteration.ext` */ -static void _create_filename_ST(char *str, char *flag, int iteration, char *filename) { +static void _create_filename_ST(char *str, char *flag, int iteration, char *filename, size_t sizeof_filename) { char *basename; char *ext; char *fileDup = (char *)malloc(strlen(str) + 1); @@ -202,9 +206,9 @@ static void _create_filename_ST(char *str, char *flag, int iteration, char *file // Put new file together if (iteration > 0) { - sprintf(filename, "%s_%s%d.%s", basename, flag, iteration, ext); + snprintf(filename, sizeof_filename, "%s_%s%d.%s", basename, flag, iteration, ext); } else { - sprintf(filename, "%s_%s.%s", basename, flag, ext); + snprintf(filename, sizeof_filename, "%s_%s.%s", basename, flag, ext); } free(fileDup); @@ -236,12 +240,12 @@ static void _create_csv_file_ST(int iteration, OutPeriod pd) // a specific order of `SW_FileIndex` --> fix and create something that // allows subsetting such as `eOutputFile[pd]` or append time period to // a basename, etc. - _create_filename_ST(SW_F_name(eOutputDaily + pd), "agg", 0, filename); + _create_filename_ST(SW_F_name(eOutputDaily + pd), "agg", 0, filename, FILENAME_MAX); SW_OutFiles.fp_reg_agg[pd] = OpenFile(filename, "w"); } if (SW_OutFiles.make_soil[pd]) { - _create_filename_ST(SW_F_name(eOutputDaily_soil + pd), "agg", 0, filename); + _create_filename_ST(SW_F_name(eOutputDaily_soil + pd), "agg", 0, filename, FILENAME_MAX); SW_OutFiles.fp_soil_agg[pd] = OpenFile(filename, "w"); } @@ -258,12 +262,12 @@ static void _create_csv_file_ST(int iteration, OutPeriod pd) } if (SW_OutFiles.make_regular[pd]) { - _create_filename_ST(SW_F_name(eOutputDaily + pd), "rep", iteration, filename); + _create_filename_ST(SW_F_name(eOutputDaily + pd), "rep", iteration, filename, FILENAME_MAX); SW_OutFiles.fp_reg[pd] = OpenFile(filename, "w"); } if (SW_OutFiles.make_soil[pd]) { - _create_filename_ST(SW_F_name(eOutputDaily_soil + pd), "rep", iteration, filename); + _create_filename_ST(SW_F_name(eOutputDaily_soil + pd), "rep", iteration, filename, FILENAME_MAX); SW_OutFiles.fp_soil[pd] = OpenFile(filename, "w"); } } @@ -336,24 +340,24 @@ void SW_OUT_create_iteration_files(int iteration) { Also, see note on test value in _write_today() for explanation of the +1. */ -void get_outstrleader(OutPeriod pd, char *str) { +void get_outstrleader(OutPeriod pd, char *str, size_t sizeof_str) { switch (pd) { case eSW_Day: - sprintf(str, "%d%c%d", SW_Model.simyear, _Sep, SW_Model.doy); + snprintf(str, sizeof_str, "%d%c%d", SW_Model.simyear, _Sep, SW_Model.doy); break; case eSW_Week: - sprintf(str, "%d%c%d", SW_Model.simyear, _Sep, + snprintf(str, sizeof_str, "%d%c%d", SW_Model.simyear, _Sep, (SW_Model.week + 1) - tOffset); break; case eSW_Month: - sprintf(str, "%d%c%d", SW_Model.simyear, _Sep, + snprintf(str, sizeof_str, "%d%c%d", SW_Model.simyear, _Sep, (SW_Model.month + 1) - tOffset); break; case eSW_Year: - sprintf(str, "%d", SW_Model.simyear); + snprintf(str, sizeof_str, "%d", SW_Model.simyear); break; } } @@ -386,7 +390,7 @@ void write_headers_to_csv(OutPeriod pd, FILE *fp_reg, FILE *fp_soil, Bool does_a header_soil[SW_Site.n_layers * OUTSTRLEN]; // Acquire headers - get_outstrheader(pd, str_time); + get_outstrheader(pd, str_time, sizeof str_time); _create_csv_headers(pd, header_reg, header_soil, does_agg); // Write headers to files diff --git a/SW_Site.c b/src/SW_Site.c similarity index 50% rename from SW_Site.c rename to src/SW_Site.c index 5e99509c8..a5763bcbc 100644 --- a/SW_Site.c +++ b/src/SW_Site.c @@ -58,17 +58,17 @@ #include #include -#include "generic.h" // externs `QuietMode`, `EchoInits` -#include "filefuncs.h" // externs `_firstfile`, `inbuf` -#include "myMemory.h" -#include "SW_Defines.h" +#include "include/generic.h" // externs `QuietMode`, `EchoInits` +#include "include/filefuncs.h" // externs `_firstfile`, `inbuf` +#include "include/myMemory.h" +#include "include/SW_Defines.h" -#include "SW_Carbon.h" // externs SW_Carbon -#include "SW_Files.h" -#include "SW_Site.h" // externs SW_Site -#include "SW_SoilWater.h" +#include "include/SW_Carbon.h" // externs SW_Carbon +#include "include/SW_Files.h" +#include "include/SW_Site.h" // externs SW_Site +#include "include/SW_SoilWater.h" -#include "SW_VegProd.h" // externs SW_VegProd, key2veg +#include "include/SW_VegProd.h" // externs SW_VegProd, key2veg @@ -91,6 +91,32 @@ RealD _SWCMinVal; /* lower bound on swc. */ +/** Character representation of implemented Soil Water Retention Curves (SWRC) + + @note Code maintenance: + - Values must exactly match those provided in `siteparam.in`. + - Order must exactly match "indices of `swrc2str`" + - See details in section \ref swrc_ptf +*/ +char const *swrc2str[N_SWRCs] = { + "Campbell1974", + "vanGenuchten1980", + "FXW" +}; + +/** Character representation of implemented Pedotransfer Functions (PTF) + + @note Code maintenance: + - Values must exactly match those provided in `siteparam.in`. + - Order must exactly match "indices of `ptf2str`" + - See details in section \ref swrc_ptf + - `rSOILWAT2` may implemented additional PTFs +*/ +char const *ptf2str[N_PTFs] = { + "Cosby1984AndOthers", + "Cosby1984" +}; + /* =================================================== */ /* Local Variables */ @@ -102,186 +128,887 @@ static char *MyFileName; /* Local Function Definitions */ /* --------------------------------------------------- */ -static void _read_layers(void) { - /* =================================================== */ - /* 5-Feb-2002 (cwb) removed dmin requirement in input file */ +/** Check validity of soil properties - SW_SITE *v = &SW_Site; - FILE *f; - LyrIndex lyrno; - int x, k; - RealF dmin = 0.0, dmax, evco, trco_veg[NVEGTYPES], psand, pclay, matricd, imperm, - soiltemp, f_gravel; + @param[in] *lyr Soil information. - /* note that Files.read() must be called prior to this. */ - MyFileName = SW_F_name(eLayers); + @return A logical value indicating if soil properties passed the checks. +*/ +static Bool SW_check_soil_properties(SW_LAYER_INFO *lyr) { + int k; + RealD fval = 0; + const char *errtype = "\0"; + Bool res = swTRUE; - f = OpenFile(MyFileName, "r"); - while (GetALine(f, inbuf)) { - lyrno = _newlayer(); + if (LE(lyr->width, 0.)) { + res = swFALSE; + fval = lyr->width; + errtype = Str_Dup("layer width"); - x = sscanf( - inbuf, - "%f %f %f %f %f %f %f %f %f %f %f %f", - &dmax, - &matricd, - &f_gravel, - &evco, - &trco_veg[SW_GRASS], &trco_veg[SW_SHRUB], &trco_veg[SW_TREES], &trco_veg[SW_FORBS], - &psand, - &pclay, - &imperm, - &soiltemp + } else if ( + LT(lyr->soilDensityInput, 0.) || + GT(lyr->soilDensityInput, 2.65) + ) { + res = swFALSE; + fval = lyr->soilDensityInput; + errtype = Str_Dup("soil density"); + + } else if ( + LT(lyr->fractionVolBulk_gravel, 0.) || + GE(lyr->fractionVolBulk_gravel, 1.) + ) { + res = swFALSE; + fval = lyr->fractionVolBulk_gravel; + errtype = Str_Dup("gravel content"); + + } else if ( + LE(lyr->fractionWeightMatric_sand, 0.) || + GE(lyr->fractionWeightMatric_sand, 1.) + ) { + res = swFALSE; + fval = lyr->fractionWeightMatric_sand; + errtype = Str_Dup("sand proportion"); + + } else if ( + LE(lyr->fractionWeightMatric_clay, 0.) || + GE(lyr->fractionWeightMatric_clay, 1.) + ) { + res = swFALSE; + fval = lyr->fractionWeightMatric_clay; + errtype = Str_Dup("clay proportion"); + + } else if ( + GE(lyr->fractionWeightMatric_sand + lyr->fractionWeightMatric_clay, 1.) + ) { + res = swFALSE; + fval = lyr->fractionWeightMatric_sand + lyr->fractionWeightMatric_clay; + errtype = Str_Dup("sand+clay proportion"); + + } else if ( + LT(lyr->impermeability, 0.) || + GT(lyr->impermeability, 1.) + ) { + res = swFALSE; + fval = lyr->impermeability; + errtype = Str_Dup("impermeability"); + + } else if ( + LT(lyr->evap_coeff, 0.) || + GT(lyr->evap_coeff, 1.) + ) { + res = swFALSE; + fval = lyr->evap_coeff; + errtype = Str_Dup("bare-soil evaporation coefficient"); + + } else { + ForEachVegType(k) { + if (LT(lyr->transp_coeff[k], 0.) || GT(lyr->transp_coeff[k], 1.)) { + res = swFALSE; + fval = lyr->transp_coeff[k]; + errtype = Str_Dup("transpiration coefficient"); + break; + } + } + } + + if (!res) { + LogError( + logfp, + LOGFATAL, + "'%s' has an invalid value (%5.4f) in layer %d.\n", + errtype, fval, lyr->id + 1 ); + } - /* Check that we have 12 values per layer */ - /* Adjust number if new variables are added */ - if (x != 12) { - CloseFile(&f); + return res; +} + + + + +/** A realistic lower limit for minimum `theta` + +@note Currently, -30 MPa + (based on limited test runs across western US including hot deserts) + lower than "air-dry" = hygroscopic point (-10. MPa; Porporato et al. 2001) + not as extreme as "oven-dry" (-1000. MPa; Fredlund et al. 1994) +*/ +static double lower_limit_of_theta_min( + unsigned int swrc_type, + double *swrcp, + double gravel, + double width +) { + double res = SWRC_SWPtoSWC(300., swrc_type, swrcp, gravel, width, LOGFATAL); + + // convert bulk [cm] to matric [cm / cm] + return res / ((1. - gravel) * width); +} + +/** + @brief User-input based minimum volumetric water content + + This function returns minimum soil water content `theta_min` determined + by user input via #_SWCMinVal as + * a fixed VWC value, + * a fixed SWP value, or + * a realistic lower limit from `lower_limit_of_theta_min()`, + unless legacy mode (`ptf` equal to "Cosby1984AndOthers") is used + (reproducing behavior prior to v7.0.0), then calculated as + maximum of `lower_limit_of_theta_min()` and `PTF_RawlsBrakensiek1985()` + with pedotransfer function by Rawls & Brakensiek 1985 \cite rawls1985WmitE + (independently of the selected SWRC). + + @param[in] ui_sm_min User input of requested minimum soil moisture, + see #_SWCMinVal + @param[in] gravel Coarse fragments (> 2 mm; e.g., gravel) + of the whole soil [m3/m3] + @param[in] width Soil layer width [cm] + @param[in] sand Sand content of the matric soil (< 2 mm fraction) [g/g] + @param[in] clay Clay content of the matric soil (< 2 mm fraction) [g/g] + @param[in] swcBulk_sat Saturated water content of the bulk soil [cm] + @param[in] swrc_type Identification number of selected SWRC + @param[in] *swrcp Vector of SWRC parameters + @param[in] legacy_mode If true then legacy behavior (see details) + + @return Minimum volumetric water content of the matric soil [cm / cm] +*/ +static double ui_theta_min( + double ui_sm_min, + double gravel, + double width, + double sand, + double clay, + double swcBulk_sat, + unsigned int swrc_type, + double *swrcp, + Bool legacy_mode +) { + double vwc_min = SW_MISSING, tmp_vwcmin; + + if (LT(ui_sm_min, 0.0)) { + /* user input: request to estimate minimum theta */ + + /* realistic lower limit for theta_min */ + vwc_min = lower_limit_of_theta_min(swrc_type, swrcp, gravel, width); + + if (legacy_mode) { + /* residual theta estimated with Rawls & Brakensiek (1985) PTF */ + PTF_RawlsBrakensiek1985( + &tmp_vwcmin, + sand, + clay, + swcBulk_sat / ((1. - gravel) * width) + ); + + /* if `PTF_RawlsBrakensiek1985()` was successful, then take max */ + if (!missing(tmp_vwcmin)) { + vwc_min = fmax(vwc_min, tmp_vwcmin); + } + } + + } else if (GE(_SWCMinVal, 1.0)) { + /* user input: fixed (matric) SWP value; unit(_SWCMinVal) == -bar */ + vwc_min = SWRC_SWPtoSWC( + _SWCMinVal, + swrc_type, + swrcp, + gravel, + width, + LOGFATAL + ) / ((1. - gravel) * width); + + } else { + /* user input: fixed matric VWC; unit(_SWCMinVal) == cm/cm */ + vwc_min = _SWCMinVal / width; + } + + return vwc_min; +} + + + +/* =================================================== */ +/* Global Function Definitions */ +/* --------------------------------------------------- */ + + +/** + @brief Translate a SWRC name into a SWRC type number + + See #swrc2str and `siteparam.in`. + Throws an error if `SWRC` is not implemented. + + @param[in] *swrc_name Name of a SWRC + + @return Internal identification number of selected SWRC +*/ +unsigned int encode_str2swrc(char *swrc_name) { + unsigned int k; + + for ( + k = 0; + k < N_SWRCs && Str_CompareI(swrc_name, (char *)swrc2str[k]); + k++ + ); + + if (k == N_SWRCs) { + LogError( + logfp, + LOGFATAL, + "SWRC '%s' is not implemented.", + swrc_name + ); + } + + return k; +} + +/** + @brief Translate a PTF name into a PTF type number + + See #ptf2str and `siteparam.in`. + + @param[in] *ptf_name Name of a PTF + + @return Internal identification number of selected PTF; + #SW_MISSING if not implemented. +*/ +unsigned int encode_str2ptf(char *ptf_name) { + unsigned int k; + + for ( + k = 0; + k < N_PTFs && Str_CompareI(ptf_name, (char *)ptf2str[k]); + k++ + ); + + if (k == N_PTFs) { + k = (unsigned int) SW_MISSING; + } + + return k; +} + + + +/** + @brief Estimate parameters of selected soil water retention curve (SWRC) + using selected pedotransfer function (PTF) + + See #ptf2str for implemented PTFs. + + @param[in] ptf_type Identification number of selected PTF + @param[out] *swrcp Vector of SWRC parameters to be estimated + @param[in] sand Sand content of the matric soil (< 2 mm fraction) [g/g] + @param[in] clay Clay content of the matric soil (< 2 mm fraction) [g/g] + @param[in] gravel Coarse fragments (> 2 mm; e.g., gravel) + of the whole soil [m3/m3] + @param[in] bdensity Density of the whole soil + (matric soil plus coarse fragments) [g/cm3]; + accepts #SW_MISSING if not used by selected PTF + +*/ +void SWRC_PTF_estimate_parameters( + unsigned int ptf_type, + double *swrcp, + double sand, + double clay, + double gravel, + double bdensity +) { + + /* Initialize swrcp[] to 0 */ + memset(swrcp, 0., SWRC_PARAM_NMAX * sizeof(swrcp[0])); + + if (ptf_type == sw_Cosby1984AndOthers) { + SWRC_PTF_Cosby1984_for_Campbell1974(swrcp, sand, clay); + + } else if (ptf_type == sw_Cosby1984) { + SWRC_PTF_Cosby1984_for_Campbell1974(swrcp, sand, clay); + + } else { + LogError( + logfp, + LOGFATAL, + "PTF is not implemented in SOILWAT2.", + ptf_type + ); + } + + /**********************************/ + /* TODO: remove once PTFs are implemented that utilize gravel */ + /* avoiding `error: unused parameter 'gravel' [-Werror=unused-parameter]` */ + if (gravel < 0.) {}; + + /* TODO: remove once PTFs are implemented that utilize bdensity */ + /* avoiding `error: unused parameter 'gravel' [-Werror=unused-parameter]` */ + if (bdensity < 0.) {}; + /**********************************/ +} + + +/** + @brief Estimate Campbell's 1974 SWRC parameters + using Cosby et al. 1984 multivariate PTF + + Estimation of four SWRC parameter values `swrcp` + based on sand, clay, and (silt). + + Parameters are explained in `SWRC_check_parameters_for_Campbell1974()`. + + Multivariate PTFs are from Cosby et al. 1984 (\cite Cosby1984) Table 4; + Cosby et al. 1984 provided also univariate PTFs in Table 5 + but they are not used here. + + See `SWRC_SWCtoSWP_Campbell1974()` and `SWRC_SWPtoSWC_Campbell1974()` + for implementation of Campbell's 1974 SWRC (\cite Campbell1974). + + @param[out] *swrcp Vector of SWRC parameters to be estimated + @param[in] sand Sand content of the matric soil (< 2 mm fraction) [g/g] + @param[in] clay Clay content of the matric soil (< 2 mm fraction) [g/g] +*/ +void SWRC_PTF_Cosby1984_for_Campbell1974( + double *swrcp, + double sand, double clay +) { + /* Table 4 */ + /* Original equations formulated with percent sand (%) and clay (%), + here re-formulated for fraction of sand and clay */ + + /* swrcp[0] = psi_saturated: originally formulated as function of silt + here re-formulated as function of clay */ + swrcp[0] = powe(10.0, -1.58 * sand - 0.63 * clay + 2.17); + /* swrcp[1] = theta_saturated: originally with units [100 * cm / cm] + here re-formulated with units [cm / cm] */ + swrcp[1] = -0.142 * sand - 0.037 * clay + 0.505; + /* swrcp[2] = b */ + swrcp[2] = -0.3 * sand + 15.7 * clay + 3.10; + /* swrcp[3] = K_saturated: originally with units [inches / day] + here re-formulated with units [cm / day] */ + swrcp[3] = 2.54 * 24. * powe(10.0, 1.26 * sand - 6.4 * clay - 0.60); +} + + +/** + @brief Saturated soil water content + + See #ptf2str for implemented PTFs. + See #swrc2str for implemented SWRCs. + + Saturated volumetric water content is usually estimated as one of the + SWRC parameters; this is what this function returns. + + For historical reasons, if `swrc_name` is "Campbell1974", then a + `ptf_name` of "Cosby1984AndOthers" will reproduce `SOILWAT2` legacy mode + (`SOILWAT2` prior to v7.0.0) and return saturated soil water content estimated + by Saxton et al. 2006 (\cite Saxton2006) PTF instead; + `ptf_name` of "Cosby1984" will return saturated soil water content estimated + by Cosby et al. 1984 (\cite Cosby1984) PTF. + + The arguments `ptf_type`, `sand`, and `clay` are utilized only if + `ptf_name` is "Cosby1984AndOthers" (see #ptf2str). + + @param[in] swrc_type Identification number of selected SWRC + @param[in] *swrcp Vector of SWRC parameters + @param[in] gravel Coarse fragments (> 2 mm; e.g., gravel) + of the whole soil [m3/m3] + @param[in] width Soil layer width [cm] + @param[in] ptf_type Identification number of selected PTF + @param[in] sand Sand content of the matric soil (< 2 mm fraction) [g/g] + @param[in] clay Clay content of the matric soil (< 2 mm fraction) [g/g] + + @return Estimated saturated water content of the bulk soil [cm] +*/ +double SW_swcBulk_saturated( + unsigned int swrc_type, + double *swrcp, + double gravel, + double width, + unsigned int ptf_type, + double sand, + double clay +) { + double theta_sat = SW_MISSING; + + switch (swrc_type) { + case sw_Campbell1974: + if (ptf_type == sw_Cosby1984AndOthers) { + // Cosby1984AndOthers (backwards compatible) + PTF_Saxton2006(&theta_sat, sand, clay); + } else { + theta_sat = swrcp[1]; + } + break; + + case sw_vanGenuchten1980: + theta_sat = swrcp[1]; + break; + + case sw_FXW: + theta_sat = swrcp[0]; + break; + + default: LogError( logfp, LOGFATAL, - "%s : Incomplete record %d.\n", - MyFileName, lyrno + 1 + "`SW_swcBulk_saturated()`: SWRC (type %d) is not implemented.", + swrc_type ); - } + break; + } - v->lyr[lyrno]->width = dmax - dmin; + // Convert from matric [cm/cm] to bulk [cm] + return theta_sat * width * (1. - gravel); +} - /* checks for valid values now carried out by `SW_SIT_init_run()` */ - dmin = dmax; - v->lyr[lyrno]->fractionVolBulk_gravel = f_gravel; - v->lyr[lyrno]->soilMatric_density = matricd; - v->lyr[lyrno]->evap_coeff = evco; +/** + @brief Lower limit of simulated soil water content + + See #ptf2str for implemented PTFs. + See #swrc2str for implemented SWRCs. + + SWRCs provide a theoretical minimum/residual volumetric water content; + however, water potential at that minimum value is infinite. Instead, we + use a realistic lower limit that does not crash the simulation. + + The lower limit is determined via `ui_theta_min()` using user input + and is strictly larger than the theoretical SWRC value. + + The arguments `sand`, `clay`, and `swcBulk_sat` + are utilized only in legacy mode, i.e., `ptf` equal to "Cosby1984AndOthers". + + @param[in] swrc_type Identification number of selected SWRC + @param[in] *swrcp Vector of SWRC parameters + @param[in] gravel Coarse fragments (> 2 mm; e.g., gravel) + of the whole soil [m3/m3] + @param[in] width Soil layer width [cm] + @param[in] ptf_type Identification number of selected PTF + @param[in] ui_sm_min User input of requested minimum soil moisture, + see #_SWCMinVal + @param[in] sand Sand content of the matric soil (< 2 mm fraction) [g/g] + @param[in] clay Clay content of the matric soil (< 2 mm fraction) [g/g] + @param[in] swcBulk_sat Saturated water content of the bulk soil [cm] + + @return Minimum water content of the bulk soil [cm] +*/ +double SW_swcBulk_minimum( + unsigned int swrc_type, + double *swrcp, + double gravel, + double width, + unsigned int ptf_type, + double ui_sm_min, + double sand, + double clay, + double swcBulk_sat +) { + double theta_min_sim, theta_min_theoretical = SW_MISSING; + + /* `theta_min` based on theoretical SWRC */ + switch (swrc_type) { + case sw_Campbell1974: // phi = infinity at theta_min + theta_min_theoretical = 0.; + break; - ForEachVegType(k) - { - v->lyr[lyrno]->transp_coeff[k] = trco_veg[k]; - } + case sw_vanGenuchten1980: // phi = infinity at theta_min + theta_min_theoretical = swrcp[0]; + break; - v->lyr[lyrno]->fractionWeightMatric_sand = psand; - v->lyr[lyrno]->fractionWeightMatric_clay = pclay; - v->lyr[lyrno]->impermeability = imperm; - v->lyr[lyrno]->avgLyrTemp = soiltemp; + case sw_FXW: // phi = 6.3 x 10^6 cm at theta_min + theta_min_theoretical = 0.; + break; - if (lyrno >= MAX_LAYERS) { - CloseFile(&f); + default: LogError( logfp, LOGFATAL, - "%s : Too many layers specified (%d).\n" - "Maximum number of layers is %d\n", - MyFileName, lyrno + 1, MAX_LAYERS + "`SW_swcBulk_minimum()`: SWRC (type %d) is not implemented.", + swrc_type ); + break; + } + + /* `theta_min` based on user input `ui_sm_min` */ + theta_min_sim = ui_theta_min( + ui_sm_min, + gravel, + width, + sand, + clay, + swcBulk_sat, + swrc_type, + swrcp, + // `(Bool) ptf_type == sw_Cosby1984AndOthers` doesn't work for unit test: + // error: "no known conversion from 'bool' to 'Bool'" + ptf_type == sw_Cosby1984AndOthers ? swTRUE : swFALSE + ); + + /* `theta_min_sim` must be strictly larger than `theta_min_theoretical` */ + theta_min_sim = fmax(theta_min_sim, theta_min_theoretical + D_DELTA); + + /* Convert from matric [cm/cm] to bulk [cm] */ + return theta_min_sim * width * (1. - gravel); +} + + + +/** + @brief Check whether selected PTF and SWRC are compatible + + See #ptf2str for implemented PTFs. + See #swrc2str for implemented SWRCs. + + @param[in] *swrc_name Name of selected SWRC + @param[in] *ptf_name Name of selected PTF + + @return A logical value indicating if SWRC and PTF are compatible. +*/ +Bool check_SWRC_vs_PTF(char *swrc_name, char *ptf_name) { + Bool res = swFALSE; + + if (!missing((double) encode_str2ptf(ptf_name))) { + if ( + Str_CompareI(swrc_name, (char *) "Campbell1974") == 0 && + ( + Str_CompareI(ptf_name, (char *) "Cosby1984AndOthers") == 0 || + Str_CompareI(ptf_name, (char *) "Cosby1984") == 0 + ) + ) { + res = swTRUE; } } - CloseFile(&f); + return res; } +/** + @brief Check Soil Water Retention Curve (SWRC) parameters + + See #swrc2str for implemented SWRCs. + + @param[in] swrc_type Identification number of selected SWRC + @param[in] *swrcp Vector of SWRC parameters + + @return A logical value indicating if parameters passed the checks. +*/ +Bool SWRC_check_parameters(unsigned int swrc_type, double *swrcp) { + Bool res = swFALSE; + + switch (swrc_type) { + case sw_Campbell1974: + res = SWRC_check_parameters_for_Campbell1974(swrcp); + break; + + case sw_vanGenuchten1980: + res = SWRC_check_parameters_for_vanGenuchten1980(swrcp); + break; + + case sw_FXW: + res = SWRC_check_parameters_for_FXW(swrcp); + break; + + default: + LogError( + logfp, + LOGFATAL, + "Selected SWRC (type %d) is not implemented.", + swrc_type + ); + break; + } + + return res; +} + -/* =================================================== */ -/* Global Function Definitions */ -/* --------------------------------------------------- */ /** - \brief Calculate soil moisture characteristics for each layer. + @brief Check Campbell's 1974 SWRC parameters - Bulk refers to the whole soil, i.e., including the rock/gravel component, - whereas matric refers to the < 2 mm fraction. + See `SWRC_SWCtoSWP_Campbell1974()` and `SWRC_SWPtoSWC_Campbell1974()` + for implementation of Campbell's 1974 SWRC (\cite Campbell1974). - Saturated moisture content of the matric component (thetasMatric), saturation matric - potential (psisMatric), and the slope of the retention curve (bMatric) for each - layer are calculated using equations found in Cosby et al. (1984). \cite Cosby1984 - The saturated moisture content of the whole soil (bulk) for each layer (swcBulk_saturated) - is calculated using equations found in Saxton and Rawls (2006; Equations 2, 3 & 5). - \cite Saxton2006 + Campbell1974 has four parameters (three are used for the SWRC): + - `swrcp[0]` (`psisMatric`): air-entry suction [cm] + - `swrcp[1]` (previously named `thetasMatric`): + saturated volumetric water content for the matric component [cm/cm] + - `swrcp[2]` (`bMatric`): slope of the linear log-log retention curve [-] + - `swrcp[3]` (`K_sat`): saturated hydraulic conductivity `[cm / day]` - Return from the function is void. Calculated values stored in SW_Site object. + @param[in] *swrcp Vector of SWRC parameters - SOILWAT2 calculates internally based on soil bulk density of the whole soil, - i.e., including rock/gravel component. However, inputs are expected to - represent soil (matric) density of the < 2 mm fraction. + @return A logical value indicating if parameters passed the checks. +*/ +Bool SWRC_check_parameters_for_Campbell1974(double *swrcp) { + Bool res = swTRUE; - sand + clay + silt must equal one. Fraction silt is calculated: 1 - (sand + clay). + if (LE(swrcp[0], 0.0)) { + res = swFALSE; + LogError( + logfp, + LOGWARN, + "SWRC_check_parameters_for_Campbell1974(): invalid value of " + "psi(saturated, matric, [cm]) = %f (must > 0)\n", + swrcp[1] + ); + } - \param fractionGravel The fraction of gravel in a layer by volume. - \param sand The fraction of sand in a layer by weight. - \param clay The fraction of clay in a layer by weight. - \param n Soil layer index. + if (LE(swrcp[1], 0.0) || GT(swrcp[1], 1.0)) { + res = swFALSE; + LogError( + logfp, + LOGWARN, + "SWRC_check_parameters_for_Campbell1974(): invalid value of " + "theta(saturated, matric, [cm/cm]) = %f (must be within 0-1)\n", + swrcp[1] + ); + } - \sideeffect - - thetasMatric Saturated water content for the matric component (m^3/m^3). - - psisMatric Saturation matric potential (MPa). - - bMatric Slope of the linear log-log retention curve (unitless). - - swcBulk_saturated The saturated water content for the whole soil (bulk) (cm/layer). + if (ZRO(swrcp[2])) { + res = swFALSE; + LogError( + logfp, + LOGWARN, + "SWRC_check_parameters_for_Campbell1974(): invalid value of " + "beta = %f (must be != 0)\n", + swrcp[2] + ); + } + if (LE(swrcp[3], 0.0)) { + res = swFALSE; + LogError( + logfp, + LOGWARN, + "SWRC_check_parameters_for_Campbell1974(): invalid value of " + "K_sat = %f (must be > 0)\n", + swrcp[3] + ); + } + + return res; +} + +/** + @brief Check van Genuchten 1980 SWRC parameters + + See `SWRC_SWCtoSWP_vanGenuchten1980()` and `SWRC_SWPtoSWC_vanGenuchten1980()` + for implementation of van Genuchten's 1980 SWRC (\cite vanGenuchten1980). + + "vanGenuchten1980" has five parameters (four are used for the SWRC): + - `swrcp[0]` (`theta_r`): residual volumetric water content + of the matric component [cm/cm] + - `swrcp[1]` (`theta_s`): saturated volumetric water content + of the matric component [cm/cm] + - `swrcp[2]` (`alpha`): related to the inverse of air entry suction [cm-1] + - `swrcp[3]` (`n`): measure of the pore-size distribution [-] + - `swrcp[4]` (`K_sat`): saturated hydraulic conductivity `[cm / day]` + + @param[in] *swrcp Vector of SWRC parameters + + @return A logical value indicating if parameters passed the checks. */ +Bool SWRC_check_parameters_for_vanGenuchten1980(double *swrcp) { + Bool res = swTRUE; -void water_eqn(RealD fractionGravel, RealD sand, RealD clay, LyrIndex n) { + if (LE(swrcp[0], 0.0) || GT(swrcp[0], 1.)) { + res = swFALSE; + LogError( + logfp, + LOGWARN, + "SWRC_check_parameters_for_vanGenuchten1980(): invalid value of " + "theta(residual, matric, [cm/cm]) = %f (must be within 0-1)\n", + swrcp[0] + ); + } - /* Cosby, B. J., G. M. Hornberger, R. B. Clapp, and T. R. Ginn. 1984. - A statistical exploration of the relationships of soil moisture - characteristics to the physical properties of soils. - Water Resources Research 20:682–690. - https://doi.org/10.1029/WR020i006p00682. */ + if (LE(swrcp[1], 0.0) || GT(swrcp[1], 1.)) { + res = swFALSE; + LogError( + logfp, + LOGWARN, + "SWRC_check_parameters_for_vanGenuchten1980(): invalid value of " + "theta(saturated, matric, [cm/cm]) = %f (must be within 0-1)\n", + swrcp[1] + ); + } - /* Table 4 */ - SW_Site.lyr[n]->thetasMatric = -14.2 * sand - 3.7 * clay + 50.5; - SW_Site.lyr[n]->psisMatric = powe(10.0, -1.58 * sand - 0.63 * clay + 2.17); - SW_Site.lyr[n]->bMatric = -0.3 * sand + 15.7 * clay + 3.10; + if (LE(swrcp[1], swrcp[0])) { + res = swFALSE; + LogError( + logfp, + LOGWARN, + "SWRC_check_parameters_for_vanGenuchten1980(): invalid values for " + "theta(residual, matric, [cm/cm]) = %f and " + "theta(saturated, matric, [cm/cm]) = %f " + "(must be theta_r < theta_s)\n", + swrcp[0], swrcp[1] + ); + } + if (LE(swrcp[2], 0.0)) { + res = swFALSE; + LogError( + logfp, + LOGWARN, + "SWRC_check_parameters_for_vanGenuchten1980(): invalid value of " + "alpha([1 / cm]) = %f (must > 0)\n", + swrcp[2] + ); + } - if ( - LE(SW_Site.lyr[n]->thetasMatric, 0.0) || - GT(SW_Site.lyr[n]->thetasMatric, 100.0) - ) { + if (LE(swrcp[3], 1.0)) { + res = swFALSE; LogError( logfp, - LOGFATAL, - "water_eqn(): invalid value of " - "theta(saturated, matric, [%]; Cosby et al. 1984) = %f " - "(must within 0-100%)\n", - SW_Site.lyr[n]->thetasMatric + LOGWARN, + "SWRC_check_parameters_for_vanGenuchten1980(): invalid value of " + "n = %f (must be > 1)\n", + swrcp[3] ); } - if (ZRO(SW_Site.lyr[n]->bMatric)) { + if (LE(swrcp[4], 0.0)) { + res = swFALSE; LogError( logfp, - LOGFATAL, - "water_eqn(): invalid value of beta = %f (must be != 0)\n", - SW_Site.lyr[n]->bMatric + LOGWARN, + "SWRC_check_parameters_for_vanGenuchten1980(): invalid value of " + "K_sat = %f (must be > 0)\n", + swrcp[4] ); } - SW_Site.lyr[n]->binverseMatric = 1.0 / SW_Site.lyr[n]->bMatric; + return res; +} - /* Saxton, K. E. and W. J. Rawls. 2006. Soil water characteristic estimates - by texture and organic matter for hydrologic solutions. - Soil Science Society of America Journal 70:1569-1578. - */ +/** + @brief Check FXW SWRC parameters - RealD - OM = 0., - theta_S, theta_33, theta_33t, theta_S33, theta_S33t, theta_1500, theta_1500t, - R_w, alpha; + See `SWRC_SWCtoSWP_FXW()` and `SWRC_SWPtoSWC_FXW()` + for implementation of FXW's SWRC + (\cite fredlund1994CGJa, \cite wang2018wrr). - /* Eq. 1: 1500 kPa moisture */ - theta_1500t = - + 0.031 \ - - 0.024 * sand \ - + 0.487 * clay \ - + 0.006 * OM \ - + 0.005 * sand * OM \ - - 0.013 * clay * OM \ - + 0.068 * sand * clay; + "FXWJD" is a synonym for the FXW SWRC (\cite wang2018wrr). - theta_1500 = theta_1500t + (0.14 * theta_1500t - 0.02); + FXW has six parameters (four are used for the SWRC): + - `swrcp[0]` (`theta_s`): saturated volumetric water content + of the matric component [cm/cm] + - `swrcp[1]` (`alpha`): shape parameter [cm-1] + - `swrcp[2]` (`n`): shape parameter [-] + - `swrcp[3]` (`m`): shape parameter [-] + - `swrcp[4]` (`K_sat`): saturated hydraulic conductivity [cm / day] + - `swrcp[5]` (`L`): tortuosity/connectivity parameter [-] + + FXW SWRC does not include a non-zero residual water content parameter; + instead, it assumes a pressure head #FXW_h0 of 6.3 x 10^6 cm + (equivalent to c. -618 MPa) at zero water content + and a "residual" pressure head #FXW_hr of 1500 cm + (\cite fredlund1994CGJa, \cite wang2018wrr, \cite rudiyanto2021G). + + Acceptable parameter values are partially based on + Table 1 in \cite wang2022WRRa. + + @param[in] *swrcp Vector of SWRC parameters + + @return A logical value indicating if parameters passed the checks. +*/ +Bool SWRC_check_parameters_for_FXW(double *swrcp) { + Bool res = swTRUE; + + if (LE(swrcp[0], 0.0) || GT(swrcp[0], 1.)) { + res = swFALSE; + LogError( + logfp, + LOGWARN, + "SWRC_check_parameters_for_FXW(): invalid value of " + "theta(saturated, matric, [cm/cm]) = %f (must be within 0-1)\n", + swrcp[0] + ); + } + + if (LE(swrcp[1], 0.0)) { + res = swFALSE; + LogError( + logfp, + LOGWARN, + "SWRC_check_parameters_for_FXW(): invalid value of " + "alpha([1 / cm]) = %f (must be > 0)\n", + swrcp[1] + ); + } + + if (LE(swrcp[2], 1.0) || GT(swrcp[2], 10.)) { + res = swFALSE; + LogError( + logfp, + LOGWARN, + "SWRC_check_parameters_for_FXW(): invalid value of " + "n = %f (must be within 1-10)\n", + swrcp[2] + ); + } + + if (LE(swrcp[3], 0.0) || GT(swrcp[3], 1.5)) { + res = swFALSE; + LogError( + logfp, + LOGWARN, + "SWRC_check_parameters_for_FXW(): invalid value of " + "m = %f (must be within 0-1.5)\n", + swrcp[3] + ); + } + + if (LE(swrcp[4], 0.0)) { + res = swFALSE; + LogError( + logfp, + LOGWARN, + "SWRC_check_parameters_for_FXW(): invalid value of " + "K_sat = %f (must be > 0)\n", + swrcp[4] + ); + } + + if (LE(swrcp[5], 0.)) { + res = swFALSE; + LogError( + logfp, + LOGWARN, + "SWRC_check_parameters_for_FXW(): invalid value of " + "L = %f (must be > 0)\n", + swrcp[5] + ); + } + + return res; +} + + +/** + @brief Saxton et al. 2006 PTFs \cite Saxton2006 + to estimate saturated soil water content + + @param[in] sand Sand content of the matric soil (< 2 mm fraction) [g/g] + @param[in] clay Clay content of the matric soil (< 2 mm fraction) [g/g] + @param[out] *theta_sat Estimated saturated volumetric water content + of the matric soil [cm/cm] +*/ +void PTF_Saxton2006( + double *theta_sat, + double sand, + double clay +) { + double + OM = 0., + theta_33, theta_33t, theta_S33, theta_S33t; /* Eq. 2: 33 kPa moisture */ theta_33t = @@ -293,7 +1020,8 @@ void water_eqn(RealD fractionGravel, RealD sand, RealD clay, LyrIndex n) { - 0.027 * clay * OM \ + 0.452 * sand * clay; - theta_33 = theta_33t + (1.283 * squared(theta_33t) - 0.374 * theta_33t - 0.015); + theta_33 = + theta_33t + (1.283 * squared(theta_33t) - 0.374 * theta_33t - 0.015); /* Eq. 3: SAT-33 kPa moisture */ theta_S33t = @@ -309,24 +1037,39 @@ void water_eqn(RealD fractionGravel, RealD sand, RealD clay, LyrIndex n) { /* Eq. 5: saturated moisture */ - theta_S = theta_33 + theta_S33 - 0.097 * sand + 0.043; + *theta_sat = theta_33 + theta_S33 - 0.097 * sand + 0.043; if ( - LE(theta_S, 0.) || - GT(theta_S, 1.) + LE(*theta_sat, 0.) || + GT(*theta_sat, 1.) ) { LogError( logfp, LOGFATAL, - "water_eqn(): invalid value of " - "theta(saturated, [cm / cm]; Saxton et al. 2006) = %f " - "(must be within 0-1)\n", - theta_S + "PTF_Saxton2006(): invalid value of " + "theta(saturated, [cm / cm]) = %f (must be within 0-1)\n", + *theta_sat ); } - SW_Site.lyr[n]->swcBulk_saturated = - SW_Site.lyr[n]->width * (1. - fractionGravel) * theta_S; + + +// currently, unused and defunct code: +// (e.g., SW_Site.lyr[n] assignments don't work here anymore!): +#ifdef UNUSED_SAXTON2006 + double R_w, alpha, theta_1500, theta_1500t; + + /* Eq. 1: 1500 kPa moisture */ + theta_1500t = + + 0.031 \ + - 0.024 * sand \ + + 0.487 * clay \ + + 0.006 * OM \ + + 0.005 * sand * OM \ + - 0.013 * clay * OM \ + + 0.068 * sand * clay; + + theta_1500 = theta_1500t + (0.14 * theta_1500t - 0.02); /* Eq. 18: slope of logarithmic tension-moisture curve */ @@ -364,30 +1107,120 @@ void water_eqn(RealD fractionGravel, RealD sand, RealD clay, LyrIndex n) { } else { SW_Site.lyr[n]->Saxton2006_fK_gravel = 1.; } +#endif +} + + + +/** + @brief Rawls and Brakensiek 1985 PTFs \cite rawls1985WmitE + to estimate residual soil water content for the Brooks-Corey SWRC + \cite brooks1964a + + @note This function was previously named "SW_VWCBulkRes". + @note This function is only well-defined for `clay` values in 5-60%, + `sand` values in 5-70%, and `porosity` must in 10-<100%. + + @param[in] sand Sand content of the matric soil (< 2 mm fraction) [g/g] + @param[in] clay Clay content of the matric soil (< 2 mm fraction) [g/g] + @param[in] porosity Pore space of the matric soil (< 2 mm fraction) [cm3/cm3] + @param[out] *theta_min Estimated residual volumetric water content + of the matric soil [cm/cm] +*/ +void PTF_RawlsBrakensiek1985( + double *theta_min, + double sand, + double clay, + double porosity +) { + if ( + GE(clay, 0.05) && LE(clay, 0.6) && + GE(sand, 0.05) && LE(sand, 0.7) && + GE(porosity, 0.1) && LT(porosity, 1.) + ) { + sand *= 100.; + clay *= 100.; + /* Note: the equation requires sand and clay in units of [100 * g / g]; + porosity, however, must be in units of [cm3 / cm3] + */ + *theta_min = fmax( + 0., + - 0.0182482 \ + + 0.00087269 * sand \ + + 0.00513488 * clay \ + + 0.02939286 * porosity \ + - 0.00015395 * squared(clay) \ + - 0.0010827 * sand * porosity \ + - 0.00018233 * squared(clay) * squared(porosity) \ + + 0.00030703 * squared(clay) * porosity \ + - 0.0023584 * squared(porosity) * clay + ); + + } else { + LogError( + logfp, + LOGWARN, + "`PTF_RawlsBrakensiek1985()`: sand, clay, or porosity out of valid range." + ); + *theta_min = SW_MISSING; + } } + + /** @brief Estimate soil density of the whole soil (bulk). - SOILWAT2 calculates internally based on soil bulk density of the whole soil, - i.e., including rock/gravel component. However, SOILWAT2 inputs are expected - to represent soil (matric) density of the < 2 mm fraction. + Based on equation 20 from Saxton. @cite Saxton2006. - Based on equation 20 from Saxton. @cite Saxton2006 + Similarly, estimate soil bulk density from `theta_sat` with + `2.65 * (1. - theta_sat * (1. - fractionGravel))`. */ RealD calculate_soilBulkDensity(RealD matricDensity, RealD fractionGravel) { - /*eqn. 20 from Saxton et al. 2006 to calculate the bulk density of soil */ - return matricDensity * (1 - fractionGravel) + (fractionGravel * 2.65); + return matricDensity * (1. - fractionGravel) + fractionGravel * 2.65; +} + + +/** + @brief Estimate soil density of the matric soil component. + + Based on equation 20 from Saxton. @cite Saxton2006 +*/ +RealD calculate_soilMatricDensity(RealD bulkDensity, RealD fractionGravel) { + double res; + + if (EQ(fractionGravel, 1.)) { + res = 0.; + } else { + res = (bulkDensity - fractionGravel * 2.65) / (1. - fractionGravel); + + if (LT(res, 0.)) { + LogError( + logfp, + LOGFATAL, + "bulkDensity (%f) is lower than expected " + "(density of coarse fragments = %f [g/cm3] " + "based on %f [%%] coarse fragments).\n", + bulkDensity, + fractionGravel * 2.65, + fractionGravel + ); + } + } + + return res; } + + /** @brief Count soil layers with bare-soil evaporation potential The count stops at first layer with 0. */ -LyrIndex nlayers_bsevap() { +LyrIndex nlayers_bsevap(void) { SW_SITE *v = &SW_Site; LyrIndex s, n = 0; @@ -474,6 +1307,7 @@ LyrIndex _newlayer(void) { : (SW_LAYER_INFO **) Mem_ReAlloc(v->lyr, sizeof(SW_LAYER_INFO *) * (v->n_layers)); /* else realloc() */ v->lyr[v->n_layers - 1] = (SW_LAYER_INFO *) Mem_Calloc(1, sizeof(SW_LAYER_INFO), "_newlayer()"); + v->lyr[v->n_layers - 1]->id = v->n_layers - 1; return v->n_layers - 1; } @@ -675,8 +1509,23 @@ void SW_SIT_read(void) { if (debug) swprintf("'SW_SIT_read': scenario = %s\n", c->scenario); #endif break; + case 41: + v->type_soilDensityInput = atoi(inbuf); + break; + case 42: + strcpy(v->site_swrc_name, inbuf); + v->site_swrc_type = encode_str2swrc(v->site_swrc_name); + break; + case 43: + strcpy(v->site_ptf_name, inbuf); + v->site_ptf_type = encode_str2ptf(v->site_ptf_name); + break; + case 44: + v->site_has_swrcp = itob(atoi(inbuf)); + break; + default: - if (lineno > 40 + MAX_TRANSP_REGIONS) + if (lineno > 44 + MAX_TRANSP_REGIONS) break; /* skip extra lines */ if (MAX_TRANSP_REGIONS < v->n_transp_rgn) { @@ -700,31 +1549,136 @@ void SW_SIT_read(void) { CloseFile(&f); if (LT(v->percentRunoff, 0.) || GT(v->percentRunoff, 1.)) { - LogError(logfp, LOGFATAL, "%s : proportion of ponded surface water removed as daily" - "runoff = %f (value ranges between 0 and 1)\n", MyFileName, v->percentRunoff); + LogError( + logfp, + LOGFATAL, + "%s : proportion of ponded surface water removed as daily" + "runoff = %f (value ranges between 0 and 1)\n", + MyFileName, v->percentRunoff + ); } if (LT(v->percentRunon, 0.)) { - LogError(logfp, LOGFATAL, "%s : proportion of water that arrives at surface added " - "as daily runon = %f (value ranges between 0 and +inf)\n", MyFileName, v->percentRunon); + LogError( + logfp, + LOGFATAL, + "%s : proportion of water that arrives at surface added " + "as daily runon = %f (value ranges between 0 and +inf)\n", + MyFileName, v->percentRunon + ); } if (too_many_regions) { - LogError(logfp, LOGFATAL, "%s : Number of transpiration regions" - " exceeds maximum allowed (%d > %d)\n", MyFileName, v->n_transp_rgn, MAX_TRANSP_REGIONS); + LogError( + logfp, + LOGFATAL, + "%s : Number of transpiration regions" + " exceeds maximum allowed (%d > %d)\n", + MyFileName, v->n_transp_rgn, MAX_TRANSP_REGIONS + ); } /* check for any discontinuities (reversals) in the transpiration regions */ for (r = 1; r < v->n_transp_rgn; r++) { if (_TranspRgnBounds[r - 1] >= _TranspRgnBounds[r]) { - LogError(logfp, LOGFATAL, "%s : Discontinuity/reversal in transpiration regions.\n", SW_F_name(eSite)); + LogError( + logfp, + LOGFATAL, + "%s : Discontinuity/reversal in transpiration regions.\n", + SW_F_name(eSite) + ); + } + } +} + + + +/** Reads soil layers and soil properties from input file + + @note Previously, the function was static and named `_read_layers()`. +*/ +void SW_LYR_read(void) { + /* =================================================== */ + /* 5-Feb-2002 (cwb) removed dmin requirement in input file */ + + SW_SITE *v = &SW_Site; + FILE *f; + LyrIndex lyrno; + int x, k; + RealF dmin = 0.0, dmax, evco, trco_veg[NVEGTYPES], psand, pclay, soildensity, imperm, + soiltemp, f_gravel; + + /* note that Files.read() must be called prior to this. */ + MyFileName = SW_F_name(eLayers); + + f = OpenFile(MyFileName, "r"); + + while (GetALine(f, inbuf)) { + lyrno = _newlayer(); + + x = sscanf( + inbuf, + "%f %f %f %f %f %f %f %f %f %f %f %f", + &dmax, + &soildensity, + &f_gravel, + &evco, + &trco_veg[SW_GRASS], &trco_veg[SW_SHRUB], &trco_veg[SW_TREES], &trco_veg[SW_FORBS], + &psand, + &pclay, + &imperm, + &soiltemp + ); + + /* Check that we have 12 values per layer */ + /* Adjust number if new variables are added */ + if (x != 12) { + CloseFile(&f); + LogError( + logfp, + LOGFATAL, + "%s : Incomplete record %d.\n", + MyFileName, lyrno + 1 + ); + } + + v->lyr[lyrno]->width = dmax - dmin; + + /* checks for valid values now carried out by `SW_SIT_init_run()` */ + + dmin = dmax; + v->lyr[lyrno]->fractionVolBulk_gravel = f_gravel; + v->lyr[lyrno]->soilDensityInput = soildensity; + v->lyr[lyrno]->evap_coeff = evco; + + ForEachVegType(k) + { + v->lyr[lyrno]->transp_coeff[k] = trco_veg[k]; + } + v->lyr[lyrno]->fractionWeightMatric_sand = psand; + v->lyr[lyrno]->fractionWeightMatric_clay = pclay; + v->lyr[lyrno]->impermeability = imperm; + v->lyr[lyrno]->avgLyrTemp = soiltemp; + + if (lyrno >= MAX_LAYERS) { + CloseFile(&f); + LogError( + logfp, + LOGFATAL, + "%s : Too many layers specified (%d).\n" + "Maximum number of layers is %d\n", + MyFileName, lyrno + 1, MAX_LAYERS + ); } } - _read_layers(); + CloseFile(&f); } + + + /** @brief Creates soil layers based on function arguments (instead of reading them from an input file as _read_layers() does) @@ -732,8 +1686,7 @@ void SW_SIT_read(void) { @param nlyrs The number of soil layers to create. @param[in] dmax Array of size \p nlyrs for depths [cm] of each soil layer measured from the surface - @param[in] matricd Array of size \p nlyrs for soil density of the matric - component, i.e., the < 2 mm fraction [g/cm3] + @param[in] bd Array of size \p nlyrs of soil bulk density [g/cm3] @param[in] f_gravel Array of size \p nlyrs for volumetric gravel content [v/v] @param[in] evco Array of size \p nlyrs with bare-soil evaporation coefficients [0, 1] that sum up to 1. @@ -766,7 +1719,7 @@ void SW_SIT_read(void) { - This function is a modified version of the function _read_layers() in SW_Site.c. */ -void set_soillayers(LyrIndex nlyrs, RealF *dmax, RealF *matricd, RealF *f_gravel, +void set_soillayers(LyrIndex nlyrs, RealF *dmax, RealF *bd, RealF *f_gravel, RealF *evco, RealF *trco_grass, RealF *trco_shrub, RealF *trco_tree, RealF *trco_forb, RealF *psand, RealF *pclay, RealF *imperm, RealF *soiltemp, int nRegions, RealD *regionLowerBounds) @@ -790,7 +1743,8 @@ void set_soillayers(LyrIndex nlyrs, RealF *dmax, RealF *matricd, RealF *f_gravel v->lyr[lyrno]->width = dmax[i] - dmin; dmin = dmax[i]; - v->lyr[lyrno]->soilMatric_density = matricd[i]; + v->lyr[lyrno]->soilDensityInput = bd[i]; + v->type_soilDensityInput = SW_BULK; v->lyr[lyrno]->fractionVolBulk_gravel = f_gravel[i]; v->lyr[lyrno]->evap_coeff = evco[i]; @@ -905,6 +1859,90 @@ void derive_soilRegions(int nRegions, RealD *regionLowerBounds){ } } + + +/** Obtain soil water retention curve parameters from disk +*/ +void SW_SWRC_read(void) { + + /* Don't read values from disk if they will be estimated via a PTF */ + if (!SW_Site.site_has_swrcp) return; + + FILE *f; + LyrIndex lyrno = 0, k; + int x; + RealF tmp_swrcp[SWRC_PARAM_NMAX]; + + /* note that Files.read() must be called prior to this. */ + MyFileName = SW_F_name(eSWRCp); + + f = OpenFile(MyFileName, "r"); + + while (GetALine(f, inbuf)) { + x = sscanf( + inbuf, + "%f %f %f %f %f %f", + &tmp_swrcp[0], + &tmp_swrcp[1], + &tmp_swrcp[2], + &tmp_swrcp[3], + &tmp_swrcp[4], + &tmp_swrcp[5] + ); + + /* Note: `SW_SIT_init_run()` will call function to check for valid values */ + + /* Check that we have n = `SWRC_PARAM_NMAX` values per layer */ + if (x != SWRC_PARAM_NMAX) { + CloseFile(&f); + LogError( + logfp, + LOGFATAL, + "%s : Bad number of SWRC parameters %d -- must be %d.\n", + MyFileName, x, SWRC_PARAM_NMAX + ); + } + + /* Check that we are within `SW_Site.n_layers` */ + if (lyrno >= SW_Site.n_layers) { + CloseFile(&f); + LogError( + logfp, + LOGFATAL, + "%s : Number of layers with SWRC parameters (%d) " + "must match number of soil layers (%d)\n", + MyFileName, lyrno + 1, SW_Site.n_layers + ); + } + + /* Copy values into structure */ + for (k = 0; k < SWRC_PARAM_NMAX; k++) { + SW_Site.lyr[lyrno]->swrcp[k] = tmp_swrcp[k]; + } + + lyrno++; + } + + CloseFile(&f); +} + + +/** + @brief Derive and check soil properties from inputs + + Bulk refers to the whole soil, + i.e., including the rock/gravel component (coarse fragments), + whereas matric refers to the < 2 mm fraction. + + Internally, SOILWAT2 calculates based on bulk soil, i.e., the whole soil. + However, sand and clay inputs are expected to represent the soil matric, + i.e., the < 2 mm fraction. + + sand + clay + silt must equal one. + Fraction of silt is calculated: 1 - (sand + clay). + + @sideeffect Values stored in global variable `SW_Site`. +*/ void SW_SIT_init_run(void) { /* =================================================== */ /* potentially this routine can be called whether the @@ -918,12 +1956,8 @@ void SW_SIT_init_run(void) { SW_LAYER_INFO *lyr; LyrIndex s, r, curregion; int k, flagswpcrit = 0; - Bool fail = swFALSE; RealD - fval = 0, - evsum = 0., trsum_veg[NVEGTYPES] = {0.}, - swcmin_help1, swcmin_help2, tmp; - const char *errtype = "\0"; + evsum = 0., trsum_veg[NVEGTYPES] = {0.}, tmp; #ifdef SWDEBUG int debug = 0; @@ -940,120 +1974,109 @@ void SW_SIT_init_run(void) { add_deepdrain_layer(); + /* Check compatibility between selected SWRC and PTF */ + if (!sp->site_has_swrcp) { + if (!check_SWRC_vs_PTF(sp->site_swrc_name, sp->site_ptf_name)) { + LogError( + logfp, + LOGFATAL, + "Selected PTF '%s' is incompatible with selected SWRC '%s'\n", + sp->site_ptf_name, + sp->site_swrc_name + ); + } + } + + /* Loop over soil layers check variables and calculate parameters */ ForEachSoilLayer(s) { lyr = sp->lyr[s]; - /* Check validity of soil variables: - previously, checked by code in `_read_layers()`, - erroneously skipped by `set_soillayers()`, - and checked by code in rSOILWAT2's `onSet_SW_LYR()` + /* Copy site-level SWRC/PTF information to each layer: + We currently allow specifying one SWRC/PTF for a site for all layers; + remove in the future if we allow SWRC/PTF to vary by soil layer */ - if (LE(lyr->width, 0.)) { - fail = swTRUE; - fval = lyr->width; - errtype = Str_Dup("layer width"); - - } else if (LT(lyr->soilMatric_density, 0.)) { - fail = swTRUE; - fval = lyr->soilMatric_density; - errtype = Str_Dup("soil density"); - - } else if ( - LT(lyr->fractionVolBulk_gravel, 0.) || - GE(lyr->fractionVolBulk_gravel, 1.) - ) { - fail = swTRUE; - fval = lyr->fractionVolBulk_gravel; - errtype = Str_Dup("gravel content"); + lyr->swrc_type = sp->site_swrc_type; + lyr->ptf_type = sp->site_ptf_type; - } else if ( - LE(lyr->fractionWeightMatric_sand, 0.) || - GE(lyr->fractionWeightMatric_sand, 1.) - ) { - fail = swTRUE; - fval = lyr->fractionWeightMatric_sand; - errtype = Str_Dup("sand proportion"); - } else if ( - LE(lyr->fractionWeightMatric_clay, 0.) || - GE(lyr->fractionWeightMatric_clay, 1.) - ) { - fail = swTRUE; - fval = lyr->fractionWeightMatric_clay; - errtype = Str_Dup("clay proportion"); + /* Check soil properties for valid values */ + if (!SW_check_soil_properties(lyr)) { + LogError( + logfp, + LOGFATAL, + "Invalid soil properties in layer %d.\n", + lyr->id + 1 + ); + } - } else if ( - GE(lyr->fractionWeightMatric_sand + lyr->fractionWeightMatric_clay, 1.) - ) { - fail = swTRUE; - fval = lyr->fractionWeightMatric_sand + lyr->fractionWeightMatric_clay; - errtype = Str_Dup("sand+clay proportion"); - } else if ( - LT(lyr->impermeability, 0.) || - GT(lyr->impermeability, 1.) - ) { - fail = swTRUE; - fval = lyr->impermeability; - errtype = Str_Dup("impermeability"); + /* Update soil density depending on inputs */ + switch (SW_Site.type_soilDensityInput) { - } else if ( - LT(lyr->evap_coeff, 0.) || - GT(lyr->evap_coeff, 1.) - ) { - fail = swTRUE; - fval = lyr->evap_coeff; - errtype = Str_Dup("bare-soil evaporation coefficient"); - - } else { - ForEachVegType(k) { - if (LT(lyr->transp_coeff[k], 0.) || GT(lyr->transp_coeff[k], 1.)) { - fail = swTRUE; - fval = lyr->transp_coeff[k]; - errtype = Str_Dup("transpiration coefficient"); - break; - } - } - } + case SW_BULK: + lyr->soilBulk_density = lyr->soilDensityInput; + + lyr->soilMatric_density = calculate_soilMatricDensity( + lyr->soilBulk_density, + lyr->fractionVolBulk_gravel + ); + + break; + + case SW_MATRIC: + lyr->soilMatric_density = lyr->soilDensityInput; + + lyr->soilBulk_density = calculate_soilBulkDensity( + lyr->soilMatric_density, + lyr->fractionVolBulk_gravel + ); + + break; - if (fail) { + default: LogError( logfp, LOGFATAL, - "Invalid %s (%5.4f) in layer %d.\n", - errtype, fval, s + 1 + "Soil density type not recognized", + SW_Site.type_soilDensityInput ); } - /* Update soil density for gravel */ - lyr->soilBulk_density = calculate_soilBulkDensity( - lyr->soilMatric_density, - lyr->fractionVolBulk_gravel - ); - /* Calculate pedotransfer function parameters */ - water_eqn( - lyr->fractionVolBulk_gravel, - lyr->fractionWeightMatric_sand, - lyr->fractionWeightMatric_clay, - s - ); - /* Calculate SWC at field capacity and at wilting point */ - lyr->swcBulk_fieldcap = lyr->width * SW_SWPmatric2VWCBulk( - lyr->fractionVolBulk_gravel, - 0.333, - s - ); + if (!sp->site_has_swrcp) { + /* Use pedotransfer function PTF + to estimate parameters of soil water retention curve (SWRC) for layer. + If `has_swrcp`, then parameters already obtained from disk + by `SW_SWRC_read()` + */ + SWRC_PTF_estimate_parameters( + lyr->ptf_type, + lyr->swrcp, + lyr->fractionWeightMatric_sand, + lyr->fractionWeightMatric_clay, + lyr->fractionVolBulk_gravel, + lyr->soilBulk_density + ); + } - lyr->swcBulk_wiltpt = lyr->width * SW_SWPmatric2VWCBulk( - lyr->fractionVolBulk_gravel, - 15, - s - ); + /* Check parameters of selected SWRC */ + if (!SWRC_check_parameters(lyr->swrc_type, lyr->swrcp)) { + LogError( + logfp, + LOGFATAL, + "Checks of parameters for SWRC '%s' in layer %d failed.", + swrc2str[lyr->swrc_type], + lyr->id + 1 + ); + } + + /* Calculate SWC at field capacity and at wilting point */ + lyr->swcBulk_fieldcap = SW_SWRC_SWPtoSWC(0.333, lyr); + lyr->swcBulk_wiltpt = SW_SWRC_SWPtoSWC(15., lyr); /* Calculate lower SWC limit of bare-soil evaporation as `max(0.5 * wiltpt, SWC@hygroscopic)` @@ -1066,69 +2089,43 @@ void SW_SIT_init_run(void) { */ lyr->swcBulk_halfwiltpt = fmax( 0.5 * lyr->swcBulk_wiltpt, - lyr->width * SW_SWPmatric2VWCBulk(lyr->fractionVolBulk_gravel, 100., s) + SW_SWRC_SWPtoSWC(100., lyr) ); - /* Compute swc wet and dry limits and init value */ - if (LT(_SWCMinVal, 0.0)) { - /* input: estimate mininum SWC */ - - /* residual SWC of Rawls & Brakensiek (1985) */ - swcmin_help1 = SW_VWCBulkRes( - lyr->fractionVolBulk_gravel, - lyr->fractionWeightMatric_sand, - lyr->fractionWeightMatric_clay, - lyr->swcBulk_saturated / ((1. - lyr->fractionVolBulk_gravel) * lyr->width) - ); - - /* Lower limit for swc_min - Notes: - - used in case the equation for residual SWC doesn't work or - produces unrealistic small values - - currently, -30 MPa - (based on limited test runs across western US including hot deserts) - lower than "air-dry" = hygroscopic point (-10. MPa; Porporato et al. 2001) - not as extreme as "oven-dry" (-1000. MPa; Fredlund et al. 1994) - */ - swcmin_help2 = SW_SWPmatric2VWCBulk(lyr->fractionVolBulk_gravel, 300., s); - - // if `SW_VWCBulkRes()` returns SW_MISSING then use `swcmin_help2` - if (missing(swcmin_help1)) { - lyr->swcBulk_min = swcmin_help2; - - } else { - lyr->swcBulk_min = fmax(swcmin_help1, swcmin_help2); - } - - } else if (GE(_SWCMinVal, 1.0)) { - /* input: fixed SWP value as minimum SWC; unit(_SWCMinVal) == -bar */ - lyr->swcBulk_min = SW_SWPmatric2VWCBulk( - lyr->fractionVolBulk_gravel, - _SWCMinVal, - s - ); - - } else { - /* input: fixed VWC value as minimum SWC; unit(_SWCMinVal) == cm/cm */ - lyr->swcBulk_min = _SWCMinVal; - } + /* Extract or estimate additional properties */ + lyr->swcBulk_saturated = SW_swcBulk_saturated( + lyr->swrc_type, + lyr->swrcp, + lyr->fractionVolBulk_gravel, + lyr->width, + lyr->ptf_type, + lyr->fractionWeightMatric_sand, + lyr->fractionWeightMatric_clay + ); - /* Convert VWC to SWC */ - lyr->swcBulk_min *= lyr->width; + lyr->swcBulk_min = SW_swcBulk_minimum( + lyr->swrc_type, + lyr->swrcp, + lyr->fractionVolBulk_gravel, + lyr->width, + lyr->ptf_type, + _SWCMinVal, + lyr->fractionWeightMatric_sand, + lyr->fractionWeightMatric_clay, + lyr->swcBulk_saturated + ); /* Calculate wet limit of SWC for what inputs defined as wet */ - lyr->swcBulk_wet = lyr->width * (GE(_SWCWetVal, 1.0) ? - SW_SWPmatric2VWCBulk(lyr->fractionVolBulk_gravel, _SWCWetVal, s) : - _SWCWetVal - ); + lyr->swcBulk_wet = GE(_SWCWetVal, 1.0) ? + SW_SWRC_SWPtoSWC(_SWCWetVal, lyr) : + _SWCWetVal * lyr->width; /* Calculate initial SWC based on inputs */ - lyr->swcBulk_init = lyr->width * (GE(_SWCInitVal, 1.0) ? - SW_SWPmatric2VWCBulk(lyr->fractionVolBulk_gravel, _SWCInitVal, s) : - _SWCInitVal - ); + lyr->swcBulk_init = GE(_SWCInitVal, 1.0) ? + SW_SWRC_SWPtoSWC(_SWCInitVal, lyr) : + _SWCInitVal * lyr->width; /* test validity of values */ @@ -1138,7 +2135,7 @@ void SW_SIT_init_run(void) { "%s : Layer %d\n" " calculated `swcBulk_init` (%.4f cm) <= `swcBulk_min` (%.4f cm).\n" " Recheck parameters and try again.\n", - MyFileName, s + 1, lyr->swcBulk_init, lyr->swcBulk_min + MyFileName, lyr->id + 1, lyr->swcBulk_init, lyr->swcBulk_min ); } @@ -1148,7 +2145,7 @@ void SW_SIT_init_run(void) { "%s : Layer %d\n" " calculated `swcBulk_wiltpt` (%.4f cm) <= `swcBulk_min` (%.4f cm).\n" " Recheck parameters and try again.\n", - MyFileName, s + 1, lyr->swcBulk_wiltpt, lyr->swcBulk_min + MyFileName, lyr->id + 1, lyr->swcBulk_wiltpt, lyr->swcBulk_min ); } @@ -1159,11 +2156,11 @@ void SW_SIT_init_run(void) { " calculated `swcBulk_halfwiltpt` (%.4f cm / %.2f MPa)\n" " <= `swcBulk_min` (%.4f cm / %.2f MPa).\n" " `swcBulk_halfwiltpt` was set to `swcBulk_min`.\n", - MyFileName, s + 1, + MyFileName, lyr->id + 1, lyr->swcBulk_halfwiltpt, - -0.1 * SW_SWCbulk2SWPmatric(lyr->fractionVolBulk_gravel, lyr->swcBulk_halfwiltpt, s), + -0.1 * SW_SWRC_SWCtoSWP(lyr->swcBulk_halfwiltpt, lyr), lyr->swcBulk_min, - -0.1 * SW_SWCbulk2SWPmatric(lyr->fractionVolBulk_gravel, lyr->swcBulk_min, s) + -0.1 * SW_SWRC_SWCtoSWP(lyr->swcBulk_min, lyr) ); lyr->swcBulk_halfwiltpt = lyr->swcBulk_min; @@ -1175,7 +2172,7 @@ void SW_SIT_init_run(void) { "%s : Layer %d\n" " calculated `swcBulk_wet` (%.4f cm) <= `swcBulk_min` (%.4f cm).\n" " Recheck parameters and try again.\n", - MyFileName, s + 1, lyr->swcBulk_wet, lyr->swcBulk_min + MyFileName, lyr->id + 1, lyr->swcBulk_wet, lyr->swcBulk_min ); } @@ -1186,18 +2183,14 @@ void SW_SIT_init_run(void) { "L[%d] swcmin=%f = swpmin=%f\n", s, lyr->swcBulk_min, - SW_SWCbulk2SWPmatric(lyr->fractionVolBulk_gravel, lyr->swcBulk_min, s) + SW_SWRC_SWCtoSWP(lyr->swcBulk_min, lyr) ); swprintf( "L[%d] SWC(HalfWiltpt)=%f = swp(hw)=%f\n", s, lyr->swcBulk_halfwiltpt, - SW_SWCbulk2SWPmatric( - lyr->fractionVolBulk_gravel, - lyr->swcBulk_halfwiltpt, - s - ) + SW_SWRC_SWCtoSWP(lyr->swcBulk_halfwiltpt, lyr) ); } #endif @@ -1210,10 +2203,9 @@ void SW_SIT_init_run(void) { { trsum_veg[k] += lyr->transp_coeff[k]; /* calculate soil water content at SWPcrit for each vegetation type */ - lyr->swcBulk_atSWPcrit[k] = lyr->width * SW_SWPmatric2VWCBulk( - lyr->fractionVolBulk_gravel, + lyr->swcBulk_atSWPcrit[k] = SW_SWRC_SWPtoSWC( SW_VegProd.veg[k].SWPcrit, - s + lyr ); if (LT(lyr->swcBulk_atSWPcrit[k], lyr->swcBulk_min)) { @@ -1222,11 +2214,7 @@ void SW_SIT_init_run(void) { // lower SWcrit [-bar] to SWP-equivalent of swBulk_min tmp = fmin( SW_VegProd.veg[k].SWPcrit, - SW_SWCbulk2SWPmatric( - lyr->fractionVolBulk_gravel, - lyr->swcBulk_min, - s - ) + SW_SWRC_SWCtoSWP(lyr->swcBulk_min, lyr) ); LogError( @@ -1236,11 +2224,11 @@ void SW_SIT_init_run(void) { " <= `swcBulk_min` (%.4f cm / %.4f MPa).\n" " `SWcrit` adjusted to %.4f MPa " "(and swcBulk_atSWPcrit in every layer will be re-calculated).\n", - MyFileName, s + 1, k + 1, + MyFileName, lyr->id + 1, k + 1, lyr->swcBulk_atSWPcrit[k], -0.1 * SW_VegProd.veg[k].SWPcrit, lyr->swcBulk_min, - -0.1 * SW_SWCbulk2SWPmatric(lyr->fractionVolBulk_gravel, lyr->swcBulk_min, s), + -0.1 * SW_SWRC_SWCtoSWP(lyr->swcBulk_min, lyr), -0.1 * tmp ); @@ -1293,10 +2281,9 @@ void SW_SIT_init_run(void) { ForEachVegType(k) { /* calculate soil water content at adjusted SWPcrit */ - lyr->swcBulk_atSWPcrit[k] = lyr->width * SW_SWPmatric2VWCBulk( - lyr->fractionVolBulk_gravel, + lyr->swcBulk_atSWPcrit[k] = SW_SWRC_SWPtoSWC( SW_VegProd.veg[k].SWPcrit, - s + lyr ); if (LT(lyr->swcBulk_atSWPcrit[k], lyr->swcBulk_min)) { @@ -1306,7 +2293,7 @@ void SW_SIT_init_run(void) { " calculated `swcBulk_atSWPcrit` (%.4f cm)\n" " <= `swcBulk_min` (%.4f cm).\n" " even with adjusted `SWcrit` (%.4f MPa).\n", - MyFileName, s + 1, k + 1, + MyFileName, lyr->id + 1, k + 1, lyr->swcBulk_atSWPcrit[k], lyr->swcBulk_min, -0.1 * SW_VegProd.veg[k].SWPcrit @@ -1326,16 +2313,26 @@ void SW_SIT_init_run(void) { /* normalize the evap and transp coefficients separately * to avoid obfuscation in the above loop */ if (!EQ_w_tol(evsum, 1.0, 1e-4)) { // inputs are not more precise than at most 3-4 digits - LogError(logfp, LOGWARN, - "%s : Evaporation coefficients were normalized:\n" \ - "\tSum of coefficients was %.4f, but must be 1.0. " \ - "New coefficients are:", MyFileName, evsum); + LogError( + logfp, + LOGWARN, + "%s : Evaporation coefficients were normalized:\n" + "\tSum of coefficients was %.4f, but must be 1.0. " + "New coefficients are:", + MyFileName, + evsum + ); ForEachEvapLayer(s) { SW_Site.lyr[s]->evap_coeff /= evsum; - LogError(logfp, LOGNOTE, " Layer %2d : %.4f", - s + 1, SW_Site.lyr[s]->evap_coeff); + LogError( + logfp, + LOGNOTE, + " Layer %2d : %.4f", + SW_Site.lyr[s]->id + 1, + SW_Site.lyr[s]->evap_coeff + ); } LogError(logfp, LOGQUIET, ""); @@ -1343,19 +2340,29 @@ void SW_SIT_init_run(void) { ForEachVegType(k) { - if (!EQ_w_tol(trsum_veg[k], 1.0, 1e-4)) { // inputs are not more precise than at most 3-4 digits - LogError(logfp, LOGWARN, - "%s : Transpiration coefficients were normalized for %s:\n" \ - "\tSum of coefficients was %.4f, but must be 1.0. " \ - "New coefficients are:", MyFileName, key2veg[k], trsum_veg[k]); + // inputs are not more precise than at most 3-4 digits + if (!EQ_w_tol(trsum_veg[k], 1.0, 1e-4)) { + LogError( + logfp, + LOGWARN, + "%s : Transpiration coefficients were normalized for %s:\n" + "\tSum of coefficients was %.4f, but must be 1.0. " + "New coefficients are:", + MyFileName, key2veg[k], trsum_veg[k] + ); ForEachSoilLayer(s) { if (GT(SW_Site.lyr[s]->transp_coeff[k], 0.)) { SW_Site.lyr[s]->transp_coeff[k] /= trsum_veg[k]; - LogError(logfp, LOGNOTE, " Layer %2d : %.4f", - s + 1, SW_Site.lyr[s]->transp_coeff[k]); + LogError( + logfp, + LOGNOTE, + " Layer %2d : %.4f", + SW_Site.lyr[s]->id + 1, + SW_Site.lyr[s]->transp_coeff[k] + ); } } @@ -1371,17 +2378,23 @@ void SW_SIT_init_run(void) { if (too_many_RGR) { // because we will use loops such `for (i = 0; i <= nRgr + 1; i++)` - LogError(logfp, LOGWARN, - "\nSOIL_TEMP FUNCTION ERROR: the number of regressions is > the "\ - "maximum number of regressions. resetting max depth, deltaX, nRgr "\ - "values to 180, 15, & 11 respectively\n"); + LogError( + logfp, + LOGWARN, + "\nSOIL_TEMP FUNCTION ERROR: the number of regressions is > the " + "maximum number of regressions. resetting max depth, deltaX, nRgr " + "values to 180, 15, & 11 respectively\n" + ); } else { // because we don't deal with partial layers - LogError(logfp, LOGWARN, - "\nSOIL_TEMP FUNCTION ERROR: max depth is not evenly divisible by "\ - "deltaX (ie the remainder != 0). resetting max depth, deltaX, nRgr "\ - "values to 180, 15, & 11 respectively\n"); + LogError( + logfp, + LOGWARN, + "\nSOIL_TEMP FUNCTION ERROR: max depth is not evenly divisible by " + "deltaX (ie the remainder != 0). resetting max depth, deltaX, nRgr " + "values to 180, 15, & 11 respectively\n" + ); } // resets it to the default values @@ -1541,26 +2554,76 @@ void _echo_inputs(void) { ForEachSoilLayer(i) { - LogError(logfp, LOGNOTE, " %3d %15.4f %15.4f %15.4f %15.4f %15.4f %15.4f %15.4f %15.4f %15.4f\n", i + 1, - SW_SWCbulk2SWPmatric(s->lyr[i]->fractionVolBulk_gravel, s->lyr[i]->swcBulk_fieldcap, i), - SW_SWCbulk2SWPmatric(s->lyr[i]->fractionVolBulk_gravel, s->lyr[i]->swcBulk_wiltpt, i), - SW_SWCbulk2SWPmatric(s->lyr[i]->fractionVolBulk_gravel, s->lyr[i]->swcBulk_atSWPcrit[SW_FORBS], i), - SW_SWCbulk2SWPmatric(s->lyr[i]->fractionVolBulk_gravel, s->lyr[i]->swcBulk_atSWPcrit[SW_TREES], i), - SW_SWCbulk2SWPmatric(s->lyr[i]->fractionVolBulk_gravel, s->lyr[i]->swcBulk_atSWPcrit[SW_SHRUB], i), - SW_SWCbulk2SWPmatric(s->lyr[i]->fractionVolBulk_gravel, s->lyr[i]->swcBulk_atSWPcrit[SW_GRASS], i), - SW_SWCbulk2SWPmatric(s->lyr[i]->fractionVolBulk_gravel, s->lyr[i]->swcBulk_wet, i), - SW_SWCbulk2SWPmatric(s->lyr[i]->fractionVolBulk_gravel, s->lyr[i]->swcBulk_min, i), - SW_SWCbulk2SWPmatric(s->lyr[i]->fractionVolBulk_gravel, s->lyr[i]->swcBulk_init, i)); + LogError( + logfp, + LOGNOTE, + " %3d %15.4f %15.4f %15.4f %15.4f %15.4f %15.4f %15.4f %15.4f %15.4f\n", + i + 1, + SW_SWRC_SWCtoSWP(s->lyr[i]->swcBulk_fieldcap, s->lyr[i]), + SW_SWRC_SWCtoSWP(s->lyr[i]->swcBulk_wiltpt, s->lyr[i]), + SW_SWRC_SWCtoSWP(s->lyr[i]->swcBulk_atSWPcrit[SW_FORBS], s->lyr[i]), + SW_SWRC_SWCtoSWP(s->lyr[i]->swcBulk_atSWPcrit[SW_TREES], s->lyr[i]), + SW_SWRC_SWCtoSWP(s->lyr[i]->swcBulk_atSWPcrit[SW_SHRUB], s->lyr[i]), + SW_SWRC_SWCtoSWP(s->lyr[i]->swcBulk_atSWPcrit[SW_SHRUB], s->lyr[i]), + SW_SWRC_SWCtoSWP(s->lyr[i]->swcBulk_atSWPcrit[SW_GRASS], s->lyr[i]), + SW_SWRC_SWCtoSWP(s->lyr[i]->swcBulk_min, s->lyr[i]), + SW_SWRC_SWCtoSWP(s->lyr[i]->swcBulk_init, s->lyr[i]) + ); + } + LogError( + logfp, + LOGNOTE, + "\nSoil Water Retention Curve:\n---------------------------\n" + ); + LogError( + logfp, + LOGNOTE, + " SWRC type: %d (%s)\n", + s->site_swrc_type, + s->site_swrc_name + ); + LogError( + logfp, + LOGNOTE, + " PTF type: %d (%s)\n", + s->site_ptf_type, + s->site_ptf_name + ); + + LogError( + logfp, + LOGNOTE, + " Lyr Param1 Param2 Param3 Param4 Param5 Param6\n" + ); + ForEachSoilLayer(i) + { + LogError( + logfp, + LOGNOTE, + " %3d%11.4f%11.4f%11.4f%11.4f%11.4f%11.4f\n", + i + 1, + s->lyr[i]->swrcp[0], + s->lyr[i]->swrcp[1], + s->lyr[i]->swrcp[2], + s->lyr[i]->swrcp[3], + s->lyr[i]->swrcp[4], + s->lyr[i]->swrcp[5] + ); } - LogError(logfp, LOGNOTE, "\n------------ End of Site Parameters ------------------\n"); - //fflush(logfp); + + LogError( + logfp, + LOGNOTE, + "\n------------ End of Site Parameters ------------------\n" + ); + //fflush(logfp); } #ifdef DEBUG_MEM -#include "myMemory.h" +#include "include/myMemory.h" /*======================================================*/ void SW_SIT_SetMemoryRefs( void) { /* when debugging memory problems, use the bookkeeping diff --git a/SW_Sky.c b/src/SW_Sky.c similarity index 80% rename from SW_Sky.c rename to src/SW_Sky.c index 1d39c8f97..ad0128f29 100644 --- a/SW_Sky.c +++ b/src/SW_Sky.c @@ -23,14 +23,14 @@ #include #include -#include "generic.h" -#include "Times.h" -#include "filefuncs.h" -#include "SW_Defines.h" -#include "SW_Files.h" -#include "SW_Model.h" // externs SW_Model -#include "SW_Sky.h" -#include "SW_Weather.h" // externs SW_Weather +#include "include/generic.h" +#include "include/Times.h" +#include "include/filefuncs.h" +#include "include/SW_Defines.h" +#include "include/SW_Files.h" +#include "include/SW_Model.h" // externs SW_Model +#include "include/SW_Sky.h" +#include "include/SW_Weather.h" // externs SW_Weather @@ -98,7 +98,7 @@ void SW_SKY_read(void) { if (x < 12) { CloseFile(&f); - sprintf(errstr, "%s : invalid record %d.\n", MyFileName, lineno); + snprintf(errstr, MAX_ERROR, "%s : invalid record %d.\n", MyFileName, lineno); LogError(logfp, LOGFATAL, errstr); } @@ -109,33 +109,6 @@ void SW_SKY_read(void) { CloseFile(&f); } -/** @brief Scale mean monthly climate values -*/ -void SW_SKY_init_run(void) { - TimeInt mon; - SW_SKY *v = &SW_Sky; - SW_WEATHER *w = &SW_Weather; - - for (mon = Jan; mon <= Dec; mon++) - { - v->cloudcov[mon] = fmin( - 100., - fmax(0.0, w->scale_skyCover[mon] + v->cloudcov[mon]) - ); - - v->windspeed[mon] = fmax( - 0.0, - w->scale_wind[mon] * v->windspeed[mon] - ); - - v->r_humidity[mon] = fmin( - 100., - fmax(0.0, w->scale_rH[mon] + v->r_humidity[mon]) - ); - } -} - - /** @brief Interpolate monthly input values to daily records (depends on "current" year) @@ -146,15 +119,13 @@ void SW_SKY_init_run(void) { void SW_SKY_new_year(void) { TimeInt year = SW_Model.year; SW_SKY *v = &SW_Sky; + Bool interpAsBase1 = swTRUE; /* We only need to re-calculate values if this is first year or if previous year was different from current year in leap/noleap status */ if (year == SW_Model.startyr || isleapyear(year) != isleapyear(year - 1)) { - interpolate_monthlyValues(v->cloudcov, v->cloudcov_daily); - interpolate_monthlyValues(v->windspeed, v->windspeed_daily); - interpolate_monthlyValues(v->r_humidity, v->r_humidity_daily); - interpolate_monthlyValues(v->snow_density, v->snow_density_daily); + interpolate_monthlyValues(v->snow_density, interpAsBase1, v->snow_density_daily); } } diff --git a/SW_SoilWater.c b/src/SW_SoilWater.c similarity index 61% rename from SW_SoilWater.c rename to src/SW_SoilWater.c index 2b952ac36..d40309c66 100644 --- a/SW_SoilWater.c +++ b/src/SW_SoilWater.c @@ -41,22 +41,22 @@ #include #include #include -#include "generic.h" -#include "filefuncs.h" -#include "myMemory.h" -#include "SW_Defines.h" -#include "SW_Files.h" -#include "SW_Model.h" // externs SW_Model -#include "SW_Site.h" // externs SW_Site -#include "SW_Flow.h" -#include "SW_SoilWater.h" -#include "SW_VegProd.h" // externs SW_VegProd +#include "include/generic.h" +#include "include/filefuncs.h" +#include "include/myMemory.h" +#include "include/SW_Defines.h" +#include "include/SW_Files.h" +#include "include/SW_Model.h" // externs SW_Model +#include "include/SW_Site.h" // externs SW_Site +#include "include/SW_Flow.h" +#include "include/SW_SoilWater.h" +#include "include/SW_VegProd.h" // externs SW_VegProd #ifdef SWDEBUG - #include "SW_Weather.h" // externs SW_Weather + #include "include/SW_Weather.h" // externs SW_Weather #endif #ifdef RSOILWAT - #include "../rSW_SoilWater.h" // for onSet_SW_SWC_hist() - #include "../SW_R_lib.h" // externs `useFiles` + #include "rSW_SoilWater.h" // for onSet_SW_SWC_hist() + #include "SW_R_lib.h" // externs `useFiles` #endif @@ -113,6 +113,237 @@ static void _reset_swc(void) { } +/** + @brief Convert soil water tension to volumetric water content using + FXW Soil Water Retention Curve + \cite fredlund1994CGJa, \cite wang2018wrr + + @note + This is the internal, bare-bones version of FXW, + use `SWRC_SWPtoSWC_FXW()` instead. + + @param[in] phi Pressure head (water tension) [cm of H20] + @param[in] *swrcp Vector of SWRC parameters + + @return Volumetric soil water content of the matric soil [cm / cm] +*/ +static double FXW_phi_to_theta( + double phi, + double *swrcp +) { + double tmp, res, S_e, C_f; + + if (GE(phi, FXW_h0)) { + res = 0.; + + } else { + // Relative saturation function: Rudiyanto et al. 2021: eq. 3 + tmp = pow(fabs(swrcp[1] * phi), swrcp[2]); // `pow()` because phi >= 0 + S_e = powe(log(swE + tmp), -swrcp[3]); + + // Correction factor: Rudiyanto et al. 2021: eq. 4 (with hr = 1500 cm) + // log(1. + 6.3e6 / 1500.) = 8.34307787116938; + C_f = 1. - log(1. + phi / FXW_hr) / 8.34307787116938; + + // Calculate matric theta [cm / cm] which is within [0, theta_sat] + // Rudiyanto et al. 2021: eq. 1 + res = swrcp[0] * S_e * C_f; + } + + // matric theta [cm / cm] + return res; +} + + +/** + \brief Interpolate, Truncate, Project (ITP) method (\cite oliveira2021ATMS) to solve FXW at a given water content + + We estimate `phi` by finding a root of + `f(phi) = theta - FXW(phi) = 0` + with + `a < b && f(a) < 0 && f(b) > 0` + + FXW SWRC (\cite fredlund1994CGJa, \cite wang2018wrr) is defined as + `theta = theta_sat * S_e(phi) * C_f(phi)` + + where + - `phi` is water tension (pressure head) [cm of H20], + - `theta` is volumetric water content [cm / cm], + - `theta_sat` is saturated water content [cm / cm], + - `S_e` is the relative saturation function [-], and + - `C_f` is a correction factor [-]. + + We choose starting values to solve `f(phi)` + - `a = 0` for which `f(a) = theta - theta_sat < 0 (for theta < theta_sat)` + - `b = FXW_h0` for which `f(b) = theta - 0 > 0 (for theta > 0)` + + [Additional info on the ITP method can be found here](https://en.wikipedia.org/wiki/ITP_method) + + + \param[in] theta Volumetric soil water content of the matric soil [cm / cm] + \param[in] *swrcp Vector of FXW SWRC parameters + + \return Water tension [bar] corresponding to theta +*/ + +static double itp_FXW_for_phi(double theta, double *swrcp) { + double + tol2 = 2e-9, // tolerance convergence + a = 0., // lower bound of bracket: a < b && f(a) < 0 + b = FXW_h0, // upper bound of bracket: b > a && f(b) > 0 + diff_ba = FXW_h0, diff_hf, + x_f, x_t, x_itp, + y_a, y_b, y_itp, + k1, k2, + x_half, + r, + delta, + sigma, + phi; + int + j = 0, n0, n_max; + + #ifdef SWDEBUG + short debug = 0; + #endif + + + // Evaluate at starting bracket (and checks) + y_a = theta - FXW_phi_to_theta(a, swrcp); + y_b = theta - FXW_phi_to_theta(b, swrcp); + + + // Set hyper-parameters + k1 = 3.174603e-08; // 0 < k1 = 0.2 / (b - a) < inf + /* + k1 = 2. converges in about 31-33 iterations + k1 = 0.2 converges in 28-30 iterations + k1 = 2.e-2 converges in 25-27 iterations + k1 = 2.e-3 converges in 22-24 iterations + k1 = 2.e-4 converges in about 20 iterations but fails for some + k1 = 2.e-6 converges in about 14 iterations or near n_max or fails + k1 = 0.2 / (b - a) = 3.174603e-08 (suggested value) converges + in about 8 iterations (for very dry soils) or near n_max = 53 or fails + */ + k1 = 2.e-3; + k2 = 2.; // 1 <= k2 < (3 + sqrt(5))/2 + n0 = 1; // 0 < n0 < inf + + // Upper limit of iterations before convergence + n_max = (int) ceil(log2(diff_ba / tol2)) + n0; + + #ifdef SWDEBUG + if (debug) { + swprintf( + "\nitp_FXW_for_phi(theta=%f): init: " + "f(a)=%f, f(b)=%f, n_max=%d, k1=%f\n", + theta, y_a, y_b, n_max, k1 + ); + } + #endif + + + // Iteration + do { + x_half = (a + b) / 2.; + + // Calculate parameters + r = (tol2 * exp2(n_max - j) - diff_ba) / 2.; + delta = k1 * pow(diff_ba, k2); + + // Interpolation + x_f = (y_b * a - y_a * b) / (y_b - y_a); + + // Truncation + diff_hf = x_half - x_f; + if (ZRO(diff_hf)) { + sigma = 0.; // `copysign()` returns -1 or +1 but never 0 + } else { + sigma = copysign(1., diff_hf); + } + + x_t = (delta <= fabs(diff_hf)) ? (x_f + sigma * delta) : x_half; + + // Projection + x_itp = (fabs(x_t - x_half) <= r) ? x_t : (x_half - sigma * r); + + // Evaluate function + y_itp = theta - FXW_phi_to_theta(x_itp, swrcp); + + #ifdef SWDEBUG + if (debug) { + swprintf( + "j=%d:" + "\ta=%f, b=%f, y_a=%f, y_b=%f,\n" + "\tr=%f, delta=%f, x_half=%f, x_f=%f, x_t=%f (s=%.0f),\n" + "\tx_itp=%f, y_itp=%f\n", + j, + a, b, y_a, y_b, + r, delta, x_half, x_f, x_t, sigma, + x_itp, y_itp + ); + } + #endif + + // Update interval brackets + if (GT(y_itp, 0.)) { + b = x_itp; + y_b = y_itp; + diff_ba = b - a; + } else if (LT(y_itp, 0.)) { + a = x_itp; + y_a = y_itp; + diff_ba = b - a; + } else { + a = x_itp; + b = x_itp; + diff_ba = 0.; + } + + j++; + } while (diff_ba > tol2 && j <= n_max); + + phi = (a + b) / 2.; + + if (j > n_max + 1 || fabs(diff_ba) > tol2 || LT(phi, 0.) || GT(phi, FXW_h0)) { + // Error if not converged or if `phi` physically impossible + LogError( + logfp, + LOGERROR, + "itp_FXW_for_phi(theta = %f): convergence failed at phi = %.9f [cm H2O] " + "after %d iterations with b - a = %.9f - %.9f = %.9f !<= tol2 = %.9f.", + theta, phi, j, b, a, b - a, tol2 + ); + + phi = SW_MISSING; + + } else { + // convert [cm of H2O at 4 C] to [bar] + phi /= 1019.716; + } + + + #ifdef SWDEBUG + if (debug) { + if (!missing(phi)) { + swprintf( + "itp_FXW_for_phi() = phi = %f [bar]: converged in j=%d/%d steps\n", + phi, j, n_max + ); + } else { + swprintf( + "itp_FXW_for_phi(): failed to converged in j=%d/%d steps\n", + j, n_max + ); + + } + } + #endif + + return phi; +} + + /* =================================================== */ /* Global Function Definitions */ /* --------------------------------------------------- */ @@ -125,7 +356,7 @@ void SW_WaterBalance_Checks(void) IntUS i, k; int debugi[N_WBCHECKS] = {1, 1, 1, 1, 1, 1, 1, 1, 1}; // print output for each check yes/no - char flag[15]; + char flag[16]; RealD Etotal, Etotalsurf, Etotalint, Eponded, Elitter, Esnow, Esoil = 0., Eveg = 0., Ttotal = 0., Ttotalj[MAX_LAYERS], @@ -194,7 +425,7 @@ void SW_WaterBalance_Checks(void) runoff = w->snowRunoff + w->surfaceRunoff; runon = w->surfaceRunon; snowmelt = w->snowmelt; - rain = w->now.rain[Today]; + rain = w->now.rain; arriving_water = rain + snowmelt + runon; @@ -213,7 +444,7 @@ void SW_WaterBalance_Checks(void) } if (debug) { - sprintf(flag, "WB (%d-%d)", SW_Model.year, SW_Model.doy); + snprintf(flag, sizeof flag, "WB (%d-%d)", SW_Model.year, SW_Model.doy); } @@ -810,7 +1041,7 @@ void _read_swc_hist(TimeInt year) { RealF swc, st_err; char fname[MAX_FILENAMESIZE]; - sprintf(fname, "%s.%4d", v->hist.file_prefix, year); + snprintf(fname, MAX_FILENAMESIZE, "%s.%4d", v->hist.file_prefix, year); if (!FileExists(fname)) { LogError(logfp, LOGWARN, "Historical SWC file %s not found.", fname); @@ -1011,178 +1242,589 @@ RealD SW_SnowDepth(RealD SWE, RealD snowdensity) { } } + +/** + @brief Convert soil water content to soil water potential using + specified soil water retention curve (SWRC) + + SOILWAT2 convenience wrapper for `SWRC_SWCtoSWP()`. + + See #swrc2str() for implemented SWRCs. + + @param[in] swcBulk Soil water content in the layer [cm] + @param[in] *lyr Soil information including + SWRC type, SWRC parameters, + coarse fragments (e.g., gravel), and soil layer width. + + @return Soil water potential [-bar] +*/ +RealD SW_SWRC_SWCtoSWP(RealD swcBulk, SW_LAYER_INFO *lyr) { + return SWRC_SWCtoSWP( + swcBulk, + lyr->swrc_type, + lyr->swrcp, + lyr->fractionVolBulk_gravel, + lyr->width, + LOGFATAL + ); +} + /** - @brief Calculates the soil water potential from soil water content of the - n-th soil layer. + @brief Convert soil water content to soil water potential using + specified soil water retention curve (SWRC) - The equation and its coefficients are based on a - paper by Cosby,Hornberger,Clapp,Ginn, in WATER RESOURCES RESEARCH - June 1984. Moisture retention data was fit to the power function. + See #swrc2str() for implemented SWRCs. The code assumes the following conditions: - * checked by `SW_SIT_init_run()` - * width > 0 - * fractionGravel, sand, clay, and sand + clay in [0, 1] - * checked by function `water_eqn()` - * thetasMatric > 0 - * bMatric != 0 - - @param fractionGravel Fraction of soil containing gravel. - @param swcBulk Soilwater content of the current layer (cm/layer) - @param n Layer number to index the **lyr pointer - - @return soil water potential -**/ - -RealD SW_SWCbulk2SWPmatric(RealD fractionGravel, RealD swcBulk, LyrIndex n) { -/********************************************************************** - -HISTORY: - DATE: April 2, 1992 - 9/1/92 (SLC) if swc comes in as zero, set swpotentl to - upperbnd. (Previously, we flagged this - as an error, and set swpotentl to zero). - - 27-Aug-03 (cwb) removed the upperbnd business. Except for - missing values, swc < 0 is impossible, so it's an error, - and the previous limit of swp to 80 seems unreasonable. - return 0.0 if input value is MISSING - - These are the values for each layer obtained via lyr[n]: - width - width of current soil layer - psisMatric - "saturation" matric potential - thetasMatric - saturated moisture content. - bMatric - see equation below. - swc_lim - limit for matric potential - - LOCAL VARIABLES: - theta1 - volumetric soil water content - - DEFINED CONSTANTS: - barconv - conversion factor from bars to cm water. (i.e. - 1 bar = 1024cm water) - - COMMENT: - See the routine "watreqn" for a description of how the variables - psisMatric, bMatric, binverseMatric, thetasMatric are initialized - **********************************************************************/ - - SW_LAYER_INFO *lyr = SW_Site.lyr[n]; - RealD theta1, theta2, swp = .0; - - if (missing(swcBulk) || ZRO(swcBulk)) - return 0.0; - - if (GT(swcBulk, 0.0)) { - // we have soil moisture - - // calculate matric VWC [cm / cm %] from bulk VWC - theta1 = (swcBulk / lyr->width) * 100. / (1. - fractionGravel); - - // calculate (VWC / VWC(saturated)) ^ b - theta2 = powe(theta1 / lyr->thetasMatric, lyr->bMatric); - - if (isnan(theta2) || ZRO(theta2)) { - LogError(logfp, LOGFATAL, "SW_SWCbulk2SWPmatric(): Year = %d, DOY=%d, Layer = %d:\n" - "\tinvalid value of (theta / theta(saturated)) ^ b = %f (must be != 0)\n", - SW_Model.year, SW_Model.doy, n, theta2); + - checked by `SW_SIT_init_run()` + - width > 0 + - fractionGravel, sand, clay, and sand + clay in [0, 1] + - SWRC parameters checked by `SWRC_check_parameters()`. + + @param[in] swcBulk Soil water content in the layer [cm] + @param[in] swrc_type Identification number of selected SWRC + @param[in] *swrcp Vector of SWRC parameters + @param[in] gravel Coarse fragments (> 2 mm; e.g., gravel) + of the whole soil [m3/m3] + @param[in] width Soil layer width [cm] + @param[in] errmode An error code passed to `LogError()`. + SOILWAT2 uses `LOGFATAL` and fails but + other applications may want to warn only (`LOGWARN`) and return. + + @return Soil water potential [-bar] +*/ +double SWRC_SWCtoSWP( + double swcBulk, + unsigned int swrc_type, + double *swrcp, + double gravel, + double width, + const int errmode +) { + double res = SW_MISSING; + + if (LT(swcBulk, 0.) || EQ(gravel, 1.) || LE(width, 0.)) { + LogError( + logfp, + errmode, + "SWRC_SWCtoSWP(): invalid SWC = %.4f (must be >= 0)\n", + swcBulk + ); + + return res; + } + + switch (swrc_type) { + case sw_Campbell1974: + res = SWRC_SWCtoSWP_Campbell1974( + swcBulk, swrcp, gravel, width, + errmode + ); + break; + + case sw_vanGenuchten1980: + res = SWRC_SWCtoSWP_vanGenuchten1980( + swcBulk, swrcp, gravel, width, + errmode + ); + break; + + case sw_FXW: + res = SWRC_SWCtoSWP_FXW( + swcBulk, swrcp, gravel, width, + errmode + ); + break; + + default: + LogError( + logfp, + errmode, + "SWRC (type %d) is not implemented.", + swrc_type + ); + break; + } + + return res; +} + + +/** + @brief Convert soil water content to soil water potential using + Campbell's 1974 \cite Campbell1974 Soil Water Retention Curve + + Parameters are explained in `SWRC_check_parameters_for_Campbell1974()`. + + @note + This function was previously named `SW_SWCbulk2SWPmatric()`. + + @note + `SWRC_SWPtoSWC_Campbell1974()` and `SWRC_SWCtoSWP_Campbell1974()` + are the inverse of each other + for `(phi, theta)` between `(swrcp[0], `theta` at `swrcp[0]`)` + and `(infinity, 0)`. + + @note + The function has a discontinuity at saturated water content for which + the matric potential at the "air-entry suction" point (see `swrcp[0]`) + is returned whereas 0 bar is returned for larger values. + + @param[in] swcBulk Soil water content in the layer [cm] + @param[in] *swrcp Vector of SWRC parameters + @param[in] gravel Coarse fragments (> 2 mm; e.g., gravel) + of the whole soil [m3/m3] + @param[in] width Soil layer width [cm] + @param[in] errmode An error code passed to `LogError()`. + SOILWAT2 uses `LOGFATAL` and fails but + other applications may want to warn only (`LOGWARN`) and return. + + @return Soil water potential [-bar] +*/ +double SWRC_SWCtoSWP_Campbell1974( + double swcBulk, + double *swrcp, + double gravel, + double width, + const int errmode +) { + // assume that we have soil moisture + double theta, tmp, res; + + // convert bulk SWC [cm] to theta = matric VWC [cm / cm] + theta = swcBulk / (width * (1. - gravel)); + + if (GT(theta, swrcp[1])) { + // `theta` should not become larger than `theta_sat`; + // however, "Cosby1984AndOthers" does not use `swrcp[1]` for `theta_sat` + // which can lead to inconsistencies; thus, + // we return with 0 instead of, correctly, with errmode and SW_MISSING + res = 0.; + + } else { + // calculate (theta / theta_s) ^ b + tmp = powe(theta / swrcp[1], swrcp[2]); + + if (LE(tmp, 0.)) { + LogError( + logfp, + errmode, + "SWRC_SWCtoSWP_Campbell1974(): invalid value of\n" + "\t(theta / theta(saturated)) ^ b = (%f / %f) ^ %f =\n" + "\t= %f (must be > 0)\n", + theta, swrcp[1], swrcp[2], tmp + ); + + return SW_MISSING; + } + + res = swrcp[0] / tmp; + } + + // convert [cm of H20; SOILWAT2 legacy value] to [bar] + return res / 1024.; +} + + +/** + @brief Convert soil water content to soil water potential using + van Genuchten 1980 \cite vanGenuchten1980 Soil Water Retention Curve + + Parameters are explained in `SWRC_check_parameters_for_vanGenuchten1980()`. + + @note + `SWRC_SWPtoSWC_vanGenuchten1980()` and `SWRC_SWCtoSWP_vanGenuchten1980()` + are the inverse of each other + for `(phi, theta)` between `(0, theta_sat)` and `(infinity, theta_min)`. + + @param[in] swcBulk Soil water content in the layer [cm] + @param[in] *swrcp Vector of SWRC parameters + @param[in] gravel Coarse fragments (> 2 mm; e.g., gravel) + of the whole soil [m3/m3] + @param[in] width Soil layer width [cm] + @param[in] errmode An error code passed to `LogError()`. + SOILWAT2 uses `LOGFATAL` and fails but + other applications may want to warn only (`LOGWARN`) and return. + + @return Soil water potential [-bar] +*/ +double SWRC_SWCtoSWP_vanGenuchten1980( + double swcBulk, + double *swrcp, + double gravel, + double width, + const int errmode +) { + double res, tmp, theta; + + // convert bulk SWC [cm] to theta = matric VWC [cm / cm] + theta = swcBulk / (width * (1. - gravel)); + + // calculate if theta in ]theta_min, theta_sat] + if (GT(theta, swrcp[0])) { + if (LT(theta, swrcp[1])) { + // calculate inverse of normalized theta + tmp = (swrcp[1] - swrcp[0]) / (theta - swrcp[0]); + + // calculate tension [cm of H20] + tmp = powe(tmp, 1. / (1. - 1. / swrcp[3])); // tmp values are >= 1 + res = pow(-1. + tmp, 1. / swrcp[3]) / swrcp[2]; // `pow()` because x >= 0 + + // convert [cm of H2O at 4 C; value from `soilDB::KSSL_VG_model()`] to [bar] + res /= 1019.716; + + } else if (EQ(theta, swrcp[1])) { + // theta is theta_sat + res = 0; + } else { - swp = lyr->psisMatric / theta2 / BARCONV; + // theta is > theta_sat + LogError( + logfp, + errmode, + "SWRC_SWCtoSWP_vanGenuchten1980(): invalid value of\n" + "\ttheta = %f (must be <= theta_sat = %f)\n", + theta, swrcp[1] + ); + + res = SW_MISSING; } } else { - LogError(logfp, LOGFATAL, "Invalid SWC value (%.4f) in SW_SWC_swc2potential.\n" - " Year = %d, DOY=%d, Layer = %d\n", swcBulk, SW_Model.year, SW_Model.doy, n); + // theta is <= theta_min + LogError( + logfp, + errmode, + "SWRC_SWCtoSWP_vanGenuchten1980(): invalid value of\n" + "\ttheta = %f (must be > theta_min = %f)\n", + theta, swrcp[0] + ); + + res = SW_MISSING; } - return swp; + return res; } + + /** -@brief Convert soil water potential to bulk volumetric water content. + @brief Convert soil water content to soil water potential using + FXW Soil Water Retention Curve + \cite fredlund1994CGJa, \cite wang2018wrr -@param fractionGravel Fraction of soil containing gravel, percentage. -@param swpMatric lyr->psisMatric calculated in water equation function -@param n Layer of soil. + Parameters are explained in `SWRC_check_parameters_for_FXW()`. -@return Volumentric water content (cm H2O/cm SOIL). -**/ + @note + `SWRC_SWPtoSWC_FXW()` and `SWRC_SWCtoSWP_FXW()` + are the inverse of each other + for `(phi, theta)` between `(0, theta_sat)` and `(6.3 x 10^6 cm, 0)`. + @param[in] swcBulk Soil water content in the layer [cm] + @param[in] *swrcp Vector of SWRC parameters + @param[in] gravel Coarse fragments (> 2 mm; e.g., gravel) + of the whole soil [m3/m3] + @param[in] width Soil layer width [cm] + @param[in] errmode An error code passed to `LogError()`. + SOILWAT2 uses `LOGFATAL` and fails but + other applications may want to warn only (`LOGWARN`) and return. + + @return Soil water potential [-bar] +*/ +double SWRC_SWCtoSWP_FXW( + double swcBulk, + double *swrcp, + double gravel, + double width, + const int errmode +) { + double res, theta; + + // convert bulk SWC [cm] to theta = matric VWC [cm / cm] + theta = swcBulk / (width * (1. - gravel)); + + // calculate if theta in [0, theta_sat] + if (GE(theta, 0.)) { + if (LT(theta, swrcp[0])) { + // calculate tension = phi [bar] + res = itp_FXW_for_phi(theta, swrcp); + + } else if (EQ(theta, swrcp[0])) { + // theta is theta_sat + res = 0.; + + } else { + // theta is > theta_sat + LogError( + logfp, + errmode, + "SWRC_SWCtoSWP_FXW(): invalid value of\n" + "\ttheta = %f (must be <= theta_sat = %f)\n", + theta, swrcp[0] + ); + + res = SW_MISSING; + } + + } else { + // theta is < 0 + LogError( + logfp, + errmode, + "SWRC_SWCtoSWP_FXW(): invalid value of\n" + "\ttheta = %f (must be >= 0)\n", + theta + ); + + res = SW_MISSING; + } + + return res; +} -RealD SW_SWPmatric2VWCBulk(RealD fractionGravel, RealD swpMatric, LyrIndex n) { /** - History: - 27-Aug-03 (cwb) moved from the Site module. -**/ - - SW_LAYER_INFO *lyr = SW_Site.lyr[n]; - RealD t, p; - swpMatric *= BARCONV; - p = powe(lyr->psisMatric / swpMatric, lyr->binverseMatric); // lyr->psisMatric calculated in water equation function | todo: check to make sure these are calculated before - t = lyr->thetasMatric * p * 0.01 * (1 - fractionGravel); - return (t); + @brief Convert soil water potential to soil water content using + specified soil water retention curve (SWRC) + + SOILWAT2 convenience wrapper for `SWRC_SWPtoSWC()`. + + See #swrc2str() for implemented SWRCs. + + @param[in] swpMatric Soil water potential [-bar] + @param[in] *lyr Soil information including + SWRC type, SWRC parameters, + coarse fragments (e.g., gravel), and soil layer width. + + @return Soil water content in the layer [cm] +*/ +RealD SW_SWRC_SWPtoSWC(RealD swpMatric, SW_LAYER_INFO *lyr) { + return SWRC_SWPtoSWC( + swpMatric, + lyr->swrc_type, + lyr->swrcp, + lyr->fractionVolBulk_gravel, + lyr->width, + LOGFATAL + ); } + /** -@brief Calculates 'Brooks-Corey' residual volumetric soil water. + @brief Convert soil water potential to soil water content using + specified soil water retention curve (SWRC) -Equations based on: Rawls WJ, Brakensiek DL (1985) Prediction of soil water properties - for hydrological modeling, based on @cite ASCE1985 + See #swrc2str() for implemented SWRCs. -@param fractionGravel Fraction of soil consisting of gravel, percentage. -@param sand Fraction of soil consisting of sand, percentage. -@param clay Fraction of soil consisting of clay, percentage. -@param porosity Fraction of Soil porosity as the saturated VWC, percentage. + The code assumes the following conditions: + - checked by `SW_SIT_init_run()` + - width > 0 + - fractionGravel, sand, clay, and sand + clay in [0, 1] + - SWRC parameters checked by `SWRC_check_parameters()`. + + @param[in] swpMatric Soil water potential [-bar] + @param[in] swrc_type Identification number of selected SWRC + @param[in] *swrcp Vector of SWRC parameters + @param[in] gravel Coarse fragments (> 2 mm; e.g., gravel) + of the whole soil [m3/m3] + @param[in] width Soil layer width [cm] + @param[in] errmode An error code passed to `LogError()`. + SOILWAT2 uses `LOGFATAL` and fails but + other applications may want to warn only (`LOGWARN`) and return. + + @return Soil water content in the layer [cm] +*/ +double SWRC_SWPtoSWC( + double swpMatric, + unsigned int swrc_type, + double *swrcp, + double gravel, + double width, + const int errmode +) { + double res = SW_MISSING; + + if (LT(swpMatric, 0.)) { + LogError( + logfp, + errmode, + "SWRC_SWPtoSWC(): invalid SWP = %.4f (must be >= 0)\n", + swpMatric + ); + + return res; + } -@returns Residual volumetric soil water (cm/cm) -**/ + switch (swrc_type) { + case sw_Campbell1974: + res = SWRC_SWPtoSWC_Campbell1974(swpMatric, swrcp, gravel, width); + break; -RealD SW_VWCBulkRes(RealD fractionGravel, RealD sand, RealD clay, RealD porosity) { -/*--------------------- -History: - 02/03/2012 (drs) calculates 'Brooks-Corey' residual volumetric soil water based on Rawls WJ, Brakensiek DL (1985) Prediction of soil water properties for hydrological modeling. In Watershed management in the Eighties (eds Jones EB, Ward TJ), pp. 293-299. American Society of Civil Engineers, New York. - however, equation is only valid if (0.05 < clay < 0.6) & (0.05 < sand < 0.7) + case sw_vanGenuchten1980: + res = SWRC_SWPtoSWC_vanGenuchten1980(swpMatric, swrcp, gravel, width); + break; ----------------------*/ + case sw_FXW: + res = SWRC_SWPtoSWC_FXW(swpMatric, swrcp, gravel, width); + break; - if (clay < .05 || clay > .6 || sand < .05 || sand > .7) { - LogError( - logfp, - LOGWARN, - "Sand and/or clay values out of valid range, simulation outputs may differ." - ); - return SW_MISSING; + default: + LogError( + logfp, + errmode, + "SWRC (type %d) is not implemented.", + swrc_type + ); + break; + } - } else { - RealD res; - sand *= 100.; - clay *= 100.; - - res = (1. - fractionGravel) * ( - - 0.0182482 \ - + 0.00087269 * sand \ - + 0.00513488 * clay \ - + 0.02939286 * porosity \ - - 0.00015395 * squared(clay) \ - - 0.0010827 * sand * porosity \ - - 0.00018233 * squared(clay) * squared(porosity) \ - + 0.00030703 * squared(clay) * porosity \ - - 0.0023584 * squared(porosity) * clay - ); - - return (fmax(res, 0.)); - } + return res; +} + + +/** + @brief Convert soil water potential to soil water content using + Campbell's 1974 \cite Campbell1974 Soil Water Retention Curve + + Parameters are explained in `SWRC_check_parameters_for_Campbell1974()`. + + @note + This function was previously named `SW_SWPmatric2VWCBulk()`. + + @note + The function returns saturated water content if `swpMatric` is at or below + the "air-entry suction" point (see `swrcp[0]`). + + @note + `SWRC_SWPtoSWC_Campbell1974()` and `SWRC_SWCtoSWP_Campbell1974()` + are the inverse of each other + for `(phi, theta)` between `(swrcp[0], `theta` at `swrcp[0]`)` + and `(infinity, 0)`. + + @param[in] swpMatric Soil water potential [-bar] + @param[in] *swrcp Vector of SWRC parameters + @param[in] gravel Coarse fragments (> 2 mm; e.g., gravel) + of the whole soil [m3/m3] + @param[in] width Soil layer width [cm] + + @return Soil water content in the layer [cm] +*/ +double SWRC_SWPtoSWC_Campbell1974( + double swpMatric, + double *swrcp, + double gravel, + double width +) { + double phi, res; + + // convert SWP [-bar] to phi [cm of H20; SOILWAT2 legacy value] + phi = swpMatric * 1024.; + + if (LT(phi, swrcp[0])) { + res = swrcp[1]; // theta_sat + + } else { + // calculate matric theta [cm / cm] + // within [0, theta_sat] because `phi` > swrcp[0] + res = swrcp[1] * powe(swrcp[0] / phi, 1. / swrcp[2]); + } + + // convert matric theta [cm / cm] to bulk SWC [cm] + return (1. - gravel) * width * res; } + +/** + @brief Convert soil water potential to soil water content using + van Genuchten 1980 \cite vanGenuchten1980 Soil Water Retention Curve + + Parameters are explained in `SWRC_check_parameters_for_vanGenuchten1980()`. + + @note + `SWRC_SWPtoSWC_vanGenuchten1980()` and `SWRC_SWCtoSWP_vanGenuchten1980()` + are the inverse of each other + for `(phi, theta)` between `(0, theta_sat)` and `(infinity, theta_min)`. + + @param[in] swpMatric Soil water potential [-bar] + @param[in] *swrcp Vector of SWRC parameters + @param[in] gravel Coarse fragments (> 2 mm; e.g., gravel) + of the whole soil [m3/m3] + @param[in] width Soil layer width [cm] + + @return Soil water content in the layer [cm] +*/ +double SWRC_SWPtoSWC_vanGenuchten1980( + double swpMatric, + double *swrcp, + double gravel, + double width +) { + double phi, tmp, res; + + // convert SWP [-bar] to phi [cm of H2O at 4 C; + // value from `soilDB::KSSL_VG_model()`] + phi = swpMatric * 1019.716; + + tmp = powe(swrcp[2] * phi, swrcp[3]); + tmp = powe(1. + tmp, 1. - 1. / swrcp[3]); + + // calculate matric theta [cm / cm] which is within [theta_min, theta_sat] + res = swrcp[0] + (swrcp[1] - swrcp[0]) / tmp; + + // convert matric theta [cm / cm] to bulk SWC [cm] + return (1. - gravel) * width * res; +} + + +/** + @brief Convert soil water potential to soil water content using + FXW Soil Water Retention Curve + \cite fredlund1994CGJa, \cite wang2018wrr + + Parameters are explained in `SWRC_check_parameters_for_FXW()`. + + @note + `SWRC_SWPtoSWC_FXW()` and `SWRC_SWCtoSWP_FXW()` + are the inverse of each other + for `(phi, theta)` between `(0, theta_sat)` and `(6.3 x 10^6 cm, 0)`. + + @param[in] swpMatric Soil water potential [-bar] + @param[in] *swrcp Vector of SWRC parameters + @param[in] gravel Coarse fragments (> 2 mm; e.g., gravel) + of the whole soil [m3/m3] + @param[in] width Soil layer width [cm] + + @return Soil water content in the layer [cm] +*/ +double SWRC_SWPtoSWC_FXW( + double swpMatric, + double *swrcp, + double gravel, + double width +) { + double phi, res; + + // convert SWP [-bar] to phi [cm of H2O at 4 C; + // value from `soilDB::KSSL_VG_model()`] + phi = swpMatric * 1019.716; + + res = FXW_phi_to_theta(phi, swrcp); + + // convert matric theta [cm / cm] to bulk SWC [cm] + return (1. - gravel) * width * res; +} + + + + /** @brief This routine sets the known memory refs in this module so they can be checked for leaks, etc. */ #ifdef DEBUG_MEM -#include "myMemory.h" +#include "include/myMemory.h" /*======================================================*/ void SW_SWC_SetMemoryRefs( void) { /* when debugging memory problems, use the bookkeeping diff --git a/SW_VegEstab.c b/src/SW_VegEstab.c similarity index 82% rename from SW_VegEstab.c rename to src/SW_VegEstab.c index 24179929d..b48462d52 100644 --- a/SW_VegEstab.c +++ b/src/SW_VegEstab.c @@ -35,18 +35,18 @@ #include #include #include -#include "generic.h" // externs `QuietMode`, `EchoInits` -#include "filefuncs.h" // externs `_firstfile`, `inbuf` -#include "myMemory.h" -#include "SW_Defines.h" -#include "SW_Files.h" -#include "SW_Site.h" // externs SW_Site -#include "SW_Times.h" -#include "SW_Model.h" // externs SW_Model -#include "SW_SoilWater.h" // externs SW_Soilwat -#include "SW_Weather.h" // externs SW_Weather -#include "SW_VegProd.h" // externs `key2veg[]` -#include "SW_VegEstab.h" +#include "include/generic.h" // externs `QuietMode`, `EchoInits` +#include "include/filefuncs.h" // externs `_firstfile`, `inbuf` +#include "include/myMemory.h" +#include "include/SW_Defines.h" +#include "include/SW_Files.h" +#include "include/SW_Site.h" // externs SW_Site +#include "include/SW_Times.h" +#include "include/SW_Model.h" // externs SW_Model +#include "include/SW_SoilWater.h" // externs SW_Soilwat +#include "include/SW_Weather.h" // externs SW_Weather +#include "include/SW_VegProd.h" // externs `key2veg[]` +#include "include/SW_VegEstab.h" @@ -288,11 +288,11 @@ void SW_VES_checkestab(void) { static void _checkit(TimeInt doy, unsigned int sppnum) { SW_VEGESTAB_INFO *v = SW_VegEstab.parms[sppnum]; - SW_WEATHER_2DAYS *wn = &SW_Weather.now; + SW_WEATHER_NOW *wn = &SW_Weather.now; SW_SOILWAT *sw = &SW_Soilwat; IntU i; - RealF avgtemp = wn->temp_avg[Today], /* avg of today's min/max temp */ + RealF avgtemp = wn->temp_avg, /* avg of today's min/max temp */ avgswc; /* avg_swc today */ if (doy == SW_Model.firstdoy) { @@ -485,14 +485,15 @@ void _spp_init(unsigned int sppnum) { /* The thetas and psis etc should be initialized by now */ /* because init_layers() must be called prior to this routine */ /* (see watereqn() ) */ - v->min_swc_germ = SW_SWPmatric2VWCBulk(lyr[0]->fractionVolBulk_gravel, v->bars[SW_GERM_BARS], 0) * lyr[0]->width; + v->min_swc_germ = SW_SWRC_SWPtoSWC(v->bars[SW_GERM_BARS], lyr[0]); /* due to possible differences in layer textures and widths, we need * to average the estab swc across the given layers to peoperly * compare the actual swc average in the checkit() routine */ v->min_swc_estab = 0.; - for (i = 0; i < v->estab_lyrs; i++) - v->min_swc_estab += SW_SWPmatric2VWCBulk(lyr[i]->fractionVolBulk_gravel, v->bars[SW_ESTAB_BARS], i) * lyr[i]->width; + for (i = 0; i < v->estab_lyrs; i++) { + v->min_swc_estab += SW_SWRC_SWPtoSWC(v->bars[SW_ESTAB_BARS], lyr[i]); + } v->min_swc_estab /= v->estab_lyrs; _sanity_check(sppnum); @@ -617,52 +618,63 @@ void _echo_VegEstab(void) { IntU i; char outstr[2048]; - sprintf(errstr, "\n=========================================================\n\n" - "Parameters for the SoilWat Vegetation Establishment Check.\n" - "----------------------------------------------------------\n" - "Number of species to be tested: %d\n", SW_VegEstab.count); + snprintf( + errstr, + MAX_ERROR, + "\n=========================================================\n\n" + "Parameters for the SoilWat Vegetation Establishment Check.\n" + "----------------------------------------------------------\n" + "Number of species to be tested: %d\n", + SW_VegEstab.count + ); strcpy(outstr, errstr); for (i = 0; i < SW_VegEstab.count; i++) { - sprintf( - errstr, "Species: %s (vegetation type %s [%d])\n----------------\n" - "Germination parameters:\n" - "\tMinimum SWP (bars) : -%.4f\n" - "\tMinimum SWC (cm/cm) : %.4f\n" - "\tMinimum SWC (cm/lyr): %.4f\n" - "\tMinimum temperature : %.1f\n" - "\tMaximum temperature : %.1f\n" - "\tFirst possible day : %d\n" - "\tLast possible day : %d\n" - "\tMinimum consecutive wet days (after first possible day): %d\n", - v[i]->sppname, - key2veg[v[i]->vegType], - v[i]->vegType, - v[i]->bars[SW_GERM_BARS], - v[i]->min_swc_germ / lyr[0]->width, - v[i]->min_swc_germ, - v[i]->min_temp_germ, - v[i]->max_temp_germ, - v[i]->min_pregerm_days, - v[i]->max_pregerm_days, - v[i]->min_wetdays_for_germ - ); - - sprintf(errstr, "Establishment parameters:\n" - "\tNumber of layers affecting successful establishment: %d\n" - "\tMinimum SWP (bars) : -%.4f\n" - "\tMinimum SWC (cm/layer) averaged across top %d layers: %.4f\n" - "\tMinimum temperature : %.1f\n" - "\tMaximum temperature : %.1f\n" - "\tMinimum number of days after germination : %d\n" - "\tMaximum number of days after germination : %d\n" - "\tMinimum consecutive wet days after germination: %d\n" - "\tMaximum consecutive dry days after germination: %d\n" - "---------------------------------------------------------------\n\n", - v[i]->estab_lyrs, v[i]->bars[SW_ESTAB_BARS], v[i]->estab_lyrs, - v[i]->min_swc_estab, v[i]->min_temp_estab, v[i]->max_temp_estab, - v[i]->min_days_germ2estab, v[i]->max_days_germ2estab, v[i]->min_wetdays_for_estab, - v[i]->max_drydays_postgerm); + snprintf( + errstr, + MAX_ERROR, + "Species: %s (vegetation type %s [%d])\n----------------\n" + "Germination parameters:\n" + "\tMinimum SWP (bars) : -%.4f\n" + "\tMinimum SWC (cm/cm) : %.4f\n" + "\tMinimum SWC (cm/lyr): %.4f\n" + "\tMinimum temperature : %.1f\n" + "\tMaximum temperature : %.1f\n" + "\tFirst possible day : %d\n" + "\tLast possible day : %d\n" + "\tMinimum consecutive wet days (after first possible day): %d\n", + v[i]->sppname, + key2veg[v[i]->vegType], + v[i]->vegType, + v[i]->bars[SW_GERM_BARS], + v[i]->min_swc_germ / lyr[0]->width, + v[i]->min_swc_germ, + v[i]->min_temp_germ, + v[i]->max_temp_germ, + v[i]->min_pregerm_days, + v[i]->max_pregerm_days, + v[i]->min_wetdays_for_germ + ); + + snprintf( + errstr, + MAX_ERROR, + "Establishment parameters:\n" + "\tNumber of layers affecting successful establishment: %d\n" + "\tMinimum SWP (bars) : -%.4f\n" + "\tMinimum SWC (cm/layer) averaged across top %d layers: %.4f\n" + "\tMinimum temperature : %.1f\n" + "\tMaximum temperature : %.1f\n" + "\tMinimum number of days after germination : %d\n" + "\tMaximum number of days after germination : %d\n" + "\tMinimum consecutive wet days after germination: %d\n" + "\tMaximum consecutive dry days after germination: %d\n" + "---------------------------------------------------------------\n\n", + v[i]->estab_lyrs, v[i]->bars[SW_ESTAB_BARS], v[i]->estab_lyrs, + v[i]->min_swc_estab, v[i]->min_temp_estab, v[i]->max_temp_estab, + v[i]->min_days_germ2estab, v[i]->max_days_germ2estab, v[i]->min_wetdays_for_estab, + v[i]->max_drydays_postgerm + ); strcat(outstr, errstr); } diff --git a/src/SW_VegProd.c b/src/SW_VegProd.c new file mode 100644 index 000000000..21b541c9e --- /dev/null +++ b/src/SW_VegProd.c @@ -0,0 +1,1496 @@ +/********************************************************/ +/********************************************************/ +/* Source file: Veg_Prod.c +Type: module +Application: SOILWAT - soilwater dynamics simulator +Purpose: Read / write and otherwise manage the model's +vegetation production parameter information. +History: +(8/28/01) -- INITIAL CODING - cwb +11/16/2010 (drs) added LAIforest, biofoliage_for, lai_conv_for, TypeGrassOrShrub, TypeForest to SW_VEGPROD +lai_live/biolive/total_agb include now LAIforest, respectively biofoliage_for +updated SW_VPD_read(), SW_VPD_init(), and _echo_inits() +increased length of char outstr[1000] to outstr[1500] because of increased echo +02/22/2011 (drs) added scan for litter_for to SW_VPD_read() +02/22/2011 (drs) added litter_for to SW_VegProd.litter and to SW_VegProd.tot_agb +02/22/2011 (drs) if TypeGrassOrShrub is turned off, then its biomass, litter, etc. values are set to 0 +08/22/2011 (drs) use variable veg_height [MAX_MONTHS] from SW_VEGPROD instead of static canopy_ht +09/08/2011 (drs) adapted SW_VPD_read() and SW_VPD_init() to reflect that now each vegetation type has own elements +09/08/2011 (drs) added input in SW_VPD_read() of tanfunc_t tr_shade_effects, and RealD shade_scale and shade_deadmax (they were previously hidden as constants in code in SW_Flow_lib.h) +09/08/2011 (drs) moved all input of hydraulic redistribution variables from SW_Site.c to SW_VPD_read() for each vegetation type +09/08/2011 (drs) added input in SW_VPD_read() of RealD veg_intPPT_a, veg_intPPT_b, veg_intPPT_c, veg_intPPT_d (they were previously hidden as constants in code in SW_Flow_lib.h) +09/09/2011 (drs) added input in SW_VPD_read() of RealD EsTpartitioning_param (it were previously hidden as constant in code in SW_Flow_lib.h) +09/09/2011 (drs) added input in SW_VPD_read() of RealD Es_param_limit (it was previously hidden as constant in code in SW_Flow_lib.h) +09/13/2011 (drs) added input in SW_VPD_read() of RealD litt_intPPT_a, litt_intPPT_b, litt_intPPT_c, litt_intPPT_d (they were previously hidden as constants in code in SW_Flow_lib.h) +09/13/2011 (drs) added input in SW_VPD_read() of RealD canopy_height_constant and updated SW_VPD_init() (as option: if > 0 then constant canopy height (cm) and overriding cnpy-tangens=f(biomass)) +09/15/2011 (drs) added input in SW_VPD_read() of RealD albedo +09/26/2011 (drs) added calls to Times.c:interpolate_monthlyValues() in SW_VPD_init() for each monthly input variable; replaced monthly loop with a daily loop for additional daily variables; adjusted _echo_inits() +10/17/2011 (drs) in SW_VPD_init(): v->veg[SW_TREES].total_agb_daily[doy] = v->veg[SW_TREES].litter_daily[doy] + v->veg[SW_TREES].biolive_daily[doy] instead of = v->veg[SW_TREES].litter_daily[doy] + v->veg[SW_TREES].biomass_daily[doy] to adjust for better scaling of potential bare-soil evaporation +02/04/2012 (drs) added input in SW_VPD_read() of RealD SWPcrit +01/29/2013 (clk) changed the read in to account for the extra fractional component in total vegetation, bare ground +added the variable RealF help_bareGround as a place holder for the sscanf call. +01/31/2013 (clk) changed the read in to account for the albedo for bare ground, storing the input in bare_cov.albedo +changed _echo_inits() to now display the bare ground components in logfile.log +06/27/2013 (drs) closed open files if LogError() with LOGFATAL is called in SW_VPD_read() +07/09/2013 (clk) added initialization of all the values of the new vegtype variable forb and forb.cov.fCover +*/ +/********************************************************/ +/********************************************************/ + +/* =================================================== */ +/* =================================================== */ +/* INCLUDES / DEFINES */ + +#include +#include +#include +#include +#include "include/generic.h" // externs `QuietMode`, `EchoInits` +#include "include/filefuncs.h" // externs `_firstfile`, `inbuf` +#include "include/myMemory.h" +#include "include/SW_Defines.h" +#include "include/SW_Files.h" +#include "include/SW_Times.h" +#include "include/SW_VegProd.h" +#include "include/SW_Model.h" // externs SW_Model +#include "include/SW_Weather.h" +#include "include/SW_Site.h" + + + +/* =================================================== */ +/* Global Variables */ +/* --------------------------------------------------- */ +SW_VEGPROD SW_VegProd; + +// key2veg must be in the same order as the indices to vegetation types defined in SW_Defines.h +char const *key2veg[NVEGTYPES] = {"Trees", "Shrubs", "Forbs", "Grasses"}; + + + +/* =================================================== */ +/* Local Variables */ +/* --------------------------------------------------- */ +static char *MyFileName; + + +/* =================================================== */ +/* Global Function Definitions */ +/* --------------------------------------------------- */ + +/** +@brief Reads file for SW_VegProd. +*/ +void SW_VPD_read(void) { + /* =================================================== */ + SW_VEGPROD *v = &SW_VegProd; + FILE *f; + TimeInt mon = Jan; + int x, k, lineno = 0, veg_method; + const int line_help = 28; // last case line number before monthly biomass densities + RealF help_veg[NVEGTYPES], help_bareGround, litt, biom, pctl, laic; + + MyFileName = SW_F_name(eVegProd); + f = OpenFile(MyFileName, "r"); + + while (GetALine(f, inbuf)) { + if (lineno++ < line_help) { + switch (lineno) { + case 1: + x = sscanf(inbuf, "%d", &veg_method); + if(x != 1) { + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in vegetation type components in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + v->veg_method = veg_method; + break; + /* fractions of vegetation types */ + case 2: + x = sscanf(inbuf, "%f %f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS], &help_bareGround); + if (x < NVEGTYPES + 1) { + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in vegetation type components in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].cov.fCover = help_veg[k]; + } + v->bare_cov.fCover = help_bareGround; + break; + + /* albedo */ + case 3: + x = sscanf(inbuf, "%f %f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS], &help_bareGround); + if (x < NVEGTYPES + 1) { + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in albedo values in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].cov.albedo = help_veg[k]; + } + v->bare_cov.albedo = help_bareGround; + break; + + /* canopy height */ + case 4: + x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS]); + if (x < NVEGTYPES) { + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in canopy xinflec in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].cnpy.xinflec = help_veg[k]; + } + break; + case 5: + x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS]); + if (x < NVEGTYPES) { + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in canopy yinflec in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].cnpy.yinflec = help_veg[k]; + } + break; + case 6: + x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS]); + if (x < NVEGTYPES) { + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in canopy range in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].cnpy.range = help_veg[k]; + } + break; + case 7: + x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS]); + if (x < NVEGTYPES) { + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in canopy slope in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].cnpy.slope = help_veg[k]; + } + break; + case 8: + x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS]); + if (x < NVEGTYPES) { + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in canopy height constant option in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].canopy_height_constant = help_veg[k]; + } + break; + + /* vegetation interception parameters */ + case 9: + x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS]); + if (x < NVEGTYPES) { + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in interception parameter kSmax(veg) in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].veg_kSmax = help_veg[k]; + } + break; + case 10: + x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS]); + if (x < NVEGTYPES) { + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in interception parameter kdead(veg) in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].veg_kdead = help_veg[k]; + } + break; + + /* litter interception parameters */ + case 11: + x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS]); + if (x < NVEGTYPES) { + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in litter interception parameter kSmax(litter) in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].lit_kSmax = help_veg[k]; + } + break; + + /* parameter for partitioning of bare-soil evaporation and transpiration */ + case 12: + x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS]); + if (x < NVEGTYPES) { + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in parameter for partitioning of bare-soil evaporation and transpiration in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].EsTpartitioning_param = help_veg[k]; + } + break; + + /* Parameter for scaling and limiting bare soil evaporation rate */ + case 13: + x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS]); + if (x < NVEGTYPES) { + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in parameter for Parameter for scaling and limiting bare soil evaporation rate in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].Es_param_limit = help_veg[k]; + } + break; + + /* shade effects */ + case 14: + x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS]); + if (x < NVEGTYPES) { + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in shade scale in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].shade_scale = help_veg[k]; + } + break; + case 15: + x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS]); + if (x < NVEGTYPES) { + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in shade max dead biomass in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].shade_deadmax = help_veg[k]; + } + break; + case 16: + x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS]); + if (x < NVEGTYPES) { + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in shade xinflec in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].tr_shade_effects.xinflec = help_veg[k]; + } + break; + case 17: + x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS]); + if (x < NVEGTYPES) { + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in shade yinflec in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].tr_shade_effects.yinflec = help_veg[k]; + } + break; + case 18: + x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS]); + if (x < NVEGTYPES) { + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in shade range in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].tr_shade_effects.range = help_veg[k]; + } + break; + case 19: + x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS]); + if (x < NVEGTYPES) { + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in shade slope in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].tr_shade_effects.slope = help_veg[k]; + } + break; + + /* Hydraulic redistribution */ + case 20: + x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS]); + if (x < NVEGTYPES) { + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in hydraulic redistribution: flag in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].flagHydraulicRedistribution = (Bool) EQ(help_veg[k], 1.); + } + break; + case 21: + x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS]); + if (x < NVEGTYPES) { + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in hydraulic redistribution: maxCondroot in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].maxCondroot = help_veg[k]; + } + break; + case 22: + x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS]); + if (x < NVEGTYPES) { + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in hydraulic redistribution: swpMatric50 in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].swpMatric50 = help_veg[k]; + } + break; + case 23: + x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS]); + if (x < NVEGTYPES) { + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in hydraulic redistribution: shapeCond in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].shapeCond = help_veg[k]; + } + break; + + /* Critical soil water potential */ + case 24: + x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS]); + if (x < NVEGTYPES) { + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in critical soil water potentials: flag in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].SWPcrit = -10. * help_veg[k]; + SW_VegProd.critSoilWater[k] = help_veg[k]; // for use with get_swa for properly partitioning available soilwater + } + get_critical_rank(); + break; + + /* CO2 Biomass Power Equation */ + // Coefficient 1 + case 25: + x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS]); + if (x < NVEGTYPES) { + snprintf(errstr, MAX_ERROR, "ERROR: Not enough arguments for CO2 Biomass Coefficient 1 in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].co2_bio_coeff1 = help_veg[k]; + } + break; + // Coefficient 2 + case 26: + x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS]); + if (x < NVEGTYPES) { + snprintf(errstr, MAX_ERROR, "ERROR: Not enough arguments for CO2 Biomass Coefficient 2 in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].co2_bio_coeff2 = help_veg[k]; + } + break; + + /* CO2 WUE Power Equation */ + // Coefficient 1 + case 27: + x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS]); + if (x < NVEGTYPES) { + snprintf(errstr, MAX_ERROR, "ERROR: Not enough arguments for CO2 WUE Coefficient 1 in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].co2_wue_coeff1 = help_veg[k]; + } + break; + // Coefficient 2 + case 28: + x = sscanf(inbuf, "%f %f %f %f", &help_veg[SW_GRASS], &help_veg[SW_SHRUB], + &help_veg[SW_TREES], &help_veg[SW_FORBS]); + if (x < NVEGTYPES) { + snprintf(errstr, MAX_ERROR, "ERROR: Not enough arguments for CO2 WUE Coefficient 2 in %s\n", MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + ForEachVegType(k) { + v->veg[k].co2_wue_coeff2 = help_veg[k]; + } + break; + + default: + break; + } + } + else { + if (lineno == line_help + 1 || lineno == line_help + 1 + 12 || lineno == line_help + 1 + 12 * 2 || lineno == line_help + 1 + 12 * 3) + mon = Jan; + + x = sscanf(inbuf, "%f %f %f %f", &litt, &biom, &pctl, &laic); + if (x < NVEGTYPES) { + snprintf(errstr, MAX_ERROR, "ERROR: invalid record %d in %s\n", mon + 1, MyFileName); + CloseFile(&f); + LogError(logfp, LOGFATAL, errstr); + } + if (lineno > line_help + 12 * 3 && lineno <= line_help + 12 * 4) { + v->veg[SW_FORBS].litter[mon] = litt; + v->veg[SW_FORBS].biomass[mon] = biom; + v->veg[SW_FORBS].pct_live[mon] = pctl; + v->veg[SW_FORBS].lai_conv[mon] = laic; + } + else if (lineno > line_help + 12 * 2 && lineno <= line_help + 12 * 3) { + v->veg[SW_TREES].litter[mon] = litt; + v->veg[SW_TREES].biomass[mon] = biom; + v->veg[SW_TREES].pct_live[mon] = pctl; + v->veg[SW_TREES].lai_conv[mon] = laic; + } + else if (lineno > line_help + 12 && lineno <= line_help + 12 * 2) { + v->veg[SW_SHRUB].litter[mon] = litt; + v->veg[SW_SHRUB].biomass[mon] = biom; + v->veg[SW_SHRUB].pct_live[mon] = pctl; + v->veg[SW_SHRUB].lai_conv[mon] = laic; + } + else if (lineno > line_help && lineno <= line_help + 12) { + v->veg[SW_GRASS].litter[mon] = litt; + v->veg[SW_GRASS].biomass[mon] = biom; + v->veg[SW_GRASS].pct_live[mon] = pctl; + v->veg[SW_GRASS].lai_conv[mon] = laic; + } + + mon++; + + } + } + + if (mon < Dec) { + snprintf(errstr, MAX_ERROR, "No Veg Production values after month %d", mon + 1); + LogError(logfp, LOGWARN, errstr); + } + + SW_VPD_fix_cover(); + + CloseFile(&f); + + if (EchoInits) + _echo_VegProd(); +} + +/** + \brief Check that sum of land cover is 1; adjust if not. + + \sideeffect + - Adjusts `SW_VegProd->bare_cov.fCover` and `SW_VegProd->veg[k].cov.fCover` + to sum to 1. + - Print a warning that values are adjusted and notes with the new values. +*/ +void SW_VPD_fix_cover(void) +{ + SW_VEGPROD *v = &SW_VegProd; + int k; + RealD fraction_sum = 0.; + + fraction_sum = v->bare_cov.fCover; + ForEachVegType(k) { + fraction_sum += v->veg[k].cov.fCover; + } + + if (!EQ_w_tol(fraction_sum, 1.0, 1e-4)) { + // inputs are never more precise than at most 3-4 digits + + LogError(logfp, LOGWARN, + "Fractions of land cover components were normalized:\n" \ + "\tSum of fractions was %.4f instead of 1.0. " \ + "New coefficients are:", fraction_sum); + + v->bare_cov.fCover /= fraction_sum; + LogError(logfp, LOGNOTE, "Bare ground fraction = %.4f", v->bare_cov.fCover); + + ForEachVegType(k) { + v->veg[k].cov.fCover /= fraction_sum; + LogError(logfp, LOGNOTE, "%s fraction = %.4f", + key2veg[k], v->veg[k].cov.fCover); + } + + LogError(logfp, LOGQUIET, ""); + } +} + +/** +@brief Constructor for SW_VegProd. +*/ +void SW_VPD_construct(void) { + /* =================================================== */ + OutPeriod pd; + + // Clear the module structure: + memset(&SW_VegProd, 0, sizeof(SW_VegProd)); + + // Allocate output structures: + ForEachOutPeriod(pd) + { + SW_VegProd.p_accu[pd] = (SW_VEGPROD_OUTPUTS *) Mem_Calloc(1, + sizeof(SW_VEGPROD_OUTPUTS), "SW_VPD_construct()"); + if (pd > eSW_Day) { + SW_VegProd.p_oagg[pd] = (SW_VEGPROD_OUTPUTS *) Mem_Calloc(1, + sizeof(SW_VEGPROD_OUTPUTS), "SW_VPD_construct()"); + } + } +} + + + +void SW_VPD_init_run(void) { + TimeInt year; + int k, veg_method; + + SW_VEGPROD *veg = &SW_VegProd; + SW_MODEL *model = &SW_Model; + SW_SITE *site = &SW_Site; + + veg_method = veg->veg_method; + + double latitude = site->latitude; + + /* Set co2-multipliers to default */ + for (year = 0; year < MAX_NYEAR; year++) + { + ForEachVegType(k) + { + SW_VegProd.veg[k].co2_multipliers[BIO_INDEX][year] = 1.; + SW_VegProd.veg[k].co2_multipliers[WUE_INDEX][year] = 1.; + } + } + + if(veg_method > 0) { + estimateVegetationFromClimate(veg, model->startyr, model->endyr, veg_method, latitude); + } + +} + + +/** +@brief Deconstructor for SW_VegProd. +*/ +void SW_VPD_deconstruct(void) +{ + OutPeriod pd; + + // De-allocate output structures: + ForEachOutPeriod(pd) + { + if (pd > eSW_Day && !isnull(SW_VegProd.p_oagg[pd])) { + Mem_Free(SW_VegProd.p_oagg[pd]); + SW_VegProd.p_oagg[pd] = NULL; + } + + if (!isnull(SW_VegProd.p_accu[pd])) { + Mem_Free(SW_VegProd.p_accu[pd]); + SW_VegProd.p_accu[pd] = NULL; + } + } +} + +/** + * @brief Applies CO2 effects to supplied biomass data. + * + * Two biomass parameters are needed so that we do not have a compound effect + * on the biomass. + * + * @param new_biomass The resulting biomass after applying the multiplier. + * @param biomass The biomass to be modified (representing the value under reference + * conditions (i.e., 360 ppm CO2, currently). + * @param multiplier The biomass multiplier for this PFT. + * + * @sideeffect new_biomass Updated biomass. + */ +void apply_biomassCO2effect(double new_biomass[], double biomass[], double multiplier) { + int i; + for (i = 0; i < 12; i++) new_biomass[i] = (biomass[i] * multiplier); +} + + + +/** +@brief Update vegetation parameters for new year +*/ +void SW_VPD_new_year(void) { + /* ================================================== */ + /* + * History: + * Originally included in the FORTRAN model. + * + * 20-Oct-03 (cwb) removed the calculation of + * lai_corr and changed the lai_conv value of 80 + * as found in the prod.in file. The conversion + * factor is now simply a divisor rather than + * an equation. Removed the following code: + lai_corr = v->lai_conv[m] * (1. - pstem) + aconst * pstem; + lai_standing = v->biomass[m] / lai_corr; + where pstem = 0.3, + aconst = 464.0, + conv_stcr = 3.0; + * + */ + + SW_VEGPROD *v = &SW_VegProd; /* convenience */ + TimeInt doy; /* base1 */ + int k; + + // Interpolation is to be in base1 in `interpolate_monthlyValues()` + Bool interpAsBase1 = swTRUE; + + double + biomass_after_CO2[MAX_MONTHS]; /* Monthly biomass after CO2 effects */ + + + // Grab the real year so we can access CO2 data + ForEachVegType(k) + { + if (GT(v->veg[k].cov.fCover, 0.)) + { + if (k == SW_TREES) + { + // CO2 effects on tree biomass restricted to percent live biomass, i.e., + // total tree biomass is constant while live biomass is increasing + apply_biomassCO2effect( + biomass_after_CO2, + v->veg[k].pct_live, + v->veg[k].co2_multipliers[BIO_INDEX][SW_Model.simyear] + ); + + interpolate_monthlyValues(biomass_after_CO2, interpAsBase1, v->veg[k].pct_live_daily); + interpolate_monthlyValues(v->veg[k].biomass, interpAsBase1, v->veg[k].biomass_daily); + + } else { + // CO2 effects on biomass applied to total biomass, i.e., + // total and live biomass are increasing + apply_biomassCO2effect( + biomass_after_CO2, + v->veg[k].biomass, + v->veg[k].co2_multipliers[BIO_INDEX][SW_Model.simyear] + ); + + interpolate_monthlyValues(biomass_after_CO2, interpAsBase1, v->veg[k].biomass_daily); + interpolate_monthlyValues(v->veg[k].pct_live, interpAsBase1, v->veg[k].pct_live_daily); + } + + // Interpolation of remaining variables from monthly to daily values + interpolate_monthlyValues(v->veg[k].litter, interpAsBase1, v->veg[k].litter_daily); + interpolate_monthlyValues(v->veg[k].lai_conv, interpAsBase1, v->veg[k].lai_conv_daily); + } + } + + for (doy = 1; doy <= MAX_DAYS; doy++) + { + ForEachVegType(k) { + if (GT(v->veg[k].cov.fCover, 0.)) + { + /* vegetation height = 'veg_height_daily' is used for 'snowdepth_scale'; historically, also for 'vegcov' */ + if (GT(v->veg[k].canopy_height_constant, 0.)) + { + v->veg[k].veg_height_daily[doy] = v->veg[k].canopy_height_constant; + + } else { + v->veg[k].veg_height_daily[doy] = tanfunc(v->veg[k].biomass_daily[doy], + v->veg[k].cnpy.xinflec, + v->veg[k].cnpy.yinflec, + v->veg[k].cnpy.range, + v->veg[k].cnpy.slope); + } + + /* live biomass = 'biolive_daily' is used for canopy-interception, transpiration, bare-soil evaporation, and hydraulic redistribution */ + v->veg[k].biolive_daily[doy] = v->veg[k].biomass_daily[doy] * v->veg[k].pct_live_daily[doy]; + + /* dead biomass = 'biodead_daily' is used for canopy-interception and transpiration */ + v->veg[k].biodead_daily[doy] = v->veg[k].biomass_daily[doy] - v->veg[k].biolive_daily[doy]; + + /* live leaf area index = 'lai_live_daily' is used for E-T partitioning */ + v->veg[k].lai_live_daily[doy] = v->veg[k].biolive_daily[doy] / v->veg[k].lai_conv_daily[doy]; + + /* compound leaf area index = 'bLAI_total_daily' is used for canopy-interception */ + v->veg[k].bLAI_total_daily[doy] = v->veg[k].lai_live_daily[doy] + + v->veg[k].veg_kdead * v->veg[k].biodead_daily[doy] / v->veg[k].lai_conv_daily[doy]; + + /* total above-ground biomass = 'total_agb_daily' is used for bare-soil evaporation */ + if (k == SW_TREES) + { + v->veg[k].total_agb_daily[doy] = v->veg[k].litter_daily[doy] + v->veg[k].biolive_daily[doy]; + } else { + v->veg[k].total_agb_daily[doy] = v->veg[k].litter_daily[doy] + v->veg[k].biomass_daily[doy]; + } + + } else { + v->veg[k].lai_live_daily[doy] = 0.; + v->veg[k].bLAI_total_daily[doy] = 0.; + v->veg[k].biolive_daily[doy] = 0.; + v->veg[k].biodead_daily[doy] = 0.; + v->veg[k].total_agb_daily[doy] = 0.; + } + } + } +} + + +/** + @brief Sum up values across vegetation types + + @param[in] *x Array of size \ref NVEGTYPES + @return Sum across `*x` +*/ +RealD sum_across_vegtypes(RealD *x) +{ + unsigned int k; + RealD sum = 0.; + + ForEachVegType(k) + { + sum += x[k]; + } + + return sum; +} + + +/** +@brief Text output for VegProd. +*/ +void _echo_VegProd(void) { + /* ================================================== */ + + SW_VEGPROD *v = &SW_VegProd; /* convenience */ + char outstr[1500]; + int k; + + snprintf( + errstr, + MAX_ERROR, + "\n==============================================\n" + "Vegetation Production Parameters\n\n" + ); + strcpy(outstr, errstr); + LogError(logfp, LOGNOTE, outstr); + + ForEachVegType(k) { + snprintf( + errstr, + MAX_ERROR, + "%s component\t= %1.2f\n" + "\tAlbedo\t= %1.2f\n" + "\tHydraulic redistribution flag\t= %d\n", + key2veg[k], v->veg[k].cov.fCover, + v->veg[k].cov.albedo, + v->veg[k].flagHydraulicRedistribution + ); + strcpy(outstr, errstr); + LogError(logfp, LOGNOTE, outstr); + } + + snprintf( + errstr, + MAX_ERROR, + "Bare Ground component\t= %1.2f\n" + "\tAlbedo\t= %1.2f\n", + v->bare_cov.fCover, + v->bare_cov.albedo + ); + strcpy(outstr, errstr); + LogError(logfp, LOGNOTE, outstr); +} + + +/** @brief Determine vegetation type of decreasingly ranked the critical SWP + + @sideeffect Sets `SW_VegProd.rank_SWPcrits[]` based on + `SW_VegProd.critSoilWater[]` +*/ +void get_critical_rank(void){ + /*---------------------------------------------------------- + Get proper order for rank_SWPcrits + ----------------------------------------------------------*/ + int i, outerLoop, innerLoop; + float key; + + RealF tempArray[NVEGTYPES], tempArrayUnsorted[NVEGTYPES]; // need two temp arrays equal to critSoilWater since we dont want to alter the original at all + + ForEachVegType(i){ + tempArray[i] = SW_VegProd.critSoilWater[i]; + tempArrayUnsorted[i] = SW_VegProd.critSoilWater[i]; + } + + // insertion sort to rank the veg types and store them in their proper order + for (outerLoop = 1; outerLoop < NVEGTYPES; outerLoop++) + { + key = tempArray[outerLoop]; // set key equal to critical value + innerLoop = outerLoop-1; + while (innerLoop >= 0 && tempArray[innerLoop] < key) + { + // code to switch values + tempArray[innerLoop+1] = tempArray[innerLoop]; + innerLoop = innerLoop-1; + } + tempArray[innerLoop+1] = key; + } + + // loops to compare sorted v unsorted array and find proper index + for(outerLoop = 0; outerLoop < NVEGTYPES; outerLoop++){ + for(innerLoop = 0; innerLoop < NVEGTYPES; innerLoop++){ + if(tempArray[outerLoop] == tempArrayUnsorted[innerLoop]){ + SW_VegProd.rank_SWPcrits[outerLoop] = innerLoop; + tempArrayUnsorted[innerLoop] = SW_MISSING; // set value to something impossible so if a duplicate a different index is picked next + break; + } + } + } + /*printf("%d = %f\n", SW_VegProd.rank_SWPcrits[0], SW_VegProd.critSoilWater[SW_VegProd.rank_SWPcrits[0]]); + printf("%d = %f\n", SW_VegProd.rank_SWPcrits[1], SW_VegProd.critSoilWater[SW_VegProd.rank_SWPcrits[1]]); + printf("%d = %f\n", SW_VegProd.rank_SWPcrits[2], SW_VegProd.critSoilWater[SW_VegProd.rank_SWPcrits[2]]); + printf("%d = %f\n\n", SW_VegProd.rank_SWPcrits[3], SW_VegProd.critSoilWater[SW_VegProd.rank_SWPcrits[3]]);*/ + /*---------------------------------------------------------- + End of rank_SWPcrits + ----------------------------------------------------------*/ +} + +/** + @brief Wrapper function for estimating natural vegetation. First, climate is calculated and averaged, then values are estimated + + @param[in,out] vegProd Structure holding all values for vegetation cover of simulation + @param[in] startYear Starting year of the simulation + @param[in] endYear Ending year of the simulation + @param[in] veg_method User specified value determining method of vegetation estimation with the current option(s): + 1 - Estimate fixed vegetation composition (fractional cover) from long-term climate conditions + @param[in] latitude Value of type double specifying latitude coordinate the current site is located at + */ + +void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYear, + int veg_method, double latitude) { + + int numYears = endYear - startYear + 1, k, bareGroundIndex = 7; + + SW_CLIMATE_YEARLY climateOutput; + SW_CLIMATE_CLIM climateAverages; + + // NOTE: 8 = number of types, 5 = (number of types) - grasses + + double coverValues[8] = {SW_MISSING, SW_MISSING, SW_MISSING, SW_MISSING, + 0.0, SW_MISSING, 0.0, 0.0}, shrubLimit = .2; + + double SumGrassesFraction = SW_MISSING, C4Variables[3], grassOutput[3], + RelAbundanceL0[8], RelAbundanceL1[5]; + + Bool fillEmptyWithBareGround = swTRUE, warnExtrapolation = swTRUE; + Bool inNorthHem = swTRUE; + Bool fixBareGround = swTRUE; + + if(latitude < 0.0) { + inNorthHem = swFALSE; + } + + // Allocate climate structs' memory + allocateClimateStructs(numYears, &climateOutput, &climateAverages); + + calcSiteClimate(SW_Weather.allHist, numYears, startYear, inNorthHem, &climateOutput); + + averageClimateAcrossYears(&climateOutput, numYears, &climateAverages); + + if(veg_method == 1) { + + C4Variables[0] = climateAverages.minTemp7thMon_C; + C4Variables[1] = climateAverages.ddAbove65F_degday; + C4Variables[2] = climateAverages.frostFree_days; + + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, coverValues, + shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, + inNorthHem, warnExtrapolation, fixBareGround, grassOutput, + RelAbundanceL0, RelAbundanceL1); + + ForEachVegType(k) { + vegProd->veg[k].cov.fCover = RelAbundanceL1[k]; + } + + vegProd->bare_cov.fCover = RelAbundanceL0[bareGroundIndex]; + } + + // Deallocate climate structs' memory + deallocateClimateStructs(&climateOutput, &climateAverages); + +} + +/** + @brief Calculate the composition (land cover) representing a potential natural vegetation based on climate relationships + + The function returns relative abundance/land cover values that completely cover the surface (i.e., they sum to 1) + of a site specified by long-term climate and/or fixed input values. + + Some of the land cover/vegetation types, i.e., trees, annual grasses, and bare-ground are not estimated from climate relationships; + they are either set to 0, or alternatively fixed at the value of the input argument(s). + + The remaining vegetation types, i.e., shrubs, C3 grasses, C4 grasses, forbs, and succulents, are estimated from climate + relationships using equations developed by Paruelo & Lauenroth 1996 @cite paruelo1996EA, or alternatively fixed at the + value of the input argument(s). If values for dailyC4vars are provided, then equations developed by Teeri & Stowe 1976 @cite teeri1976O + are used to limit the occurrence of C4 grasses. + + The relative abundance values of the the vegetation types that can be estimated and are not fixed by inputs, are + estimated in two steps: (i) as if they cover the entire surface; (ii) scaled to the proportion of the surface that is not fixed by inputs. + + The equations developed by Paruelo & Lauenroth 1996 @cite paruelo1996EA are based on sites with MAT from 2 C to 21.2 C and MAP + from 117 to 1011 mm. If warn_extrapolation is set to TRUE, then inputs are checked against supported ranges, i.e., if MAT is below 1 C, + then it is reset to 1 C with a warning. If other inputs exceed their ranges, then a warning is issued and the code proceeds. + + `calcSiteClimate()` and `averageClimateAcrossYears()` can be used to calculate climate variables required as inputs.` + + @param[in] meanTemp_C Value containing the long-term average of yearly temperatures [C] + @param[in] PPT_cm Value containing the long-term average of yearly precipitation [cm] + @param[in] meanTempMon_C Array of size MAX_MONTHS containing long-term average monthly mean temperatures [C] + @param[in] PPTMon_cm Array of size MAX_MONTHS containing sum of monthly mean precipitation [cm] + @param[in] inputValues Array of size eight that contains values input by user for each component of cover. + The elements of compositions are: 0) Succulents 1) Forbs 2) C3 3) C4 4) Grass Annuals 5) Shrubs 6) Trees 7) Bare ground. + A value of SW_MISSING indicates the respective component's value will be estimated. If an element is not SW_MISSING, + a value from 0-1 indicates the component cover is fixed and will not be estimated. + @param[in] shrubLimit Shrub cover lower than shrubLimit selects the "grassland" equation to determine C3 grass cover; + shrub cover larger than shrubLimit selects the "shrubland" equation (default value of 0.2; page 1213 of Paruelo & Lauenroth 1996). + @param[in] SumGrassesFraction Value holding sum of grasses, if not SW_MISSING, the sum of grasses is fixed and + if a grass component is not fixed, it will be estimated relative to this value + @param[in] C4Variables Array of size three holding C4 variables after being averaged by `averageClimateAcrossYears()`. + The elements are: 0) July precipitation, 1) mean temperature of dry quarter, 2) mean minimum temperature of February + @param[in] fillEmptyWithBareGround Bool value specifying whether or not to fill gaps in values with bare ground + @param[in] inNorthHem Bool value specifying if the current site is in the northern hemisphere + @param[in] warnExtrapolation Bool value specifying whether or not to warn the user when extrapolation happens + @param[in] fixBareGround Bool value specifying if bare ground input value is fixed + @param[out] grassOutput Array of size three holding estimated grass values. The elements are: 0) C3, 1) C4, 2) annual grasses + @param[out] RelAbundanceL0 Array of size eight holding all estimated values. The elements are: + 0) Succulents, 1) Forbs, 2) C3, 3) C4, 4) annual grasses, 5) Shrubs, 6) Trees, 7) Bare ground + @param[out] RelAbundanceL1 Array of size five holding all estimated values aside from grasses (not including sum of grasses). + The elements are: 0) trees, 1) shrubs 2) sum of forbs and succulents 3) overall sum of grasses 4) bare ground + + @note This function uses equations developed by + Paruelo & Lauenroth (1996) @cite paruelo1996EA and, + for C4 grasses, an equation by Teeri & Stowe (1976) @cite teeri1976O. + */ + +void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTempMon_C[], + double PPTMon_cm[], double inputValues[], double shrubLimit, double SumGrassesFraction, + double C4Variables[], Bool fillEmptyWithBareGround, Bool inNorthHem, Bool warnExtrapolation, + Bool fixBareGround, double *grassOutput, double *RelAbundanceL0, double *RelAbundanceL1) { + + const int nTypes = 8; + int winterMonths[3], summerMonths[3]; + + // Indices both single value and arrays + int index, succIndex = 0, forbIndex = 1, C3Index = 2, C4Index = 3, grassAnn = 4, + shrubIndex = 5, treeIndex = 6, bareGround = 7, grassEstimSize = 0, overallEstimSize = 0, + julyMin = 0, degreeAbove65 = 1, frostFreeDays = 2, estimIndicesNotNA = 0, grassesEstim[3], + overallEstim[nTypes], iFixed[nTypes], iFixedSize = 0, + isetIndices[3] = {grassAnn, treeIndex, bareGround}; + + const char *txt_isetIndices[] = {"annual grasses", "trees", "bare ground"}; + + // Totals of different areas of variables + double totalSumGrasses = 0., inputSumGrasses = 0., tempDiffJanJul, + summerMAP = 0., winterMAP = 0., C4Species = SW_MISSING, C3Grassland, C3Shrubland, + estimGrassSum = 0, finalVegSum = 0., estimCoverSum = 0., tempSumGrasses = 0., + estimCover[nTypes], initialVegSum = 0., tempSwapValue, fixedValuesSum = 0; + + Bool fixSumGrasses = (Bool) (!missing(SumGrassesFraction)), + isGrassIndex = swFALSE, tempShrubBool; + + + // Land cover/vegetation types that are not estimated + // (trees, annual grasses, and bare-ground): + // set to 0 if input is `SW_MISSING` + for (index = 0; index < 3; index++) { + if (missing(inputValues[isetIndices[index]])) { + inputValues[isetIndices[index]] = 0.; + + LogError( + logfp, + LOGWARN, + "No equation for requested cover type '%s': cover set to 0.\n", + txt_isetIndices[index] + ); + } + } + + // Loop through inputValues and get the total + for(index = 0; index < nTypes; index++) { + if(!missing(inputValues[index])) { + initialVegSum += inputValues[index]; + } + } + + // Check if grasses are fixed + if(fixSumGrasses) { + // Set SumGrassesFraction + // If SumGrassesFraction < 0, set to zero, otherwise keep at value + cutZeroInf(SumGrassesFraction); + // Get sum of input grass values and set to inputSumGrasses + for(index = C3Index; index <= grassAnn; index++) { + if(!missing(inputValues[index])) inputSumGrasses += inputValues[index]; + } + + // Get totalSumGrasses + totalSumGrasses = SumGrassesFraction - inputSumGrasses; + + // Check if totalSumGrasses is less than zero + if(totalSumGrasses < 0){ + LogError(logfp, LOGFATAL, "'estimate_PotNatVeg_composition': " + "User defined grass values including C3, C4, and annuals " + "sum to more than user defined total grass cover."); + } + // Find indices to estimate related to grass (i.e., C3, C4 and annual grasses) + for(index = C3Index; index < grassAnn; index++) { + if(missing(inputValues[index])) { + grassesEstim[grassEstimSize] = index; + grassEstimSize++; + } + } + + // Check if totalSumGrasses is greater than zero + if(totalSumGrasses > 0) { + + // Check if there is only one grass index to be estimated + if(grassEstimSize == 1) { + + // Set element to SumGrassesFraction - inputSumGrasses + inputValues[grassesEstim[0]] = SumGrassesFraction - inputSumGrasses; + + // Set totalSumGrasses to zero + totalSumGrasses = 0.; + } + } else { + // Otherwise, totalSumGrasses is zero or below + for(index = 0; index < grassEstimSize; index++) { + // Set all found ids to estimate to zero + inputValues[grassesEstim[index]] = 0.; + } + } + } + + // Initialize overallEstim and add fixed indices to `iFixed` + for(index = 0; index < nTypes; index++) { + if(!missing(inputValues[index])) { + iFixed[iFixedSize] = index; + iFixedSize++; + estimCover[index] = inputValues[index]; + estimIndicesNotNA++; + } else { + overallEstim[overallEstimSize] = index; + overallEstimSize++; + estimCover[index] = 0.; + } + } + + uniqueIndices(isetIndices, iFixed, 3, iFixedSize, iFixed, &iFixedSize); + + // Set boolean value to true if grasses still need to be estimated + if(!EQ(totalSumGrasses, 0.)) { + initialVegSum += totalSumGrasses; + } + + if(GT(initialVegSum, 1.)) { + LogError(logfp, LOGFATAL, "'estimate_PotNatVeg_composition': " + "User defined relative abundance values sum to more than " + "1 = full land cover."); + } + + // Check if number of elements to estimate is less than or equal to 1 + // Or `initialVegSum` is 1 and we do not have to estimate any grasses + if(overallEstimSize <= 1) { + if(overallEstimSize == 0) { + // Check if we want to fill gaps in data with bare ground + if(fillEmptyWithBareGround) { + // Set estimCover at index `bareGround` to 1 - (all values execpt + // at index `bareGround`) + inputValues[bareGround] = 1 - (initialVegSum - estimCover[bareGround]); + } else if(initialVegSum < 1) { + LogError(logfp, LOGFATAL, "'estimate_PotNatVeg_composition': " + "User defined relative abundance values are all fixed, " + "but their sum is smaller than 1 = full land cover."); + } + } else if(overallEstimSize == 1) { + estimCover[overallEstim[0]] = 1 - initialVegSum; + } + } else { + + if(PPT_cm * 10 <= 1) { + for(index = 0; index < nTypes - 1; index++) { + estimCover[index] = 0.; + } + estimCover[bareGround] = 1.; + } else { + // Set months of winter and summer (northern/southern hemisphere) + // and get their three month values in precipitation and temperature + if(inNorthHem) { + for(index = 0; index < 3; index++) { + winterMonths[index] = (index + 11) % MAX_MONTHS; + summerMonths[index] = (index + 5); + summerMAP += PPTMon_cm[summerMonths[index]]; + winterMAP += PPTMon_cm[winterMonths[index]]; + } + } else { + for(index = 0; index < 3; index++) { + summerMonths[index] = (index + 11) % MAX_MONTHS; + winterMonths[index] = (index + 5); + summerMAP += PPTMon_cm[summerMonths[index]]; + winterMAP += PPTMon_cm[winterMonths[index]]; + } + } + // Set summer and winter precipitations in mm + summerMAP /= PPT_cm; + winterMAP /= PPT_cm; + + // Get the difference between July and Janurary + tempDiffJanJul = cutZeroInf(meanTempMon_C[summerMonths[1]] - + meanTempMon_C[winterMonths[1]]); + + if(warnExtrapolation) { + if(meanTemp_C < 1) { + LogError(logfp, LOGWARN, "Equations used outside supported range" + "(2 - 21.2 C): MAT = %2f, C reset to 1C", meanTemp_C); + + meanTemp_C = 1; + } + + if(meanTemp_C > 21.2) { + LogError(logfp, LOGWARN, "Equations used outside supported range" + "(2 - 21.2 C): MAT = %2f C", meanTemp_C); + } + + if(PPT_cm * 10 < 117 || PPT_cm * 10 > 1011) { + LogError(logfp, LOGWARN, "Equations used outside supported range" + "(117 - 1011 mm): MAP = %3f mm", PPT_cm * 10); + } + } + // Paruelo & Lauenroth (1996): shrub climate-relationship: + if(PPT_cm * 10 < 1) { + estimCover[shrubIndex] = 0.; + } else { + estimCover[shrubIndex] = cutZeroInf(1.7105 - (.2918 * log(PPT_cm * 10)) + + (1.5451 * winterMAP)); + } + + // Paruelo & Lauenroth (1996): C4-grass climate-relationship: + if(meanTemp_C <= 0) { + estimCover[C4Index] = 0; + } else { + estimCover[C4Index] = cutZeroInf(-0.9837 + (.000594 * (PPT_cm * 10)) + + (1.3528 * summerMAP) + (.2710 * log(meanTemp_C))); + + // This equations give percent species/vegetation -> use to limit + // Paruelo's C4 equation, i.e., where no C4 species => C4 abundance == 0 + if(!missing(C4Variables[julyMin])) { + if(C4Variables[frostFreeDays] <= 0) { + C4Species = 0; + } else { + C4Species = cutZeroInf(((1.6 * (C4Variables[julyMin] * 9 / 5 + 32)) + + (.0086 * (C4Variables[degreeAbove65] * 9 / 5)) + - (8.98 * log(C4Variables[frostFreeDays])) - 22.44) / 100); + } + if(EQ(C4Species, 0.)) estimCover[C4Index] = 0; + } + } + + // Paruelo & Lauenroth (1996): C3-grass climate-relationship: + if(winterMAP <= 0) { + C3Grassland = C3Shrubland = 0; + } else { + C3Grassland = cutZeroInf(1.1905 - .02909 * meanTemp_C + + .1781 * log(winterMAP) - .2383); + + C3Shrubland = cutZeroInf(1.1905 - .02909 * meanTemp_C + + .1781 * log(winterMAP) - .4766); + } + + tempShrubBool = (Bool) (!missing(estimCover[shrubIndex]) && + estimCover[shrubIndex] >= shrubLimit); + + if(tempShrubBool) { + estimCover[C3Index] = C3Shrubland; + } else { + estimCover[C3Index] = C3Grassland; + } + // Paruelo & Lauenroth (1996): forb climate-relationship: + if(PPT_cm * 10 < 1 || meanTemp_C <= 0) { + estimCover[forbIndex] = 0.; + } else { + estimCover[forbIndex] = cutZeroInf(-.2035 + (.07975 * log(PPT_cm * 10)) + - (.0623 * log(meanTemp_C))); + } + + // Paruelo & Lauenroth (1996): succulent climate-relationship: + if(tempDiffJanJul <= 0 || winterMAP <= 0) { + estimCover[succIndex] = 0.; + } else { + estimCover[succIndex] = + cutZeroInf(-1 + ((1.20246 * pow(tempDiffJanJul, -.0689)) * + (pow(winterMAP, -.0322)))); + } + + // Check if fillEmptyWithBareGround is FALSE and there's less than or equal + // to one indices to estimate + if(!fillEmptyWithBareGround && estimIndicesNotNA <= 1) { + if(PPT_cm * 10 < 600) { + estimCover[shrubIndex] += 1.; + } + if(meanTemp_C < 10) { + estimCover[C3Index] += 1.; + } + if(meanTemp_C >= 10 && PPT_cm * 10 > 600) { + estimCover[C4Index] += 1.; + } + } + + if(fixSumGrasses && GT(totalSumGrasses, 0.)) { + for(index = 0; index < grassEstimSize; index++) { + estimGrassSum += estimCover[grassesEstim[index]]; + } + + // If estimGrassSum is 0, make it 1. to prevent dividing by zero + estimGrassSum = (EQ(estimGrassSum, 0.)) ? 1. : estimGrassSum; + + if(GT(estimGrassSum, 0.)) { + for(index = 0; index < grassEstimSize; index++) { + estimCover[grassesEstim[index]] *= + (totalSumGrasses / estimGrassSum); + } + } else if(grassEstimSize > 0) { + for(index = 0; index < grassEstimSize; index++) { + estimCover[grassesEstim[index]] = (totalSumGrasses / grassEstimSize); + } + + LogError(logfp, LOGWARN, "'estimate_PotNatVeg_composition': " + "Total grass cover set, but no grass cover estimated; " + "requested cover evenly divided among grass types."); + } + } + + if(fixSumGrasses) { + // Add grasses to `iFixed` array + uniqueIndices(iFixed, grassesEstim, iFixedSize, grassEstimSize, iFixed, &iFixedSize); + + // Remove them from the `estimIndices` array + for(index = 0; index < overallEstimSize; index++) { + do { + isGrassIndex = (Bool) (overallEstim[index] == C3Index + || overallEstim[index] == C4Index + || overallEstim[index] == grassAnn); + + if(isGrassIndex && index + 1 != overallEstimSize) { + tempSwapValue = overallEstim[overallEstimSize - 1]; + overallEstim[overallEstimSize - 1] = overallEstim[index]; + overallEstim[index] = tempSwapValue; + overallEstimSize--; + } + } while(index != overallEstimSize - 1 && isGrassIndex); + } + + isGrassIndex = (Bool) (overallEstim[index - 1] == C3Index + || overallEstim[index - 1] == C4Index + || overallEstim[index - 1] == grassAnn); + + if(isGrassIndex) overallEstimSize--; + } + + // Get final estimated vegetation sum + for(index = 0; index < nTypes; index++) { + if(missing(inputValues[index])) { + finalVegSum += estimCover[index]; + } else { + finalVegSum += inputValues[index]; + fixedValuesSum += inputValues[index]; + } + } + + // Include fixed grass sum if not missing + if(fixSumGrasses && grassEstimSize > 0) { + fixedValuesSum += totalSumGrasses; + } + + // Check if the final estimated vegetation sum is equal to one + if(!EQ(finalVegSum, 1.)) { + for(index = 0; index < overallEstimSize; index++) { + estimCoverSum += estimCover[overallEstim[index]]; + } + if(estimCoverSum > 0) { + for(index = 0; index < overallEstimSize; index++) { + estimCover[overallEstim[index]] *= (1 - fixedValuesSum) / estimCoverSum; + } + } else { + if(fillEmptyWithBareGround && !fixBareGround) { + inputValues[bareGround] = 1.; + for(index = 0; index < nTypes - 1; index++) { + inputValues[bareGround] -= estimCover[index]; + } + } else { + LogError(logfp, LOGFATAL, "'estimate_PotNatVeg_composition': " + "The estimated vegetation cover values are 0, " + "the user fixed relative abundance values sum to less than 1, " + "and bare-ground is fixed. " + "Thus, the function cannot compute " + "complete land cover composition."); + } + } + } + } + } + + // Fill in all output arrays (grassOutput, RelAbundanceL0, RelAbundanceL1) + for(index = 0; index < nTypes; index++) { + // Check if inputValues at index is missing or if current index is + // bare ground if bare ground is fixed + if(!missing(inputValues[index])) { + + // Reset estimated value to fixed value that was input + estimCover[index] = inputValues[index]; + } + + RelAbundanceL0[index] = estimCover[index]; + } + + grassOutput[0] = (missing(inputValues[C3Index])) ? estimCover[C3Index] : inputValues[C3Index]; + grassOutput[1] = (missing(inputValues[C4Index])) ? estimCover[C4Index] : inputValues[C4Index]; + grassOutput[2] = (missing(inputValues[grassAnn])) ? estimCover[grassAnn] : inputValues[grassAnn]; + + tempSumGrasses += grassOutput[0]; + tempSumGrasses += grassOutput[1]; + tempSumGrasses += grassOutput[2]; + + if(tempSumGrasses > 0) { + for(index = 0; index < 3; index++) { + grassOutput[index] /= (fixSumGrasses && overallEstimSize <= 1) + ? SumGrassesFraction : tempSumGrasses; + } + } + + RelAbundanceL1[0] = estimCover[treeIndex]; + RelAbundanceL1[1] = estimCover[shrubIndex]; + RelAbundanceL1[2] = estimCover[forbIndex] + estimCover[succIndex]; + + if(fixSumGrasses && grassEstimSize > 0) { + RelAbundanceL1[3] = SumGrassesFraction; + } else { + RelAbundanceL1[3] = tempSumGrasses; + } + + RelAbundanceL1[4] = inputValues[bareGround]; +} + +/** + @brief Helper function to `estimatePotNatVegComposition()` that doesn't allow a value to go below zero + + @param testValue A value of type double holding a value that is to be tested to see if it is below zero + + @return A value that is either above or equal to zero + */ + +double cutZeroInf(double testValue) { + if(LT(testValue, 0.)) { + return 0.; + } else { + return testValue; + } +} + +/** + @brief Helper function to `estimatePotNatVegComposition()` that gets unique indices from two input arrays + + @param[in] arrayOne First array to search through to get indices inside of it + @param[in] arrayTwo Second array to search through to get indices inside of it + @param[in] arrayOneSize Size of first array + @param[in] arrayTwoSize Size of second array + @param[out] finalIndexArray Array of size finalIndexArraySize that holds all unique indices from both arrays + @param[in,out] finalIndexArraySize Value holding the size of finalIndexArray both before and after the function is run + */ + +void uniqueIndices(int arrayOne[], int arrayTwo[], int arrayOneSize, int arrayTwoSize, + int *finalIndexArray, int *finalIndexArraySize) { + + int index, finalArrayIndex = 0, nTypes = 8, + tempSize = arrayOneSize + arrayTwoSize + finalArrayIndex, tempIndex = 0; + int *tempArray, *tempArraySeen; + + tempArray = (int *)malloc(sizeof(int) * tempSize); + tempArraySeen = (int *)malloc(sizeof(int) * nTypes); + + memset(tempArray, 0, sizeof(int) * tempSize); + memset(tempArraySeen, 0, sizeof(int) * nTypes); + + for(index = 0; index < tempSize; index++) { + // Initalize the "seen" version of tempArray + if(index < nTypes) tempArraySeen[index] = 0; + + // Add all elements of of finalArrayIndex, arrayOne and arrayTWo + // into "tempArray" + if(index < finalArrayIndex) { + tempArray[tempIndex] = finalIndexArray[index]; + tempIndex++; + } + if(index < arrayOneSize) { + tempArray[tempIndex] = arrayOne[index]; + tempIndex++; + } + if(index < arrayTwoSize) { + tempArray[tempIndex] = arrayTwo[index]; + tempIndex++; + } + } + + // Loop through `tempArray` and search for unique indices + for(index = 0; index < tempSize; index++) { + // Check if we have found the current index in question + if(tempArraySeen[tempArray[index]] == 0) { + finalIndexArray[finalArrayIndex] = tempArray[index]; + finalArrayIndex++; + tempArraySeen[tempArray[index]] = tempArray[index]; + } + } + + *finalIndexArraySize = finalArrayIndex; + + free(tempArray); + free(tempArraySeen); + +} diff --git a/src/SW_Weather.c b/src/SW_Weather.c new file mode 100644 index 000000000..7ccfe6637 --- /dev/null +++ b/src/SW_Weather.c @@ -0,0 +1,2027 @@ +/********************************************************/ +/********************************************************/ +/* Source file: Weather.c + Type: class + Application: SOILWAT - soilwater dynamics simulator + Purpose: Read / write and otherwise manage the model's + weather-related information. + History: + (8/28/01) -- INITIAL CODING - cwb + 12/02 - IMPORTANT CHANGE - cwb + refer to comments in Times.h regarding base0 + 20090916 (drs) changed in SW_WTH_new_day() the following code: + wn->temp_avg[Today] = (tmpmax + tmpmin) / 2.; + to wn->temp_avg[Today] = (wn->temp_max[Today] + wn->temp_min[Today]) / 2.; + so that monthly scaling factors also influence average T and not only Tmin and Tmax + 20091009 (drs) moved call to SW_SWC_adjust_snow () to SW_Flow.c + and split it up into snow accumulation and snow melt + 20091014 (drs) added pct_snowdrift as input to weathsetup.in + 20091015 (drs) ppt is divided into rain and snow, added snowmelt + 20101004 (drs) moved call to SW_SWC_adjust_snow() from SW_Flow.c back to SW_WTH_new_day(), see comment 20091009 + 01/04/2011 (drs) added parameter '*snowloss' to function SW_SWC_adjust_snow() call and updated function SW_WTH_new_year() + 01/05/2011 (drs) removed unused variables rain and snow from SW_WTH_new_day() + 02/16/2011 (drs) added pct_runoff read from input file to SW_WTH_read() + 02/19/2011 (drs) to track 'runoff', updated functions SW_WTH_new_year(), SW_WTH_new_day() + moved soil_inf from SW_Soilwat to SW_Weather + 09/30/2011 (drs) weather name prefix no longer read in from file weathersetup.in with function SW_WTH_read(), but extracted from SW_Files.c:SW_WeatherPrefix() + 01/13/2011 (drs) function '_read_hist' didn't close opened files: after reaching OS-limit of openend connections, no files could be read any more -> added 'fclose(f);' to close open connections after use + 06/01/2012 (DLM) edited _read_hist() function to calculate the yearly avg air temperature & the monthly avg air temperatures... + 11/30/2010 (clk) added appopriate lines for 'surfaceRunoff' in SW_WTH_new_year() and SW_WTH_new_day(); + 06/21/2013 (DLM) variable 'tail' got too large by 1 and leaked memory when accessing array runavg_list[] in function _runavg_temp(): changed test from '(tail < SW_Weather.days_in_runavg)' to '(tail < (SW_Weather.days_in_runavg-1))' + in function _clear_hist_weather() temp_max was tested twice, one should be temp_min + 06/24/2013 (rjm) added function void SW_WTH_clear_runavg_list(void) to free memory + 06/24/2013 (rjm) made 'tail' and 'firsttime' a module-level static variable (instead of function-level): otherwise it will not get reset to 0 between consecutive calls as a dynamic library + need to set these variables to 0 resp TRUE in function SW_WTH_construct() + 06/27/2013 (drs) closed open files if LogError() with LOGFATAL is called in SW_WTH_read(), _read_hist() + 08/26/2013 (rjm) removed extern SW_OUTPUT never used. + */ +/********************************************************/ +/********************************************************/ + +#include +#include +#include +#include "include/generic.h" +#include "include/filefuncs.h" +#include "include/myMemory.h" +#include "include/Times.h" +#include "include/SW_Defines.h" +#include "include/SW_Files.h" +#include "include/SW_Model.h" // externs SW_Model +#include "include/SW_SoilWater.h" +#include "include/SW_Markov.h" +#include "include/SW_Flow_lib_PET.h" +#include "include/SW_Sky.h" + +#include "include/SW_Weather.h" + + + +/* =================================================== */ +/* Global Variables */ +/* --------------------------------------------------- */ + +SW_WEATHER SW_Weather; + +/* =================================================== */ +/* Local Variables */ +/* --------------------------------------------------- */ + +static char *MyFileName; + + +/* =================================================== */ +/* Local Function Definitions */ +/* --------------------------------------------------- */ + +/** + @brief Takes averages through the number of years of the calculated values from calc_SiteClimate + + @param[in] climateOutput Structure of type SW_CLIMATE_YEARLY that holds all output from `calcSiteClimate()` + like monthly/yearly temperature and precipitation values + @param[in] numYears Number of years represented within simulation + @param[out] climateAverages Structure of type SW_CLIMATE_CLIM that holds averages and + standard deviations output by `averageClimateAcrossYears()` + */ + +void averageClimateAcrossYears(SW_CLIMATE_YEARLY *climateOutput, int numYears, + SW_CLIMATE_CLIM *climateAverages) { + + int month; + + // Take long-term average of monthly mean, maximum and minimum temperature + // and precipitation throughout "numYears" + for(month = 0; month < MAX_MONTHS; month++) { + climateAverages->meanTempMon_C[month] = mean(climateOutput->meanTempMon_C[month], numYears); + climateAverages->maxTempMon_C[month] = mean(climateOutput->maxTempMon_C[month], numYears); + climateAverages->minTempMon_C[month] = mean(climateOutput->minTempMon_C[month], numYears); + climateAverages->PPTMon_cm[month] = mean(climateOutput->PPTMon_cm[month], numYears); + } + + // Take the long-term average of yearly precipitation and temperature, + // C4 and Cheatgrass variables + climateAverages->PPT_cm = mean(climateOutput->PPT_cm, numYears); + climateAverages->meanTemp_C = mean(climateOutput->meanTemp_C, numYears); + climateAverages->PPT7thMon_mm = mean(climateOutput->PPT7thMon_mm, numYears); + climateAverages->meanTempDriestQtr_C = mean(climateOutput->meanTempDriestQtr_C, numYears); + climateAverages->minTemp2ndMon_C = mean(climateOutput->minTemp2ndMon_C, numYears); + climateAverages->ddAbove65F_degday = mean(climateOutput->ddAbove65F_degday, numYears); + climateAverages->frostFree_days = mean(climateOutput->frostFree_days, numYears); + climateAverages->minTemp7thMon_C = mean(climateOutput->minTemp7thMon_C, numYears); + + // Calculate and set standard deviation of C4 variables + climateAverages->sdC4[0] = standardDeviation(climateOutput->minTemp7thMon_C, numYears); + climateAverages->sdC4[1] = standardDeviation(climateOutput->frostFree_days, numYears); + climateAverages->sdC4[2] = standardDeviation(climateOutput->ddAbove65F_degday, numYears); + + // Calculate and set the standard deviation of cheatgrass variables + climateAverages->sdCheatgrass[0] = standardDeviation(climateOutput->PPT7thMon_mm, numYears); + climateAverages->sdCheatgrass[1] = standardDeviation(climateOutput->meanTempDriestQtr_C, numYears); + climateAverages->sdCheatgrass[2] = standardDeviation(climateOutput->minTemp2ndMon_C, numYears); +} + +/** + @brief Calculate monthly and annual time series of climate variables from daily weather while adjusting as + needed depending on if the site in question is in the northern or southern hemisphere. + + This function has two different types of years: + + - The first one being "calendar" years. Which refers to the days and months that show up on a calendar (Jan-Dec). + + - The second type being "adjusted" years. This type is used when the site location is in the southern hemisphere + and the year starts in July instead of January. For example, the adjusted year 1980 starts half a year earlier on + July 1st 1979, and ends on June 30th, 1980 (see example below). If our data starts in 1980, then we don't have + values for the first six months of adjusted year, 1980. Therefore, we start calculating the climate variables in the + following adjusted year, 1981. And both the first and last six months of data are not used. + + Calendar year vs. adjusted year: + | Calendar year | Year North | Start N | End N | Year South | Start S | End S | + |:------------------:|:---------------:|:---------:|:--------:|:---------------:|:---------:|:---------:| + | 1980 | 1980 | 1980 Jan 1 | 1980 Dec 31 | 1980 | 1979 July 1 | 1980 June 30 | + | 1981 | 1981 | 1981 Jan 1 | 1981 Dec 31 | 1981 | 1980 July 1 | 1981 June 30 | + | 1982 | 1982 | 1982 Jan 1 | 1982 Dec 31 | 1982 | 1981 July 1 | 1982 June 30 | + | ... | ... | ... | ... | ... | ... | ... | + | 2020 | 2020 | 2020 Jan 1 | 2020 Dec 31 | 2020 | 2020 July 1 | 2021 June 30 | + + @param[in] allHist Array containing all historical data of a site + @param[in] numYears Number of years represented within simulation + @param[in] startYear Calendar year corresponding to first year of `allHist` + @param[in] inNorthHem Boolean value specifying if site is in northern hemisphere + @param[out] climateOutput Structure of type SW_CLIMATE_YEARLY that holds all output from `calcSiteClimate()` + like monthly/yearly temperature and precipitation values + */ + +void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, + Bool inNorthHem, SW_CLIMATE_YEARLY *climateOutput) { + + int month, yearIndex, year, day, numDaysYear, currMonDay; + int numDaysMonth, adjustedDoy, adjustedYear = 0, secondMonth, seventhMonth, + adjustedStartYear, calendarYearDays; + + double currentTempMin, currentTempMean, totalAbove65, current7thMonMin, PPT7thMon, + consecNonFrost, currentNonFrost; + + // Initialize accumulated value arrays to all zeros + for(month = 0; month < MAX_MONTHS; month++) { + memset(climateOutput->meanTempMon_C[month], 0., sizeof(double) * numYears); + memset(climateOutput->maxTempMon_C[month], 0., sizeof(double) * numYears); + memset(climateOutput->minTempMon_C[month], 0., sizeof(double) * numYears); + memset(climateOutput->PPTMon_cm[month], 0., sizeof(double) * numYears); + } + memset(climateOutput->PPT_cm, 0., sizeof(double) * numYears); + memset(climateOutput->meanTemp_C, 0., sizeof(double) * numYears); + memset(climateOutput->minTemp2ndMon_C, 0., sizeof(double) * numYears); + memset(climateOutput->minTemp7thMon_C, 0., sizeof(double) * numYears); + + calcSiteClimateLatInvariants(allHist, numYears, startYear, climateOutput); + + // Set starting conditions that are dependent on north/south before main loop is entered + if(inNorthHem) { + secondMonth = Feb; + seventhMonth = Jul; + numDaysMonth = Time_days_in_month(Jan); + adjustedStartYear = 0; + } else { + secondMonth = Aug; + seventhMonth = Jan; + numDaysMonth = Time_days_in_month(Jul); + adjustedStartYear = 1; + climateOutput->minTemp7thMon_C[0] = SW_MISSING; + climateOutput->PPT7thMon_mm[0] = SW_MISSING; + climateOutput->ddAbove65F_degday[0] = SW_MISSING; + climateOutput->frostFree_days[0] = SW_MISSING; + } + + // Loop through all years of data starting at "adjustedStartYear" + for(yearIndex = adjustedStartYear; yearIndex < numYears; yearIndex++) { + year = yearIndex + startYear; + Time_new_year(year); + numDaysYear = Time_get_lastdoy_y(year); + month = (inNorthHem) ? Jan : Jul; + currMonDay = 0; + current7thMonMin = SW_MISSING; + totalAbove65 = 0; + currentNonFrost = 0; + consecNonFrost = 0; + PPT7thMon = 0; + + if(!inNorthHem) { + // Get calendar year days only when site is in southern hemisphere + // To deal with number of days in data + calendarYearDays = Time_get_lastdoy_y(year - 1); + } + + for(day = 0; day < numDaysYear; day++) { + if(inNorthHem) { + adjustedDoy = day; + adjustedYear = yearIndex; + } else { + // Adjust year and day to meet southern hemisphere requirements + adjustedDoy = (calendarYearDays == 366) ? day + 182 : day + 181; + adjustedDoy = adjustedDoy % calendarYearDays; + + if(adjustedDoy == 0) { + adjustedYear++; + Time_new_year(year); + } + } + + if(month == Jul && adjustedYear >= numYears - 1 && !inNorthHem) { + // Set all current accumulated values to SW_MISSING to prevent + // a zero going into the last year of the respective arrays + // Last six months of data is ignored and do not go into + // a new year of data for "adjusted years" + PPT7thMon = SW_MISSING; + totalAbove65 = SW_MISSING; + currentNonFrost = SW_MISSING; + consecNonFrost = SW_MISSING; + currMonDay = SW_MISSING; + break; + } + + currMonDay++; + currentTempMin = allHist[adjustedYear]->temp_min[adjustedDoy]; + currentTempMean = allHist[adjustedYear]->temp_avg[adjustedDoy]; + + // Part of code that deals with gathering seventh month information + if(month == seventhMonth){ + current7thMonMin = (currentTempMin < current7thMonMin) ? + currentTempMin : current7thMonMin; + PPT7thMon += allHist[adjustedYear]->ppt[adjustedDoy] * 10; + } + + // Part of code dealing with consecutive amount of days without frost + if(currentTempMin > 0.0) { + currentNonFrost += 1.; + } else if(currentNonFrost > consecNonFrost){ + consecNonFrost = currentNonFrost; + currentNonFrost = 0.; + } else { + currentNonFrost = 0.; + } + + // Gather minimum temperature of second month of year + if(month == secondMonth) { + climateOutput->minTemp2ndMon_C[yearIndex] += allHist[adjustedYear]->temp_min[adjustedDoy]; + } + + // Once we have reached the end of the month days, + // handle it by getting information about the next month + if(currMonDay == numDaysMonth) { + if(month == secondMonth) climateOutput->minTemp2ndMon_C[yearIndex] /= numDaysMonth; + + month = (month + 1) % 12; + numDaysMonth = Time_days_in_month(month); + currMonDay = 0; + } + + // Accumulate information of degrees above 65ºF + // Check if "currentTempMean" is SW_MISSING + // When "calendarYearDays" is the number of days in a leap year + // (e.g. adjustedYear = 1985, calendarYearDays = 366 for previous year) + // It gets to data that is SW_MISSING, so we want to ignore that + if(!missing(currentTempMean)) { + // Get degrees in Fahrenheit + currentTempMean -= 18.333; + + // Add to total above 65ºF if high enough temperature + totalAbove65 += (currentTempMean > 0.0) ? currentTempMean : 0.0; + } + + } + // Set all values + climateOutput->minTemp7thMon_C[yearIndex] = current7thMonMin; + climateOutput->PPT7thMon_mm[yearIndex] = PPT7thMon; + climateOutput->ddAbove65F_degday[yearIndex] = totalAbove65; + + // The reason behind checking if consecNonFrost is greater than zero, + // is that there is a chance all days in the year are above 32F + climateOutput->frostFree_days[yearIndex] = (consecNonFrost > 0) ? consecNonFrost : currentNonFrost; + } + + findDriestQtr(numYears, inNorthHem, climateOutput->meanTempDriestQtr_C, + climateOutput->meanTempMon_C, climateOutput->PPTMon_cm); +} + +/** + @brief Helper function to `calcSiteClimate()`. Manages all information that is independant of the site + being in the northern/southern hemisphere. + + @param[in] allHist Array containing all historical data of a site + @param[in] numYears Number of years represented within simulation + @param[in] startYear Calendar year corresponding to first year of `allHist` + @param[out] climateOutput Structure of type SW_CLIMATE_YEARLY that holds all output from `calcSiteClimate()` + like monthly/yearly temperature and precipitation values + */ + +void calcSiteClimateLatInvariants(SW_WEATHER_HIST **allHist, int numYears, int startYear, + SW_CLIMATE_YEARLY *climateOutput) { + + int month = Jan, numDaysMonth = Time_days_in_month(month), yearIndex, + day, numDaysYear, currMonDay, year; + + for(yearIndex = 0; yearIndex < numYears; yearIndex++) { + year = yearIndex + startYear; + Time_new_year(year); + numDaysYear = Time_get_lastdoy_y(year); + currMonDay = 0; + for(day = 0; day < numDaysYear; day++) { + currMonDay++; + climateOutput->meanTempMon_C[month][yearIndex] += allHist[yearIndex]->temp_avg[day]; + climateOutput->maxTempMon_C[month][yearIndex] += allHist[yearIndex]->temp_max[day]; + climateOutput->minTempMon_C[month][yearIndex] += allHist[yearIndex]->temp_min[day]; + climateOutput->PPTMon_cm[month][yearIndex] += allHist[yearIndex]->ppt[day]; + climateOutput->PPT_cm[yearIndex] += allHist[yearIndex]->ppt[day]; + climateOutput->meanTemp_C[yearIndex] += allHist[yearIndex]->temp_avg[day]; + + if(currMonDay == numDaysMonth) { + climateOutput->meanTempMon_C[month][yearIndex] /= numDaysMonth; + climateOutput->maxTempMon_C[month][yearIndex] /= numDaysMonth; + climateOutput->minTempMon_C[month][yearIndex] /= numDaysMonth; + + month = (month + 1) % MAX_MONTHS; + numDaysMonth = Time_days_in_month(month); + currMonDay = 0; + } + } + climateOutput->meanTemp_C[yearIndex] /= numDaysYear; + } + +} + +/** + @brief Helper function to `calcSiteClimate()` to find the average temperature during the driest quarter of the year + + @param[in] numYears Number of years represented within simulation + @param[in] inNorthHem Boolean value specifying if site is in northern hemisphere + @param[out] meanTempDriestQtr_C Array of size numYears holding the average temperature of the + driest quarter of the year for every year + @param[out] meanTempMon_C 2D array containing monthly means average daily air temperature (deg;C) with + dimensions of row (months) size MAX_MONTHS and columns (years) of size numYears + @param[out] PPTMon_cm 2D array containing monthly amount precipitation (cm) with dimensions + of row (months) size MAX_MONTHS and columns (years) of size numYears + */ +void findDriestQtr(int numYears, Bool inNorthHem, double *meanTempDriestQtr_C, + double **meanTempMon_C, double **PPTMon_cm) { + + int yearIndex, month, prevMonth, nextMonth, adjustedMonth = 0, + numQuarterMonths = 3, endNumYears = (inNorthHem) ? numYears : numYears - 1; + + // NOTE: These variables are the same throughout the program if site is in + // northern hempisphere + // The main purpose of these are to easily control the correct year when + // dealing with adjusted years in the southern hempisphere + int adjustedYearZero = 0, adjustedYearOne = 0, adjustedYearTwo = 0; + + double driestThreeMonPPT, driestMeanTemp, currentQtrPPT, currentQtrTemp; + + for(yearIndex = 0; yearIndex < endNumYears; yearIndex++) { + driestThreeMonPPT = SW_MISSING; + driestMeanTemp = SW_MISSING; + + adjustedYearZero = adjustedYearOne = adjustedYearTwo = yearIndex; + + for(month = 0; month < MAX_MONTHS; month++) { + + if(inNorthHem) { + adjustedMonth = month; + + prevMonth = (adjustedMonth == Jan) ? Dec : adjustedMonth - 1; + nextMonth = (adjustedMonth == Dec) ? Jan : adjustedMonth + 1; + } else { + driestQtrSouthAdjMonYears(month, &adjustedYearZero, &adjustedYearOne, + &adjustedYearTwo, &adjustedMonth, &prevMonth, &nextMonth); + } + + // Get precipitation of current quarter + currentQtrPPT = (PPTMon_cm[prevMonth][adjustedYearZero]) + + (PPTMon_cm[adjustedMonth][adjustedYearOne]) + + (PPTMon_cm[nextMonth][adjustedYearTwo]); + + // Get temperature of current quarter + currentQtrTemp = (meanTempMon_C[prevMonth][adjustedYearZero]) + + (meanTempMon_C[adjustedMonth][adjustedYearOne]) + + (meanTempMon_C[nextMonth][adjustedYearTwo]); + + // Check if the current quarter precipitation is a new low + if(currentQtrPPT < driestThreeMonPPT) { + // Make current temperature/precipitation the new lowest + driestMeanTemp = currentQtrTemp; + driestThreeMonPPT = currentQtrPPT; + } + + } + + meanTempDriestQtr_C[yearIndex] = driestMeanTemp / numQuarterMonths; + + } + + if(!inNorthHem) meanTempDriestQtr_C[numYears - 1] = SW_MISSING; + +} + + +/** + @brief Helper function to `findDriestQtr()` to find adjusted months and years when the current site is in + the southern hempisphere. + + When site is in the southern hemisphere, the year starts in July and ends in June of the next calendar year, so + months and years need to be adjusted to wrap around years to get accurate driest quarters in a year. + See `calcSiteClimate()` documentation for more explanation on adjusted months and adjusted/calendar years. + + @param[in] month Current month of the year [January, December] + @param[in,out] adjustedYearZero First adjusted year that is paired with previous month of year + @param[in,out] adjustedYearOne Second adjusted year that is paired with current month of year + @param[in,out] adjustedYearTwo Third adjusted year that is paired with next month of year + @param[in,out] adjustedMonth Adjusted month starting at July going to June of next calendar year + @param[in,out] prevMonth Month preceding current input month + @param[in,out] nextMonth Month following current input month + */ +void driestQtrSouthAdjMonYears(int month, int *adjustedYearZero, int *adjustedYearOne, + int *adjustedYearTwo, int *adjustedMonth, int *prevMonth, + int *nextMonth) +{ + *adjustedMonth = month + Jul; + *adjustedMonth %= MAX_MONTHS; + + // Adjust prevMonth, nextMonth and adjustedYear(s) to the respective adjustedMonth + switch(*adjustedMonth) { + case Jan: + adjustedYearOne++; + adjustedYearTwo++; + + *prevMonth = Dec; + *nextMonth = Feb; + break; + case Dec: + adjustedYearTwo++; + + *prevMonth = Nov; + *nextMonth = Jan; + break; + case Jun: + adjustedYearTwo--; + + *prevMonth = May; + *nextMonth = Jul; + break; + case Jul: + adjustedYearZero--; + + *prevMonth = Jun; + *nextMonth = Aug; + break; + default: + // Do adjusted months normally + *prevMonth = *adjustedMonth - 1; + *nextMonth = *adjustedMonth + 1; + break; + } + + /* coerce to void to silence `-Wunused-but-set-parameter` [clang-15] */ + (void) adjustedYearZero; + (void) adjustedYearOne; + (void) adjustedYearTwo; +} + + +/* =================================================== */ +/* Global Function Definitions */ +/* --------------------------------------------------- */ + + + +/** + @brief Reads in all weather data + + Reads in weather data from disk (if available) for all years and + stores values in global SW_Weather's SW_WEATHER::allHist. + If missing, set values to #SW_MISSING. + + @param[out] allHist 2D array holding all weather data gathered + @param[in] startYear Start year of the simulation + @param[in] n_years Number of years in simulation + @param[in] use_weathergenerator_only A boolean; if #swFALSE, code attempts to + read weather files from disk. + @param[in] weather_prefix File name of weather data without extension. + @param[in] use_cloudCoverMonthly A boolean; if #swTRUE, function will interpolate mean + monthly values provided by \p cloudcov to daily time series + @param[in] use_humidityMonthly A boolean; if #swTRUE, function will interpolate mean + monthly values provided by \p r_humidity to daily time series + @param[in] use_windSpeedMonthly A boolean; if #swTRUE, function will interpolate mean + monthly values provided by \p windspeed to daily time series + @param[in] n_input_forcings Number of read-in columns from disk + @param[in] dailyInputIndices An array of size #MAX_INPUT_COLUMNS holding the calculated + column number of which a certain variable resides + @param[in] dailyInputFlags An array of size #MAX_INPUT_COLUMNS holding booleans specifying + what variable has daily input on disk + @param[in] cloudcov Array of size #MAX_MONTHS holding monthly cloud cover values + to be interpolated + @param[in] windspeed Array of size #MAX_MONTHS holding monthly wind speed values + to be interpolated + @param[in] r_humidity Array of size #MAX_MONTHS holding monthly relative humidity values + to be interpolated + +*/ +void readAllWeather( + SW_WEATHER_HIST **allHist, + int startYear, + unsigned int n_years, + Bool use_weathergenerator_only, + char weather_prefix[], + Bool use_cloudCoverMonthly, + Bool use_humidityMonthly, + Bool use_windSpeedMonthly, + unsigned int n_input_forcings, + unsigned int *dailyInputIndices, + Bool *dailyInputFlags, + RealD *cloudcov, + RealD *windspeed, + RealD *r_humidity +) { + unsigned int yearIndex, year; + + /* Interpolation is to be in base0 in `interpolate_monthlyValues()` */ + Bool interpAsBase1 = swFALSE; + + for(yearIndex = 0; yearIndex < n_years; yearIndex++) { + year = yearIndex + startYear; + + // Set all daily weather values to missing + _clear_hist_weather(allHist[yearIndex]); + + // Update yearly day/month information needed when interpolating + // cloud cover, wind speed, and relative humidity if necessary + Time_new_year(year); + + if(use_cloudCoverMonthly) { + interpolate_monthlyValues(cloudcov, interpAsBase1, allHist[yearIndex]->cloudcov_daily); + } + + if(use_humidityMonthly) { + interpolate_monthlyValues(r_humidity, interpAsBase1, allHist[yearIndex]->r_humidity_daily); + } + + if(use_windSpeedMonthly) { + interpolate_monthlyValues(windspeed, interpAsBase1, allHist[yearIndex]->windspeed_daily); + } + + // Read daily weather values from disk + if (!use_weathergenerator_only) { + + _read_weather_hist( + year, + allHist[yearIndex], + weather_prefix, + n_input_forcings, + dailyInputIndices, + dailyInputFlags + ); + } + } +} + + + +/** + @brief Impute missing values and scale with monthly parameters + + Finalize weather values after they have been read in via + `readAllWeather()` or `SW_WTH_read()` + (the latter also handles (re-)allocation). +*/ +void finalizeAllWeather(SW_WEATHER *w) { + + unsigned int day, yearIndex; + + // Impute missing values + generateMissingWeather( + w->allHist, + w->startYear, + w->n_years, + w->generateWeatherMethod, + 3 // optLOCF_nMax (TODO: make this user input) + ); + + // Check to see if actual vapor pressure needs to be calculated + if(w->use_humidityMonthly) { + for(yearIndex = 0; yearIndex < w->n_years; yearIndex++) { + for(day = 0; day < MAX_DAYS; day++) { + + // Make sure calculation of actual vapor pressure is not polluted + // by values of `SW_MISSING` + if(!missing(w->allHist[yearIndex]->r_humidity_daily[day]) && + !missing(w->allHist[yearIndex]->temp_avg[day])) { + + w->allHist[yearIndex]->actualVaporPressure[day] = + actualVaporPressure1(w->allHist[yearIndex]->r_humidity_daily[day], + w->allHist[yearIndex]->temp_avg[day]); + } + } + } + } + + + // Scale with monthly additive/multiplicative parameters + scaleAllWeather( + w->allHist, + w->startYear, + w->n_years, + w->scale_temp_max, + w->scale_temp_min, + w->scale_precip, + w->scale_skyCover, + w->scale_wind, + w->scale_rH, + w->scale_actVapPress, + w->scale_shortWaveRad + ); + + // Make sure all input, scaled, generated, and calculated daily weather values + // are within reason + checkAllWeather(w); +} + + +void SW_WTH_finalize_all_weather(void) { + finalizeAllWeather(&SW_Weather); +} + + +/** + @brief Apply temperature, precipitation, cloud cover, relative humidity, and wind speed + scaling to daily weather values + + @param[in,out] allHist 2D array holding all weather data + @param[in] startYear Start year of the simulation (and `allHist`) + @param[in] n_years Number of years in simulation (length of `allHist`) + @param[in] scale_temp_max Array of monthly, additive scaling parameters to + modify daily maximum air temperature [C] + @param[in] scale_temp_min Array of monthly, additive scaling parameters to + modify daily minimum air temperature [C] + @param[in] scale_precip Array of monthly, multiplicative scaling parameters to + modify daily precipitation [-] + @param[in] scale_skyCover Array of monthly, additive scaling parameters to modify daily sky cover [%] + @param[in] scale_wind Array of monthly, multiplicitive scaling parameters to modify daily wind speed [-] + @param[in] scale_rH Array of monthly, additive scaling parameters to modify daily relative humidity [%] + @param[in] scale_actVapPress Array of monthly, multiplicitive scaling parameters to modify daily actual vapor pressure [-] + @param[in] scale_shortWaveRad Array of monthly, multiplicitive scaling parameters to modify daily shortwave radiation [%] + + @note Daily average air temperature is re-calculated after scaling + minimum and maximum air temperature. + + @note Missing values in `allHist` remain unchanged. +*/ +void scaleAllWeather( + SW_WEATHER_HIST **allHist, + int startYear, + unsigned int n_years, + double *scale_temp_max, + double *scale_temp_min, + double *scale_precip, + double *scale_skyCover, + double *scale_wind, + double *scale_rH, + double *scale_actVapPress, + double *scale_shortWaveRad +) { + + int year, month; + unsigned int yearIndex, numDaysYear, day; + + Bool trivial = swTRUE; + + // Check if we have any non-trivial scaling parameter + for (month = 0; trivial && month < MAX_MONTHS; month++) { + trivial = (Bool) ( + ZRO(scale_temp_max[month]) && + ZRO(scale_temp_min[month]) && + EQ(scale_precip[month], 1.) + ); + } + + if (!trivial) { + // Apply scaling parameters to each day of `allHist` + for (yearIndex = 0; yearIndex < n_years; yearIndex++) { + year = yearIndex + startYear; + Time_new_year(year); // required for `doy2month()` + numDaysYear = Time_get_lastdoy_y(year); + + for (day = 0; day < numDaysYear; day++) { + month = doy2month(day + 1); + + if (!missing(allHist[yearIndex]->temp_max[day])) { + allHist[yearIndex]->temp_max[day] += scale_temp_max[month]; + } + + if (!missing(allHist[yearIndex]->temp_min[day])) { + allHist[yearIndex]->temp_min[day] += scale_temp_min[month]; + } + + if (!missing(allHist[yearIndex]->ppt[day])) { + allHist[yearIndex]->ppt[day] *= scale_precip[month]; + } + + if(!missing(allHist[yearIndex]->cloudcov_daily[day])) { + allHist[yearIndex]->cloudcov_daily[day] = fmin( + 100., + fmax(0.0, scale_skyCover[month] + allHist[yearIndex]->cloudcov_daily[day]) + ); + } + + if(!missing(allHist[yearIndex]->windspeed_daily[day])) { + allHist[yearIndex]->windspeed_daily[day] = fmax( + 0.0, + scale_wind[month] * allHist[yearIndex]->windspeed_daily[day] + ); + } + + if(!missing(allHist[yearIndex]->r_humidity_daily[day])) { + allHist[yearIndex]->r_humidity_daily[day] = fmin( + 100., + fmax(0.0, scale_rH[month] + allHist[yearIndex]->r_humidity_daily[day]) + ); + } + + if(!missing(allHist[yearIndex]->actualVaporPressure[day])) { + allHist[yearIndex]->actualVaporPressure[day] = fmax( + 0.0, + scale_actVapPress[month] * allHist[yearIndex]->actualVaporPressure[day] + ); + } + + if(!missing(allHist[yearIndex]->shortWaveRad[day])) { + allHist[yearIndex]->shortWaveRad[day] = fmax( + 0.0, + scale_shortWaveRad[month] * allHist[yearIndex]->shortWaveRad[day] + ); + } + + /* re-calculate average air temperature */ + if ( + !missing(allHist[yearIndex]->temp_max[day]) && + !missing(allHist[yearIndex]->temp_min[day]) + ) { + allHist[yearIndex]->temp_avg[day] = + (allHist[yearIndex]->temp_max[day] + allHist[yearIndex]->temp_min[day]) / 2.; + } + } + } + } +} + + +/** + @brief Generate missing weather + + Meteorological inputs are required for each day; they can either be + observed and provided via weather input files or they can be generated + such as by a weather generator (which has separate input requirements). + + SOILWAT2 handles three scenarios of missing data: + 1. Some individual days are missing (values correspond to #SW_MISSING); + if any relevant variable is missing on a day, then all relevant + variables are imputed + 2. An entire year is missing (file `weath.xxxx` for year `xxxx` is absent) + 3. No daily weather input files are available + + Available methods to generate weather: + 1. Pass through (`method` = 0) + 2. Imputation by last-value-carried forward "LOCF" (`method` = 1) + - affected variables (all implemented): + - minimum and maximum temperature + - precipitation (which is set to 0 instead of "LOCF") + - cloud cover + - wind speed + - relative humidity + - downard surface shortwave radiation + - actual vapor pressure + - missing values are imputed individually + - error if more than `optLOCF_nMax` days per calendar year are missing + 3. First-order Markov weather generator (`method` = 2) + - affected variables (others are passed through as is): + - minimum and maximum temperature + - precipitation + - if a day contains any missing values (of affected variables), then + values for all of these variables are replaced by values created by + the weather generator + + The user can specify that SOILWAT2 generates all weather without reading + any historical weather data files from disk + (see `weathsetup.in`: use weather generator for all weather). + + @note `SW_MKV_today()` is called if `method` = 2 + (i.e., the weather generator is used); + this requires that appropriate structures are initialized. + + @param[in,out] allHist 2D array holding all weather data + @param[in] startYear Start year of the simulation + @param[in] n_years Number of years in simulation + @param[in] method Number to identify which method to apply to generate + missing values (see details). + @param[in] optLOCF_nMax Maximum number of missing days per year (e.g., 5) + before imputation by `LOCF` throws an error. +*/ +void generateMissingWeather( + SW_WEATHER_HIST **allHist, + int startYear, + unsigned int n_years, + unsigned int method, + unsigned int optLOCF_nMax +) { + + int year; + unsigned int yearIndex, numDaysYear, day, iMissing; + + double yesterdayPPT = 0., yesterdayTempMin = 0., yesterdayTempMax = 0., + yesterdayCloudCov = 0., yesterdayWindSpeed = 0., yesterdayRelHum = 0., + yesterdayShortWR = 0., yesterdayActVP = 0.; + + Bool + any_missing, + missing_Tmax = swFALSE, missing_Tmin = swFALSE, missing_PPT = swFALSE, + missing_CloudCov = swFALSE, missing_WindSpeed = swFALSE, + missing_RelHum = swFALSE, missing_ActVP = swFALSE, + missing_ShortWR = swFALSE; + + + // Pass through method: return early + if (method == 0) return; + + + // Error out if method not implemented + if (method > 2) { + LogError( + logfp, + LOGFATAL, + "generateMissingWeather(): method = %u is not implemented.\n", + method + ); + } + + + // Loop over years + for (yearIndex = 0; yearIndex < n_years; yearIndex++) { + year = yearIndex + startYear; + numDaysYear = Time_get_lastdoy_y(year); + iMissing = 0; + + for (day = 0; day < numDaysYear; day++) { + missing_Tmax = (Bool) missing(allHist[yearIndex]->temp_max[day]); + missing_Tmin = (Bool) missing(allHist[yearIndex]->temp_min[day]); + missing_PPT = (Bool) missing(allHist[yearIndex]->ppt[day]); + + if (method != 2) { + // `SW_MKV_today()` currently generates only Tmax, Tmin, and PPT + missing_CloudCov = (Bool) missing(allHist[yearIndex]->cloudcov_daily[day]); + missing_WindSpeed = (Bool) missing(allHist[yearIndex]->windspeed_daily[day]); + missing_RelHum = (Bool) missing(allHist[yearIndex]->r_humidity_daily[day]); + missing_ShortWR = (Bool) missing(allHist[yearIndex]->shortWaveRad[day]); + missing_ActVP = (Bool) missing(allHist[yearIndex]->actualVaporPressure[day]); + } + + any_missing = (Bool) (missing_Tmax || missing_Tmin || missing_PPT || + missing_CloudCov || missing_WindSpeed || missing_RelHum || + missing_ShortWR || missing_ActVP); + + if (any_missing) { + // some of today's values are missing + + if (method == 2) { + // Weather generator + allHist[yearIndex]->ppt[day] = yesterdayPPT; + SW_MKV_today( + day, + &allHist[yearIndex]->temp_max[day], + &allHist[yearIndex]->temp_min[day], + &allHist[yearIndex]->ppt[day] + ); + + } else if (method == 1) { + // LOCF (temp, cloud cover, wind speed, relative humidity, + // shortwave radiation, and actual vapor pressure) + 0 (PPT) + allHist[yearIndex]->temp_max[day] = missing_Tmax ? + yesterdayTempMax : + allHist[yearIndex]->temp_max[day]; + + allHist[yearIndex]->temp_min[day] = missing_Tmin ? + yesterdayTempMin : + allHist[yearIndex]->temp_min[day]; + + allHist[yearIndex]->cloudcov_daily[day] = missing_CloudCov ? + yesterdayCloudCov : + allHist[yearIndex]->cloudcov_daily[day]; + + allHist[yearIndex]->windspeed_daily[day] = missing_WindSpeed ? + yesterdayWindSpeed : + allHist[yearIndex]->windspeed_daily[day]; + + allHist[yearIndex]->r_humidity_daily[day] = missing_RelHum ? + yesterdayRelHum : + allHist[yearIndex]->r_humidity_daily[day]; + + allHist[yearIndex]->shortWaveRad[day] = missing_ShortWR ? + yesterdayShortWR : + allHist[yearIndex]->shortWaveRad[day]; + + allHist[yearIndex]->actualVaporPressure[day] = missing_ActVP ? + yesterdayActVP : + allHist[yearIndex]->actualVaporPressure[day]; + + allHist[yearIndex]->ppt[day] = missing_PPT ? + 0. : + allHist[yearIndex]->ppt[day]; + + + // Throw an error if too many values per calendar year are missing + iMissing++; + + if (iMissing > optLOCF_nMax) { + LogError( + logfp, + LOGFATAL, + "generateMissingWeather(): more than %u days missing in year %u " + "and weather generator turned off.\n", + optLOCF_nMax, + year + ); + } + } + + + // Re-calculate average air temperature + allHist[yearIndex]->temp_avg[day] = ( + allHist[yearIndex]->temp_max[day] + allHist[yearIndex]->temp_min[day] + ) / 2.; + } + + yesterdayPPT = allHist[yearIndex]->ppt[day]; + yesterdayTempMax = allHist[yearIndex]->temp_max[day]; + yesterdayTempMin = allHist[yearIndex]->temp_min[day]; + yesterdayCloudCov = allHist[yearIndex]->cloudcov_daily[day]; + yesterdayWindSpeed = allHist[yearIndex]->windspeed_daily[day]; + yesterdayRelHum = allHist[yearIndex]->r_humidity_daily[day]; + yesterdayShortWR = allHist[yearIndex]->shortWaveRad[day]; + yesterdayActVP = allHist[yearIndex]->actualVaporPressure[day]; + } + } +} + +/** + @brief Check weather through all years/days within simultation and make sure all input values are reasonable + after possible weather generation and scaling. If a value is to be found unreasonable, the function will execute a program crash. + + @param weather Struct of type SW_WEATHER holding all relevant information pretaining to weather input data + */ +void checkAllWeather(SW_WEATHER *weather) { + + // Initialize any variables + TimeInt year, doy, numDaysInYear; + SW_WEATHER_HIST **weathHist = weather->allHist; + + double dailyMinTemp, dailyMaxTemp; + + // Loop through `allHist` years + for(year = 0; year < weather->n_years; year++) { + numDaysInYear = Time_get_lastdoy_y(year + weather->startYear); + + // Loop through `allHist` days + for(doy = 0; doy < numDaysInYear; doy++) { + + dailyMaxTemp = weathHist[year]->temp_max[doy]; + dailyMinTemp = weathHist[year]->temp_min[doy]; + + // Check if minimum temp greater than maximum temp + if(!missing(dailyMaxTemp) && !missing(dailyMinTemp) && + dailyMinTemp > dailyMaxTemp) { + + // Fail + LogError(logfp, LOGFATAL, "Daily input value for minimum temperature" + " is greater than daily input value for maximum temperature (minimum = %f, maximum = %f)" + " on day %d of year %d.", dailyMinTemp, dailyMaxTemp, doy + 1, year + weather->startYear); + } + // Otherwise, check if maximum or minimum temp, or + // dew point temp is not [-100, 100] + else if( + (!missing(dailyMaxTemp) && !missing(dailyMinTemp)) && + ((dailyMinTemp > 100. || dailyMinTemp < -100.) || + (dailyMaxTemp > 100. || dailyMaxTemp < -100.)) + ) { + + // Fail + LogError(logfp, LOGFATAL, "Daily minimum and/or maximum temperature on " + "day %d of year %d do not fit in the range of [-100, 100] C.", + doy, year + weather->startYear); + } + // Otherwise, check if precipitation is less than 0cm + else if(!missing(weathHist[year]->ppt[doy]) && weathHist[year]->ppt[doy] < 0) { + + // Fail + LogError(logfp, LOGFATAL, "Invalid daily precipitation value: %f cm (< 0) on day %d of year %d.", + weathHist[year]->ppt[doy], doy + 1, year + weather->startYear); + } + // Otherwise, check if relative humidity is less than 0% or greater than 100% + else if( + !missing(weathHist[year]->r_humidity_daily[doy]) && + (weathHist[year]->r_humidity_daily[doy] < 0. || + weathHist[year]->r_humidity_daily[doy] > 100.) + ) { + + // Fail + LogError(logfp, LOGFATAL, "Invalid daily/calculated relative humidity value did" + " not fall in the range [0, 100] % (relative humidity = %f). ", + weathHist[year]->r_humidity_daily[doy]); + } + // Otherwise, check if cloud cover was input and + // if the value is less than 0% or greater than 100% + else if( + !missing(weathHist[year]->cloudcov_daily[doy]) && + (weathHist[year]->cloudcov_daily[doy] < 0. || + weathHist[year]->cloudcov_daily[doy] > 100.) + ) { + + // Fail + LogError(logfp, LOGFATAL, "Invalid daily/calculated cloud cover value did" + " not fall in the range [0, 100] % (cloud cover = %f). ", + weathHist[year]->cloudcov_daily[doy]); + } + // Otherwise, check if wind speed is less than 0 m/s + else if(!missing(weathHist[year]->windspeed_daily[doy]) && + weathHist[year]->windspeed_daily[doy] < 0.) { + + // Fail + LogError(logfp, LOGFATAL, "Invalid daily wind speed value is less than zero." + "(wind speed = %f) on day %d of year %d. ", + weathHist[year]->windspeed_daily[doy], doy + 1, year + weather->startYear); + } + // Otherwise, check if radiation if less than 0 W/m^2 + else if(!missing(weathHist[year]->shortWaveRad[doy]) && + weathHist[year]->shortWaveRad[doy] < 0.) { + + // Fail + LogError(logfp, LOGFATAL, "Invalid daily shortwave radiation value is less than zero." + "(shortwave radation = %f) on day %d of year %d. ", + weathHist[year]->shortWaveRad[doy], doy + 1, year + weather->startYear); + } + // Otherwise, check if actual vapor pressure is less than 0 kPa + else if(!missing(weathHist[year]->actualVaporPressure[doy]) && + weathHist[year]->actualVaporPressure[doy] < 0.) { + + // Fail + LogError(logfp, LOGFATAL, "Invalid daily actual vapor pressure value is less than zero." + "(actual vapor pressure = %f) on day %d of year %d. ", + weathHist[year]->actualVaporPressure[doy], doy + 1, year + weather->startYear); + } + } + } +} + + +/** +@brief Clears weather history. +@note Used by rSOILWAT2 +*/ +void _clear_hist_weather(SW_WEATHER_HIST *yearWeather) { + /* --------------------------------------------------- */ + TimeInt d; + + for (d = 0; d < MAX_DAYS; d++) { + yearWeather->ppt[d] = SW_MISSING; + yearWeather->temp_max[d] = SW_MISSING; + yearWeather->temp_min[d] = SW_MISSING; + yearWeather->temp_avg[d] = SW_MISSING; + yearWeather->cloudcov_daily[d] = SW_MISSING; + yearWeather->windspeed_daily[d] = SW_MISSING; + yearWeather->r_humidity_daily[d] = SW_MISSING; + yearWeather->shortWaveRad[d] = SW_MISSING; + yearWeather->actualVaporPressure[d] = SW_MISSING; + } +} + + +/* =================================================== */ +/* =================================================== */ +/* Global Function Definitions */ +/* --------------------------------------------------- */ + +/** +@brief Constructor for SW_Weather. +*/ +void SW_WTH_construct(void) { + /* =================================================== */ + OutPeriod pd; + + // Clear the module structure: + memset(&SW_Weather, 0, sizeof(SW_Weather)); + + // Allocate output structures: + ForEachOutPeriod(pd) + { + SW_Weather.p_accu[pd] = (SW_WEATHER_OUTPUTS *) Mem_Calloc(1, + sizeof(SW_WEATHER_OUTPUTS), "SW_WTH_construct()"); + if (pd > eSW_Day) { + SW_Weather.p_oagg[pd] = (SW_WEATHER_OUTPUTS *) Mem_Calloc(1, + sizeof(SW_WEATHER_OUTPUTS), "SW_WTH_construct()"); + } + } + SW_Weather.n_years = 0; +} + +/** +@brief Deconstructor for SW_Weather and SW_Markov (if used) +*/ +void SW_WTH_deconstruct(void) +{ + OutPeriod pd; + + // De-allocate output structures: + ForEachOutPeriod(pd) + { + if (pd > eSW_Day && !isnull(SW_Weather.p_oagg[pd])) { + Mem_Free(SW_Weather.p_oagg[pd]); + SW_Weather.p_oagg[pd] = NULL; + } + + if (!isnull(SW_Weather.p_accu[pd])) { + Mem_Free(SW_Weather.p_accu[pd]); + SW_Weather.p_accu[pd] = NULL; + } + } + + if (SW_Weather.generateWeatherMethod == 2) { + SW_MKV_deconstruct(); + } + + deallocateAllWeather(&SW_Weather); +} + + +/** + @brief Allocate memory for `allHist` for `w` based on `n_years` +*/ +void allocateAllWeather(SW_WEATHER *w) { + unsigned int year; + + w->allHist = (SW_WEATHER_HIST **)malloc(sizeof(SW_WEATHER_HIST *) * w->n_years); + + for (year = 0; year < w->n_years; year++) { + + w->allHist[year] = (SW_WEATHER_HIST *)malloc(sizeof(SW_WEATHER_HIST)); + } +} + + +/** + @brief Helper function to SW_WTH_deconstruct to deallocate `allHist` of `w`. + */ + +void deallocateAllWeather(SW_WEATHER *w) { + unsigned int year; + + if(!isnull(w->allHist)) { + for(year = 0; year < w->n_years; year++) { + free(w->allHist[year]); + } + + free(w->allHist); + w->allHist = NULL; + } + +} + +/** +@brief Initialize weather variables for a simulation run + + They are used as default if weather for the first day is missing +*/ +void SW_WTH_init_run(void) { + /* setup today's weather because it's used as a default + * value when weather for the first day is missing. + * Notice that temps of 0. are reasonable for January + * (doy=1) and are below the critical temps for freezing + * and with ppt=0 there's nothing to freeze. + */ + SW_Weather.now.temp_max = SW_Weather.now.temp_min = 0.; + SW_Weather.now.ppt = SW_Weather.now.rain = 0.; + SW_Weather.snow = SW_Weather.snowmelt = SW_Weather.snowloss = 0.; + SW_Weather.snowRunoff = 0.; + SW_Weather.surfaceRunoff = SW_Weather.surfaceRunon = 0.; + SW_Weather.soil_inf = 0.; +} + +/** +@brief Guarantees that today's weather will not be invalid via -_todays_weth(). +*/ +void SW_WTH_new_day(void) { + /* =================================================== */ + /* History + * + * 20-Jul-2002 -- added growing season computation to + * facilitate the steppe/soilwat interface. + * 06-Dec-2002 -- modified the seasonal computation to + * account for n-s hemispheres. + * 16-Sep-2009 -- (drs) scaling factors were only applied to Tmin and Tmax + * but not to Taverage -> corrected + * 09-Oct-2009 -- (drs) commented out snow adjustement, because call moved to SW_Flow.c + * 20091015 (drs) ppt is divided into rain and snow + */ + + SW_WEATHER *w = &SW_Weather; + SW_WEATHER_NOW *wn = &SW_Weather.now; + + /* Indices to today's weather record in `allHist` */ + TimeInt + day = SW_Model.doy - 1, + yearIndex = SW_Model.year - SW_Weather.startYear; + +/* +#ifdef STEPWAT + TimeInt doy = SW_Model.doy; + Bool is_warm; + Bool is_growingseason = swFALSE; +#endif +*/ + + /* get the daily weather from allHist */ + + /* SOILWAT2 simulations requires non-missing values of forcing variables. + Exceptions + 1. shortwave radiation can be missing if cloud cover is not missing + 2. cloud cover can be missing if shortwave radiation is not missing + */ + if ( + missing(w->allHist[yearIndex]->temp_avg[day]) || + missing(w->allHist[yearIndex]->ppt[day]) || + missing(w->allHist[yearIndex]->windspeed_daily[day]) || + missing(w->allHist[yearIndex]->r_humidity_daily[day]) || + missing(w->allHist[yearIndex]->actualVaporPressure[day]) || + ( + missing(w->allHist[yearIndex]->shortWaveRad[day]) && + missing(w->allHist[yearIndex]->cloudcov_daily[day]) + ) + ) { + LogError( + logfp, + LOGFATAL, + "Missing weather data (day %u - %u) during simulation: " + "Tavg=%.2f, ppt=%.2f, wind=%.2f, rH=%.2f, vp=%.2f, rsds=%.2f / cloud=%.2f\n", + SW_Model.year, + SW_Model.doy, + w->allHist[yearIndex]->temp_avg[day], + w->allHist[yearIndex]->ppt[day], + w->allHist[yearIndex]->windspeed_daily[day], + w->allHist[yearIndex]->r_humidity_daily[day], + w->allHist[yearIndex]->actualVaporPressure[day], + w->allHist[yearIndex]->shortWaveRad[day], + w->allHist[yearIndex]->cloudcov_daily[day] + ); + } + + wn->temp_max = w->allHist[yearIndex]->temp_max[day]; + wn->temp_min = w->allHist[yearIndex]->temp_min[day]; + wn->ppt = w->allHist[yearIndex]->ppt[day]; + wn->cloudCover = w->allHist[yearIndex]->cloudcov_daily[day]; + wn->windSpeed = w->allHist[yearIndex]->windspeed_daily[day]; + wn->relHumidity = w->allHist[yearIndex]->r_humidity_daily[day]; + wn->shortWaveRad = w->allHist[yearIndex]->shortWaveRad[day]; + wn->actualVaporPressure = w->allHist[yearIndex]->actualVaporPressure[day]; + + wn->temp_avg = w->allHist[yearIndex]->temp_avg[day]; + + w->snow = w->snowmelt = w->snowloss = 0.; + w->snowRunoff = w->surfaceRunoff = w->surfaceRunon = w->soil_inf = 0.; + + if (w->use_snow) + { + SW_SWC_adjust_snow(wn->temp_min, wn->temp_max, wn->ppt, + &wn->rain, &w->snow, &w->snowmelt); + } else { + wn->rain = wn->ppt; + } +} + +/** +@brief Reads in file for SW_Weather. +*/ +void SW_WTH_setup(void) { + /* =================================================== */ + SW_WEATHER *w = &SW_Weather; + const int nitems = 35; + FILE *f; + int lineno = 0, month, x; + RealF sppt, stmax, stmin; + RealF sky, wind, rH, actVP, shortWaveRad; + + Bool *dailyInputFlags = w->dailyInputFlags; + + MyFileName = SW_F_name(eWeather); + f = OpenFile(MyFileName, "r"); + + while (GetALine(f, inbuf)) { + switch (lineno) { + case 0: + w->use_snow = itob(atoi(inbuf)); + break; + case 1: + w->pct_snowdrift = atoi(inbuf); + break; + case 2: + w->pct_snowRunoff = atoi(inbuf); + break; + + case 3: + x = atoi(inbuf); + w->use_weathergenerator_only = swFALSE; + + switch (x) { + case 0: + // As is + w->generateWeatherMethod = 0; + break; + + case 1: + // weather generator + w->generateWeatherMethod = 2; + break; + + case 2: + // weather generatory only + w->generateWeatherMethod = 2; + w->use_weathergenerator_only = swTRUE; + break; + + case 3: + // LOCF (temp) + 0 (PPT) + w->generateWeatherMethod = 1; + break; + + default: + CloseFile(&f); + LogError( + logfp, + LOGFATAL, + "%s : Bad missing weather method %d.", + MyFileName, + x + ); + } + break; + + case 4: + w->rng_seed = atoi(inbuf); + break; + + case 5: + w->use_cloudCoverMonthly = itob(atoi(inbuf)); + break; + + case 6: + w->use_windSpeedMonthly = itob(atoi(inbuf)); + break; + + case 7: + w->use_humidityMonthly = itob(atoi(inbuf)); + break; + + case 8: + w->dailyInputFlags[TEMP_MAX] = itob(atoi(inbuf)); + break; + + case 9: + w->dailyInputFlags[TEMP_MIN] = itob(atoi(inbuf)); + break; + + case 10: + w->dailyInputFlags[PPT] = itob(atoi(inbuf)); + break; + + case 11: + w->dailyInputFlags[CLOUD_COV] = itob(atoi(inbuf)); + break; + + case 12: + w->dailyInputFlags[WIND_SPEED] = itob(atoi(inbuf)); + break; + + case 13: + w->dailyInputFlags[WIND_EAST] = itob(atoi(inbuf)); + break; + + case 14: + w->dailyInputFlags[WIND_NORTH] = itob(atoi(inbuf)); + break; + + case 15: + w->dailyInputFlags[REL_HUMID] = itob(atoi(inbuf)); + break; + + case 16: + w->dailyInputFlags[REL_HUMID_MAX] = itob(atoi(inbuf)); + break; + + case 17: + w->dailyInputFlags[REL_HUMID_MIN] = itob(atoi(inbuf)); + break; + + case 18: + w->dailyInputFlags[SPEC_HUMID] = itob(atoi(inbuf)); + break; + + case 19: + w->dailyInputFlags[TEMP_DEWPOINT] = itob(atoi(inbuf)); + break; + + case 20: + w->dailyInputFlags[ACTUAL_VP] = itob(atoi(inbuf)); + break; + + case 21: + w->dailyInputFlags[SHORT_WR] = itob(atoi(inbuf)); + break; + + case 22: + w->desc_rsds = atoi(inbuf); + break; + + + default: + if (lineno == 5 + MAX_MONTHS) + break; + + x = sscanf( + inbuf, + "%d %f %f %f %f %f %f %f %f", + &month, &sppt, &stmax, &stmin, &sky, &wind, &rH, &actVP, &shortWaveRad + ); + + if (x != 9) { + CloseFile(&f); + LogError(logfp, LOGFATAL, "%s : Bad record %d.", MyFileName, lineno); + } + + month--; // convert to base0 + w->scale_precip[month] = sppt; + w->scale_temp_max[month] = stmax; + w->scale_temp_min[month] = stmin; + w->scale_skyCover[month] = sky; + w->scale_wind[month] = wind; + w->scale_rH[month] = rH; + w->scale_actVapPress[month] = actVP; + w->scale_shortWaveRad[month] = shortWaveRad; + } + + lineno++; + } + + SW_WeatherPrefix(w->name_prefix); + CloseFile(&f); + + if (lineno < nitems) { + LogError(logfp, LOGFATAL, "%s : Too few input lines.", MyFileName); + } + + // Calculate value indices for `allHist` + set_dailyInputIndices( + dailyInputFlags, + w->dailyInputIndices, + &w->n_input_forcings + ); + + check_and_update_dailyInputFlags( + w->use_cloudCoverMonthly, + w->use_humidityMonthly, + w->use_windSpeedMonthly, + dailyInputFlags + ); +} + + +/** + @brief Set and count indices of daily inputs based on user-set flags + + @param[in] dailyInputFlags An array of size #MAX_INPUT_COLUMNS + indicating which daily input variable is active (TRUE). + @param[out] dailyInputIndices An array of size #MAX_INPUT_COLUMNS + with the calculated column number of all possible daily input variables. + @param[out] n_input_forcings The number of active daily input variables. +*/ +void set_dailyInputIndices( + Bool dailyInputFlags[MAX_INPUT_COLUMNS], + unsigned int dailyInputIndices[MAX_INPUT_COLUMNS], + unsigned int *n_input_forcings +) { + int currFlag; + + // Default n_input_forcings to 0 + *n_input_forcings = 0; + + // Loop through MAX_INPUT_COLUMNS (# of input flags) + for(currFlag = 0; currFlag < MAX_INPUT_COLUMNS; currFlag++) + { + // Default the current index to zero + dailyInputIndices[currFlag] = 0; + + // Check if current flag is set + if (dailyInputFlags[currFlag]) { + + // Set current index to current number of "n_input_forcings" + // which is the current number of flags found + dailyInputIndices[currFlag] = *n_input_forcings; + + // Increment "n_input_forcings" + (*n_input_forcings)++; + } + } +} + + +/* + * Turn off necessary flags. This happens after the calculation of + input indices due to the fact that setting before calculating may + result in an incorrect `n_input_forcings` in SW_WEATHER, and unexpectedly + crash the program in `_read_weather_hist()`. + + * Check if monthly flags have been chosen to override daily flags. + * Aside from checking for purely a monthly flag, we must make sure we have + daily flags to turn off instead of attemping to turn off flags that are already off. +*/ +void check_and_update_dailyInputFlags( + Bool use_cloudCoverMonthly, + Bool use_humidityMonthly, + Bool use_windSpeedMonthly, + Bool *dailyInputFlags +) { + Bool monthlyFlagPrioritized = swFALSE; + + // Check if temperature max/min flags are unevenly set (1/0) or (0/1) + if((dailyInputFlags[TEMP_MAX] && !dailyInputFlags[TEMP_MIN]) || + (!dailyInputFlags[TEMP_MAX] && dailyInputFlags[TEMP_MIN])) { + + // Fail + LogError(logfp, LOGFATAL, "Maximum/minimum temperature flags are unevenly set. " + "Both flags for temperature must be set."); + + } + + // Check if minimum and maximum temperature, or precipitation flags are not set + if((!dailyInputFlags[TEMP_MAX] && !dailyInputFlags[TEMP_MIN]) || !dailyInputFlags[PPT]) { + // Fail + LogError(logfp, LOGFATAL, "Both maximum/minimum temperature and/or precipitation flag(s) " + "are not set. All three flags must be set."); + } + + if(use_windSpeedMonthly && (dailyInputFlags[WIND_SPEED] || + dailyInputFlags[WIND_EAST] || + dailyInputFlags[WIND_NORTH])) { + + dailyInputFlags[WIND_SPEED] = swFALSE; + dailyInputFlags[WIND_EAST] = swFALSE; + dailyInputFlags[WIND_NORTH] = swFALSE; + monthlyFlagPrioritized = swTRUE; + } + + if(use_humidityMonthly) { + if(dailyInputFlags[REL_HUMID] || dailyInputFlags[REL_HUMID_MAX] || + dailyInputFlags[REL_HUMID_MIN] || dailyInputFlags[SPEC_HUMID] || + dailyInputFlags[ACTUAL_VP]) { + + dailyInputFlags[REL_HUMID] = swFALSE; + dailyInputFlags[REL_HUMID_MAX] = swFALSE; + dailyInputFlags[REL_HUMID_MIN] = swFALSE; + dailyInputFlags[SPEC_HUMID] = swFALSE; + dailyInputFlags[ACTUAL_VP] = swFALSE; + monthlyFlagPrioritized = swTRUE; + } + } + + if(use_cloudCoverMonthly && dailyInputFlags[CLOUD_COV]) { + dailyInputFlags[CLOUD_COV] = swFALSE; + monthlyFlagPrioritized = swTRUE; + } + + // Check if max/min relative humidity flags are set unevenly (1/0) or (0/1) + if((dailyInputFlags[REL_HUMID_MAX] && !dailyInputFlags[REL_HUMID_MIN]) || + (!dailyInputFlags[REL_HUMID_MAX] && dailyInputFlags[REL_HUMID_MIN])) { + + // Fail + LogError(logfp, LOGFATAL, "Max/min relative humidity flags are unevenly set. " + "Both flags for maximum/minimum relative humidity must be the " + "same (1 or 0)."); + + } + + // Check if east/north wind speed flags are set unevenly (1/0) or (0/1) + if((dailyInputFlags[WIND_EAST] && !dailyInputFlags[WIND_NORTH]) || + (!dailyInputFlags[WIND_EAST] && dailyInputFlags[WIND_NORTH])) { + + // Fail + LogError(logfp, LOGFATAL, "East/north wind speed flags are unevenly set. " + "Both flags for east/north wind speed components " + "must be the same (1 or 0)."); + + } + + // Check to see if any daily flags were turned off due to a set monthly flag + if(monthlyFlagPrioritized) { + // Give the user a generalized note + LogError(logfp, LOGNOTE, "One or more daily flags have been turned off due to a set monthly " + "input flag which overrides daily flags. Please see `weathsetup.in` " + "to change this if it was not the desired action."); + } +} + + +/** + @brief (Re-)allocate `allHist` and read daily meteorological input from disk + + The weather generator is not run and daily values are not scaled with + monthly climate parameters, see `SW_WTH_finalize_all_weather()` instead. +*/ +void SW_WTH_read(void) { + + // Deallocate (previous, if any) `allHist` + // (using value of `SW_Weather.n_years` previously used to allocate) + // `SW_WTH_construct()` sets `n_years` to zero + deallocateAllWeather(&SW_Weather); + + // Update number of years and first calendar year represented + SW_Weather.n_years = SW_Model.endyr - SW_Model.startyr + 1; + SW_Weather.startYear = SW_Model.startyr; + + // Allocate new `allHist` (based on current `SW_Weather.n_years`) + allocateAllWeather(&SW_Weather); + + // Read daily meteorological input from disk (if available) + readAllWeather( + SW_Weather.allHist, + SW_Weather.startYear, + SW_Weather.n_years, + SW_Weather.use_weathergenerator_only, + SW_Weather.name_prefix, + SW_Weather.use_cloudCoverMonthly, + SW_Weather.use_humidityMonthly, + SW_Weather.use_windSpeedMonthly, + SW_Weather.n_input_forcings, + SW_Weather.dailyInputIndices, + SW_Weather.dailyInputFlags, + SW_Sky.cloudcov, + SW_Sky.windspeed, + SW_Sky.r_humidity + ); +} + + + +/** @brief Read the historical (observed) weather file for a simulation year. + + The naming convection of the weather input files: + `[weather-data path/][weather-file prefix].[year]` + + Format of a input file (white-space separated values): + `doy maxtemp(°C) mintemp (°C) precipitation (cm)` + + @note Used by rSOILWAT2 + + @param year Current year within the simulation + @param yearWeather Current year's weather array that is to be filled by function + @param weather_prefix File name of weather data without extension. + @param n_input_forcings Number of read-in columns from disk + @param dailyInputIndices An array of size MAX_INPUT_COLUMNS holding the calculated + column number of which a certain variable resides + @param dailyInputFlags An array of size MAX_INPUT_COLUMNS holding booleans specifying + what variable has daily input on disk +*/ +void _read_weather_hist( + TimeInt year, + SW_WEATHER_HIST *yearWeather, + char weather_prefix[], + unsigned int n_input_forcings, + unsigned int *dailyInputIndices, + Bool *dailyInputFlags +) { + /* =================================================== */ + /* Read the historical (measured) weather files. + * Format is + * day-of-month, month number, year, doy, mintemp, maxtemp, ppt + * + * I dislike the inclusion of the first three columns + * but this is the old format. If a new format emerges + * these columns will likely be removed. + * + * temps are in degrees C, ppt is in cm, + * + * 26-Jan-02 - changed format of the input weather files. + * + */ + + FILE *f; + unsigned int x, lineno = 0; + int doy; + // TimeInt mon, j, k = 0; + // RealF acc = 0.0; + RealD weathInput[MAX_INPUT_COLUMNS]; + + Bool hasMaxMinTemp = (Bool) (dailyInputFlags[TEMP_MAX] && dailyInputFlags[TEMP_MIN]); + Bool hasMaxMinRelHumid = (Bool) (dailyInputFlags[REL_HUMID_MAX] && dailyInputFlags[REL_HUMID_MIN]); + Bool hasEastNorthWind = (Bool) (dailyInputFlags[WIND_EAST] && dailyInputFlags[WIND_NORTH]); + + // Calculate if daily input values of humidity are to be used instead of + // being interpolated from monthly values + Bool useHumidityDaily = (Bool) (hasMaxMinRelHumid || dailyInputFlags[REL_HUMID] || + dailyInputFlags[SPEC_HUMID] || dailyInputFlags[ACTUAL_VP]); + + double es, e, relHum, tempSlope, svpVal; + + char fname[MAX_FILENAMESIZE]; + + // Create file name: `[weather-file prefix].[year]` + snprintf(fname, MAX_FILENAMESIZE, "%s.%4d", weather_prefix, year); + + if (NULL == (f = fopen(fname, "r"))) + return; + + while (GetALine(f, inbuf)) { + lineno++; + x = sscanf(inbuf, "%d %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf", + &doy, &weathInput[0], &weathInput[1], &weathInput[2], &weathInput[3], + &weathInput[4], &weathInput[5], &weathInput[6], &weathInput[7], + &weathInput[8], &weathInput[9], &weathInput[10], &weathInput[11], + &weathInput[12], &weathInput[13]); + + if (x != n_input_forcings + 1) { + CloseFile(&f); + LogError(logfp, LOGFATAL, "%s : Incomplete record %d (doy=%d).", fname, lineno, doy); + } + if (x > MAX_INPUT_COLUMNS + 1) { + CloseFile(&f); + LogError(logfp, LOGFATAL, "%s : Too many values in record %d (doy=%d).", fname, lineno, doy); + } + if (doy < 1 || doy > MAX_DAYS) { + CloseFile(&f); + LogError(logfp, LOGFATAL, "%s : Day of year out of range, line %d.", fname, lineno); + } + + /* --- Make the assignments ---- */ + doy--; // base1 -> base0 + yearWeather->temp_max[doy] = weathInput[dailyInputIndices[TEMP_MAX]]; + yearWeather->temp_min[doy] = weathInput[dailyInputIndices[TEMP_MIN]]; + yearWeather->ppt[doy] = weathInput[dailyInputIndices[PPT]]; + + // Calculate average air temperature if min/max not missing + if (!missing(weathInput[dailyInputIndices[TEMP_MAX]]) && + !missing(weathInput[dailyInputIndices[TEMP_MIN]])) { + + yearWeather->temp_avg[doy] = (weathInput[dailyInputIndices[TEMP_MAX]] + + weathInput[dailyInputIndices[TEMP_MIN]]) / 2.0; + } + + if(dailyInputFlags[CLOUD_COV]) { + yearWeather->cloudcov_daily[doy] = weathInput[dailyInputIndices[CLOUD_COV]]; + } + + if(dailyInputFlags[WIND_SPEED]) { + yearWeather->windspeed_daily[doy] = weathInput[dailyInputIndices[WIND_SPEED]]; + + } else if(hasEastNorthWind) { + + // Make sure wind is not averaged calculated with any instances of SW_MISSING + if(!missing(weathInput[dailyInputIndices[WIND_EAST]]) && + !missing(weathInput[dailyInputIndices[WIND_NORTH]])) { + + yearWeather->windspeed_daily[doy] = sqrt(squared(weathInput[dailyInputIndices[WIND_EAST]]) + + squared(weathInput[dailyInputIndices[WIND_NORTH]])); + } else { + yearWeather->windspeed_daily[doy] = SW_MISSING; + } + } + + // Check to see if daily humidity values are being used + if(useHumidityDaily) { + if(hasMaxMinRelHumid) { + + // Make sure relative humidity is not averaged from any instances of SW_MISSING + if(!missing(weathInput[dailyInputIndices[REL_HUMID_MAX]]) && + !missing(weathInput[dailyInputIndices[REL_HUMID_MIN]])) { + + yearWeather->r_humidity_daily[doy] = (weathInput[dailyInputIndices[REL_HUMID_MAX]] + + weathInput[dailyInputIndices[REL_HUMID_MIN]]) / 2; + } + + } else if(dailyInputFlags[REL_HUMID]) { + yearWeather->r_humidity_daily[doy] = weathInput[dailyInputIndices[REL_HUMID]]; + + } else if(dailyInputFlags[SPEC_HUMID]) { + + // Make sure the calculation of relative humidity will not be + // executed while average temperature and/or specific humidity are + // holding the value "SW_MISSING" + if(!missing(yearWeather->temp_avg[doy]) && + !missing(weathInput[dailyInputIndices[SPEC_HUMID]])) { + + // Specific Humidity (Bolton 1980) + es = (6.112 * exp(17.67 * yearWeather->temp_avg[doy]) / + (yearWeather->temp_avg[doy] + 243.5)); + + e = (weathInput[dailyInputIndices[SPEC_HUMID]] * 1013.25) / + (.378 * weathInput[dailyInputIndices[SPEC_HUMID]] + .622); + + relHum = e / es; + relHum = max(0., relHum); + + yearWeather->r_humidity_daily[doy] = min(100., relHum); + + } else { + // Set relative humidity to "SW_MISSING" + yearWeather->r_humidity_daily[doy] = SW_MISSING; + } + + } + + // Deal with actual vapor pressure + if(dailyInputFlags[ACTUAL_VP]){ + + yearWeather->actualVaporPressure[doy] = weathInput[dailyInputIndices[ACTUAL_VP]]; + + } else if(dailyInputFlags[TEMP_DEWPOINT] && + !missing(weathInput[dailyInputIndices[TEMP_DEWPOINT]])) { + + yearWeather->actualVaporPressure[doy] = + actualVaporPressure3(weathInput[dailyInputIndices[TEMP_DEWPOINT]]); + + } else if(hasMaxMinTemp && hasMaxMinRelHumid) { + + // Make sure the calculation of actual vapor pressure will not be + // executed while max and/or min temperature and/or relative humidity + // are holding the value "SW_MISSING" + if(!missing(yearWeather->temp_max[doy]) && + !missing(yearWeather->temp_min[doy]) && + !missing(weathInput[dailyInputIndices[REL_HUMID_MAX]]) && + !missing(weathInput[dailyInputIndices[REL_HUMID_MIN]])) { + + yearWeather->actualVaporPressure[doy] = + actualVaporPressure2(weathInput[dailyInputIndices[REL_HUMID_MAX]], + weathInput[dailyInputIndices[REL_HUMID_MIN]], + weathInput[dailyInputIndices[TEMP_MAX]], + weathInput[dailyInputIndices[TEMP_MIN]]); + } else { + // Set actual vapor pressure to "SW_MISSING" + yearWeather->actualVaporPressure[doy] = SW_MISSING; + } + + } else if(dailyInputFlags[REL_HUMID] || dailyInputFlags[SPEC_HUMID]) { + + // Make sure the daily values for relative humidity and average + // temperature are not SW_MISSING + if(!missing(yearWeather->r_humidity_daily[doy]) && + !missing(yearWeather->temp_avg[doy])) { + + yearWeather->actualVaporPressure[doy] = + actualVaporPressure1(yearWeather->r_humidity_daily[doy], + yearWeather->temp_avg[doy]); + } else { + yearWeather->actualVaporPressure[doy] = SW_MISSING; + } + } + + // Check if a calculation of relative humidity is available using dewpoint temperature + // or actual vapor pressure, but only if the daily value of relative humidity + // is "SW_MISSING" + if(missing(yearWeather->r_humidity_daily[doy]) && + (dailyInputFlags[ACTUAL_VP] || dailyInputFlags[TEMP_DEWPOINT])) { + + // Make sure the calculation of relative humidity will not be + // executed while average temperature and/or actual vapor pressure + // hold the value "SW_MISSING" + if(!missing(yearWeather->temp_avg[doy]) && + !missing(yearWeather->actualVaporPressure[doy])) { + + svpVal = svp(yearWeather->temp_avg[doy], &tempSlope); + + yearWeather->r_humidity_daily[doy] = + yearWeather->actualVaporPressure[doy] / svpVal; + } + } + } + + + if (dailyInputFlags[SHORT_WR]) { + yearWeather->shortWaveRad[doy] = weathInput[dailyInputIndices[SHORT_WR]]; + } + + + // Calculate annual average temperature based on historical input, i.e., + // the `temp_year_avg` calculated here is prospective and unsuitable when + // the weather generator is used to generate values for the + // current simulation year, e.g., STEPWAT2 + /* + if (!missing(tmpmax) && !missing(tmpmin)) { + k++; + acc += wh->temp_avg[doy]; + } + */ + } /* end of input lines */ + + // Calculate annual average temperature based on historical input + // wh->temp_year_avg = acc / (k + 0.0); + + // Calculate monthly average temperature based on historical input + // the `temp_month_avg` calculated here is prospective and unsuitable when + // the weather generator is used to generate values for the + // current simulation year, e.g., STEPWAT2 + /* + for (mon = Jan; mon <= Dec; i++) { + k = Time_days_in_month(mon); + + acc = 0.0; + for (j = 0; j < k; j++) + { + acc += wh->temp_avg[j + x]; + } + wh->temp_month_avg[i] = acc / (k + 0.0); + } + */ + + fclose(f); +} + +void allocateClimateStructs(int numYears, SW_CLIMATE_YEARLY *climateOutput, + SW_CLIMATE_CLIM *climateAverages) { + + int month; + + climateOutput->PPTMon_cm = (double **)malloc(sizeof(double *) * MAX_MONTHS); + climateOutput->meanTempMon_C = (double **)malloc(sizeof(double *) * MAX_MONTHS); + climateOutput->maxTempMon_C = (double **)malloc(sizeof(double *) * MAX_MONTHS); + climateOutput->minTempMon_C = (double **)malloc(sizeof(double *) * MAX_MONTHS); + + for(month = 0; month < MAX_MONTHS; month++) { + climateOutput->PPTMon_cm[month] = (double *)malloc(sizeof(double) * numYears); + climateOutput->meanTempMon_C[month] = (double *)malloc(sizeof(double) * numYears); + climateOutput->maxTempMon_C[month] = (double *)malloc(sizeof(double) * numYears); + climateOutput->minTempMon_C[month] = (double *)malloc(sizeof(double) * numYears); + } + + climateOutput->PPT_cm = (double *)malloc(sizeof(double) * numYears); + climateOutput->PPT7thMon_mm = (double *)malloc(sizeof(double) * numYears); + climateOutput->meanTemp_C = (double *)malloc(sizeof(double) * numYears); + climateOutput->meanTempDriestQtr_C = (double *)malloc(sizeof(double) * numYears); + climateOutput->minTemp2ndMon_C = (double *)malloc(sizeof(double) * numYears); + climateOutput->minTemp7thMon_C = (double *)malloc(sizeof(double) * numYears); + climateOutput->frostFree_days = (double *)malloc(sizeof(double) * numYears); + climateOutput->ddAbove65F_degday = (double *)malloc(sizeof(double) * numYears); + climateAverages->meanTempMon_C = (double *)malloc(sizeof(double) * MAX_MONTHS); + climateAverages->maxTempMon_C = (double *)malloc(sizeof(double) * MAX_MONTHS); + climateAverages->minTempMon_C = (double *)malloc(sizeof(double) * MAX_MONTHS); + climateAverages->PPTMon_cm = (double *)malloc(sizeof(double) * MAX_MONTHS); + climateAverages->sdC4 = (double *)malloc(sizeof(double) * 3); + climateAverages->sdCheatgrass = (double *)malloc(sizeof(double) * 3); +} + +void deallocateClimateStructs(SW_CLIMATE_YEARLY *climateOutput, + SW_CLIMATE_CLIM *climateAverages) { + + int month; + + free(climateOutput->PPT_cm); + free(climateOutput->PPT7thMon_mm); + free(climateOutput->meanTemp_C); + free(climateOutput->meanTempDriestQtr_C); + free(climateOutput->minTemp2ndMon_C); + free(climateOutput->minTemp7thMon_C); + free(climateOutput->frostFree_days); + free(climateOutput->ddAbove65F_degday); + free(climateAverages->meanTempMon_C); + free(climateAverages->maxTempMon_C); + free(climateAverages->minTempMon_C); + free(climateAverages->PPTMon_cm); + free(climateAverages->sdC4); + free(climateAverages->sdCheatgrass); + + for(month = 0; month < MAX_MONTHS; month++) { + free(climateOutput->PPTMon_cm[month]); + free(climateOutput->meanTempMon_C[month]); + free(climateOutput->maxTempMon_C[month]); + free(climateOutput->minTempMon_C[month]); + } + + free(climateOutput->PPTMon_cm); + free(climateOutput->meanTempMon_C); + free(climateOutput->maxTempMon_C); + free(climateOutput->minTempMon_C); +} + +#ifdef DEBUG_MEM +#include "include/myMemory.h" +/*======================================================*/ +void SW_WTH_SetMemoryRefs( void) { + /* when debugging memory problems, use the bookkeeping + code in myMemory.c + This routine sets the known memory refs in this module + so they can be checked for leaks, etc. All refs will + have been cleared by a call to ClearMemoryRefs() before + this, and will be checked via CheckMemoryRefs() after + this, most likely in the main() function. + */ +} + +#endif diff --git a/Times.c b/src/Times.c similarity index 80% rename from Times.c rename to src/Times.c index ac473ae5b..f7bd4e687 100644 --- a/Times.c +++ b/src/Times.c @@ -27,8 +27,8 @@ #include #include #include -#include "generic.h" -#include "Times.h" +#include "include/generic.h" +#include "include/Times.h" /* =================================================== */ @@ -179,19 +179,32 @@ Bool isleapyear(const TimeInt year) { @date 09/22/2011 @param[in] monthlyValues Array with values for each month + @param[in] interpAsBase1 Boolean value specifying if "dailyValues" should be base1 or base0 @param[out] dailyValues Array with linearly interpolated values for each day - @note dailyValues[0] will always be 0 as the function does not modify it - since there is no day 0 (doy is base1), furthermore dailyValues is - only sub-setted by base1 objects in the model. + @note If `interpAsBase1` is TRUE, then `dailyValues[0]` is ignored (with a value of 0) because a `base1` + index for "day of year" (doy) is used, i.e., the value on the first day of year (`doy = 1`) is located in `dailyValues[1]`. + If `interpAsBase1` is FALSE, then `dailyValues[0]` is utilized because a `base0` index for "day of year" (doy) is used, + i.e., the value on the first day of year (`doy = 0`) is located in `dailyValues[0]`. **/ -void interpolate_monthlyValues(double monthlyValues[], double dailyValues[]) { +void interpolate_monthlyValues(double monthlyValues[], Bool interpAsBase1, + double dailyValues[]) { unsigned int doy, mday, month, month2 = NoMonth, nmdays; + unsigned int startdoy = 1, endDay = MAX_DAYS, doyOffset = 0; double sign = 1.; - for (doy = 1; doy <= MAX_DAYS; doy++) { - mday = doy2mday(doy); - month = doy2month(doy); + // Check if we are interpolating values as base1 + if(!interpAsBase1) { + + // Make `dailyValues` base0 + startdoy = 0; + endDay = MAX_DAYS - 1; + doyOffset = 1; + } + + for (doy = startdoy; doy <= endDay; doy++) { + mday = doy2mday(doy + doyOffset); + month = doy2month(doy + doyOffset); if (mday == 15) { dailyValues[doy] = monthlyValues[month]; diff --git a/filefuncs.c b/src/filefuncs.c similarity index 79% rename from filefuncs.c rename to src/filefuncs.c index bfdd15e5d..06ec79a4e 100644 --- a/filefuncs.c +++ b/src/filefuncs.c @@ -13,10 +13,10 @@ #include #endif -#include "filefuncs.h" -#include "generic.h" // externs errstr -#include "myMemory.h" -#include "SW_Defines.h" +#include "include/filefuncs.h" +#include "include/generic.h" // externs errstr +#include "include/myMemory.h" +#include "include/SW_Defines.h" #ifdef RSOILWAT #include // for REvprintf(), error(), and warning() #endif @@ -140,52 +140,75 @@ void sw_error(int code, const char *format, ...) /**************************************************************/ void LogError(FILE *fp, const int mode, const char *fmt, ...) { - /* uses global variable logged to indicate that a log message - * was sent to output, which can be used to inform user, etc. - * - * 9-Dec-03 (cwb) Modified to accept argument list similar - * to fprintf() so sprintf(errstr...) doesn't need - * to be called each time replacement args occur. - */ + /* uses global variable logged to indicate that a log message + * was sent to output, which can be used to inform user, etc. + * + * 9-Dec-03 (cwb) Modified to accept argument list similar + * to fprintf() so sprintf(errstr...) doesn't need + * to be called each time replacement args occur. + */ + + char outfmt[MAX_ERROR] = {0}; /* to prepend err type str */ + char buf[MAX_ERROR]; + va_list args; + + va_start(args, fmt); + + if (LOGQUIET & mode) + strcpy(outfmt, ""); + else if (LOGNOTE & mode) + strcpy(outfmt, "NOTE: "); + else if (LOGWARN & mode) + strcpy(outfmt, "WARNING: "); + else if (LOGERROR & mode) + strcpy(outfmt, "ERROR: "); + + strcat(outfmt, fmt); + strcat(outfmt, "\n"); - char outfmt[MAX_ERROR] = {0}; /* to prepend err type str */ - va_list args; + #ifdef RSOILWAT + vsnprintf(buf, MAX_ERROR, outfmt, args); - va_start(args, fmt); + if (LOGEXIT & mode) { + // exit with error and always show message + error(buf); - if (LOGQUIET & mode) - strcpy(outfmt, ""); - else if (LOGNOTE & mode) - strcpy(outfmt, "NOTE: "); - else if (LOGWARN & mode) - strcpy(outfmt, "WARNING: "); - else if (LOGERROR & mode) - strcpy(outfmt, "ERROR: "); + } else if(fp != NULL) { + // send warning message only if not silenced (fp is not NULL) + warning(buf); + } - strcat(outfmt, fmt); - strcat(outfmt, "\n"); + #else + if (isnull(fp)) { + // silence messages (fp is NULL) except errors (which go to stderr) + if (LOGEXIT & mode) { + vsnprintf(buf, MAX_ERROR, outfmt, args); + sw_error(-1, buf); + } - #ifdef RSOILWAT - if (fp != NULL) { - REvprintf(outfmt, args); - } - #else - int check_eof; - check_eof = (EOF == vfprintf(fp, outfmt, args)); + } else { + // send message to fp - if (check_eof) - sw_error(0, "SYSTEM: Cannot write to FILE *fp in LogError()\n"); - fflush(fp); - #endif + int check_eof; + check_eof = (EOF == vfprintf(fp, outfmt, args)); + if (check_eof) { + sw_error(0, "SYSTEM: Cannot write to FILE *fp in LogError()\n"); + } - logged = swTRUE; - va_end(args); + fflush(fp); - if (LOGEXIT & mode) { - sw_error(-1, "@ generic.c LogError"); - } + if (LOGEXIT & mode) { + // exit with error + sw_error(-1, "@ generic.c LogError"); + } + } + #endif + + + logged = swTRUE; + va_end(args); } diff --git a/generic.c b/src/generic.c similarity index 86% rename from generic.c rename to src/generic.c index 459303d1b..97d48967d 100644 --- a/generic.c +++ b/src/generic.c @@ -19,8 +19,8 @@ 05/31/2012 (DLM) added st_getBounds() function for use in the soil_temperature function in SW_Flow_lib.c */ -#include "generic.h" -#include "filefuncs.h" +#include "include/generic.h" +#include "include/filefuncs.h" @@ -421,3 +421,59 @@ double final_running_sd(unsigned int n, double ssqr) { return (n > 1) ? sqrt(ssqr / (n - 1)) : 0.; } + +/** + @brief Takes the average over an array of size length + + @param values Array of size length holding the values to be averaged + @param length Size of input array may be years, months, days, etc. + + @note When a value is SW_MISSING, the function sees it as a value to skip and ignores it to not influence + the mean. + */ +double mean(double values[], int length) { + + int index, finalLength = 0; + double total = 0.0, currentVal; + + for(index = 0; index < length; index++) { + currentVal = values[index]; + + if(!missing(currentVal)) { + total += currentVal; + finalLength++; + } + } + + return total / finalLength; +} + +/** + @brief Takes the standard deviation of all values in an array all at once. + + @param inputArray Array containing values to find the standard deviation of + @param length Size of the input array + + @note This function is preferred to be used when the set is small. If the standard deviation of a large set is needed, + attempt a running standard deviation. + + @note When a value is SW_MISSING, the function sees it as a value to skip and ignores it to not influence + the standard deviation. + */ +double standardDeviation(double inputArray[], int length) { + + int index, finalLength = 0; + double arrayMean = mean(inputArray, length), total = 0.0, currentVal; + + for(index = 0; index < length; index++) { + currentVal = inputArray[index]; + + if(!missing(currentVal)) { + total += (currentVal - arrayMean) * (currentVal - arrayMean); + finalLength++; + } + } + + return sqrt(total / (finalLength - 1)); + +} diff --git a/mymemory.c b/src/mymemory.c similarity index 95% rename from mymemory.c rename to src/mymemory.c index 92d2f84c4..b6af21380 100644 --- a/mymemory.c +++ b/src/mymemory.c @@ -28,9 +28,9 @@ #include #include #include -#include "filefuncs.h" -#include "generic.h" -#include "myMemory.h" +#include "include/filefuncs.h" +#include "include/generic.h" +#include "include/myMemory.h" /* not sure how to handle this block migrated from gen_funcs.c */ #ifdef DEBUG_MEM_X diff --git a/rands.c b/src/rands.c similarity index 94% rename from rands.c rename to src/rands.c index f9a3e3605..da8bd9013 100644 --- a/rands.c +++ b/src/rands.c @@ -4,12 +4,12 @@ #include #include #include -#include "generic.h" -#include "rands.h" -#include "myMemory.h" -#include "filefuncs.h" +#include "include/generic.h" +#include "include/rands.h" +#include "include/myMemory.h" +#include "include/filefuncs.h" -#include "pcg/pcg_basic.h" +#include "external/pcg/pcg_basic.h" #ifdef RSOILWAT @@ -388,7 +388,7 @@ double RandNorm(double mean, double stddev, pcg32_random_t* pcg_rng) { The beta distribution has two shape parameters \f$a\f$ and \f$b\f$. The density is - \f[\frac{x ^ (a - 1) * (1 - x) ^ (b - 1)}{Beta(a, b)}\f] + \f[\frac{x ^ {(a - 1)} * (1 - x) ^ {(b - 1)}}{Beta(a, b)}\f] for \f$0 < x < 1\f$ The code for RandBeta was taken from ranlib, a FORTRAN77 library. Original diff --git a/test/test_SW_Site.cc b/test/test_SW_Site.cc deleted file mode 100644 index 7b8f97fd1..000000000 --- a/test/test_SW_Site.cc +++ /dev/null @@ -1,203 +0,0 @@ -#include "gtest/gtest.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include // for 'typeid' - -#include "../generic.h" -#include "../myMemory.h" -#include "../filefuncs.h" -#include "../rands.h" -#include "../Times.h" -#include "../SW_Defines.h" -#include "../SW_Times.h" -#include "../SW_Files.h" -#include "../SW_Carbon.h" -#include "../SW_Site.h" // externs `_TranspRgnBounds` -#include "../SW_VegProd.h" -#include "../SW_VegEstab.h" -#include "../SW_Model.h" -#include "../SW_SoilWater.h" -#include "../SW_Weather.h" -#include "../SW_Markov.h" -#include "../SW_Sky.h" - -#include "sw_testhelpers.h" - - - -namespace { - // Test the water equation function 'water_eqn' - TEST(SWSiteTest, WaterEquation) { - - //declare inputs - RealD fractionGravel = 0.1, sand = .33, clay =.33; - LyrIndex n = 1; - - water_eqn(fractionGravel, sand, clay, n); - - // Test swcBulk_saturated - EXPECT_GT(SW_Site.lyr[n]->swcBulk_saturated, 0.); // The swcBulk_saturated should be greater than 0 - EXPECT_LT(SW_Site.lyr[n]->swcBulk_saturated, SW_Site.lyr[n]->width); // The swcBulk_saturated can't be greater than the width of the layer - - // Test thetasMatric - EXPECT_GT(SW_Site.lyr[n]->thetasMatric, 36.3); /* Value should always be greater - than 36.3 based upon complete consideration of potential range of sand and clay values */ - EXPECT_LT(SW_Site.lyr[n]->thetasMatric, 46.8); /* Value should always be less - than 46.8 based upon complete consideration of potential range of sand and clay values */ - EXPECT_DOUBLE_EQ(SW_Site.lyr[n]->thetasMatric, 44.593); /* If sand is .33 and - clay is .33, thetasMatric should be 44.593 */ - - // Test psisMatric - EXPECT_GT(SW_Site.lyr[n]->psisMatric, 3.890451); /* Value should always be greater - than 3.890451 based upon complete consideration of potential range of sand and clay values */ - EXPECT_LT(SW_Site.lyr[n]->psisMatric, 34.67369); /* Value should always be less - than 34.67369 based upon complete consideration of potential range of sand and clay values */ - EXPECT_DOUBLE_EQ(SW_Site.lyr[n]->psisMatric, 27.586715750763947); /* If sand is - .33 and clay is .33, psisMatric should be 27.5867 */ - - // Test bMatric - EXPECT_GT(SW_Site.lyr[n]->bMatric, 2.8); /* Value should always be greater than - 2.8 based upon complete consideration of potential range of sand and clay values */ - EXPECT_LT(SW_Site.lyr[n]->bMatric, 18.8); /* Value should always be less - than 18.8 based upon complete consideration of potential range of sand and clay values */ - EXPECT_DOUBLE_EQ(SW_Site.lyr[n]->bMatric, 8.182); /* If sand is .33 and clay is .33, - thetasMatric should be 8.182 */ - - // Reset to previous global states - Reset_SOILWAT2_after_UnitTest(); - } - - // Test that water equation function 'water_eqn' fails - TEST(SWSiteDeathTest, WaterEquation) { - - //declare inputs - RealD fractionGravel = 0.1; - LyrIndex n = 1; - - // Test that error will be logged when b_matric is 0 - RealD sand = 10. + 1./3.; // So that bmatric will equal 0, even though this is a very irrealistic value - RealD clay = 0; - - EXPECT_DEATH_IF_SUPPORTED( - water_eqn(fractionGravel, sand, clay, n), - "@ generic.c LogError" - ); - } - - - // Test that `SW_SIT_init_run` fails on bad soil inputs - TEST(SWSiteDeathTest, SoilParameters) { - LyrIndex n1 = 0, n2 = 1, k = 2; - RealD help; - - // Check error for bad bare-soil evaporation coefficient (should be [0-1]) - help = SW_Site.lyr[n1]->evap_coeff; - SW_Site.lyr[n1]->evap_coeff = -0.5; - EXPECT_DEATH_IF_SUPPORTED(SW_SIT_init_run(), "@ generic.c LogError"); - SW_Site.lyr[n1]->evap_coeff = help; - - // Check error for bad transpiration coefficient (should be [0-1]) - SW_Site.lyr[n2]->transp_coeff[k] = 1.5; - EXPECT_DEATH_IF_SUPPORTED(SW_SIT_init_run(), "@ generic.c LogError"); - - // Reset to previous global states - Reset_SOILWAT2_after_UnitTest(); - } - - - // Test that soil transpiration regions are derived well - TEST(SWSiteTest, SoilTranspirationRegions) { - /* Notes: - - SW_Site.n_layers is base1 - - soil layer information in _TranspRgnBounds is base0 - */ - - LyrIndex - i, id, - nRegions, - prev_TranspRgnBounds[MAX_TRANSP_REGIONS] = {0}; - RealD - soildepth; - - for (i = 0; i < MAX_TRANSP_REGIONS; ++i) { - prev_TranspRgnBounds[i] = _TranspRgnBounds[i]; - } - - - // Check that "default" values do not change region bounds - nRegions = 3; - RealD regionLowerBounds1[] = {20., 40., 100.}; - derive_soilRegions(nRegions, regionLowerBounds1); - - for (i = 0; i < nRegions; ++i) { - // Quickly calculate soil depth for current region as output information - soildepth = 0.; - for (id = 0; id <= _TranspRgnBounds[i]; ++id) { - soildepth += SW_Site.lyr[id]->width; - } - - EXPECT_EQ(prev_TranspRgnBounds[i], _TranspRgnBounds[i]) << - "for transpiration region = " << i + 1 << - " at a soil depth of " << soildepth << " cm"; - } - - - // Check that setting one region for all soil layers works - nRegions = 1; - RealD regionLowerBounds2[] = {100.}; - derive_soilRegions(nRegions, regionLowerBounds2); - - for (i = 0; i < nRegions; ++i) { - EXPECT_EQ(SW_Site.n_layers - 1, _TranspRgnBounds[i]) << - "for a single transpiration region across all soil layers"; - } - - - // Check that setting one region for one soil layer works - nRegions = 1; - RealD regionLowerBounds3[] = {SW_Site.lyr[0]->width}; - derive_soilRegions(nRegions, regionLowerBounds3); - - for (i = 0; i < nRegions; ++i) { - EXPECT_EQ(0, _TranspRgnBounds[i]) << - "for a single transpiration region for the shallowest soil layer"; - } - - - // Check that setting the maximal number of regions works - nRegions = MAX_TRANSP_REGIONS; - RealD *regionLowerBounds4 = new RealD[nRegions]; - // Example: one region each for the topmost soil layers - soildepth = 0.; - for (i = 0; i < nRegions; ++i) { - soildepth += SW_Site.lyr[i]->width; - regionLowerBounds4[i] = soildepth; - } - derive_soilRegions(nRegions, regionLowerBounds4); - - for (i = 0; i < nRegions; ++i) { - EXPECT_EQ(i, _TranspRgnBounds[i]) << - "for transpiration region for the " << i + 1 << "-th soil layer"; - } - - delete[] regionLowerBounds4; - - // Reset to previous global states - Reset_SOILWAT2_after_UnitTest(); - } - -} // namespace diff --git a/test/test_SW_SoilWater.cc b/test/test_SW_SoilWater.cc deleted file mode 100644 index 2a6c50275..000000000 --- a/test/test_SW_SoilWater.cc +++ /dev/null @@ -1,264 +0,0 @@ -#include "gtest/gtest.h" -#include -#include -#include -#include -#include -#include "../generic.h" -#include "../filefuncs.h" -#include "../myMemory.h" -#include "../SW_Defines.h" -#include "../SW_Files.h" -#include "../SW_Model.h" -#include "../SW_Site.h" -#include "../SW_SoilWater.h" -#include "../SW_VegProd.h" -#include "../SW_Site.h" -#include "../SW_Flow_lib.h" -#include "sw_testhelpers.h" - - - -namespace{ - // Test the 'SW_SoilWater' function 'SW_VWCBulkRes' - TEST(SWSoilWaterTest, VWCBulkRes){ - //declare mock INPUTS - RealD fractionGravel = .1; - RealD clay = .7; - RealD sand = .2; - RealD porosity = 1; - - RealD res = SW_VWCBulkRes(fractionGravel, sand, clay, porosity); - // when clay > .6, we expect res == SW_MISSING since this isn't within reasonable - // range - EXPECT_DOUBLE_EQ(res, SW_MISSING); - - // Reset to previous global states - Reset_SOILWAT2_after_UnitTest(); - - clay = .5; - sand = .04; - - res = SW_VWCBulkRes(fractionGravel, sand, clay, porosity); - // when sand < .05, we expect res == SW_MISSING since this isn't within reasonable - // range - EXPECT_DOUBLE_EQ(res, SW_MISSING); - // Reset to previous global states - Reset_SOILWAT2_after_UnitTest(); - - sand = .4; - porosity = .4; - - res = SW_VWCBulkRes(fractionGravel, sand, clay, porosity); - // when sand == .4, clay == .5, porosity == .4 and fractionGravel ==.1, - // we expect res == .088373829599999967 - EXPECT_DOUBLE_EQ(res, .088373829599999967); - // Reset to previous global states - Reset_SOILWAT2_after_UnitTest(); - - porosity = .1; - - res = SW_VWCBulkRes(fractionGravel, sand, clay, porosity); - // when sand == .4, clay == .5, porosity == .1 and fractionGravel ==.1, - // we expect res == 0 - EXPECT_DOUBLE_EQ(res, 0); - // Reset to previous global states - Reset_SOILWAT2_after_UnitTest(); - } - - // Test the 'SW_SoilWater' function 'SW_SWC_adjust_snow' - TEST(SWSoilWaterTest, SWCdjustSnow){ - // setup mock variables - SW_Site.TminAccu2 = 0; - SW_Model.doy = 1; - SW_Site.RmeltMax = 1; - SW_Site.RmeltMin = 0; - SW_Site.lambdasnow = .1; - SW_Site.TmaxCrit = 1; - - RealD temp_min = 0, temp_max = 10, ppt = 1, rain = 1.5, snow = 1.5, - snowmelt = 1.2; - - SW_SWC_adjust_snow(temp_min, temp_max, ppt, &rain, &snow, &snowmelt); - // when average temperature >= SW_Site.TminAccu2, we expect rain == ppt - EXPECT_EQ(rain, 1); - // when average temperature >= SW_Site.TminAccu2, we expect snow == 0 - EXPECT_EQ(snow, 0); - // when temp_snow <= SW_Site.TmaxCrit, we expect snowmelt == 0 - EXPECT_EQ(snowmelt, 0); - // Reset to previous global states - Reset_SOILWAT2_after_UnitTest(); - - SW_Site.TminAccu2 = 6; - - SW_SWC_adjust_snow(temp_min, temp_max, ppt, &rain, &snow, &snowmelt); - // when average temperature < SW_Site.TminAccu2, we expect rain == 0 - EXPECT_EQ(rain, 0); - // when average temperature < SW_Site.TminAccu2, we expect snow == ppt - EXPECT_EQ(snow, 1); - // when temp_snow > SW_Site.TmaxCrit, we expect snowmelt == fmax(0, *snowpack - *snowmelt ) - EXPECT_EQ(snowmelt, 0); - // Reset to previous global states - Reset_SOILWAT2_after_UnitTest(); - - temp_max = 22; - - SW_SWC_adjust_snow(temp_min, temp_max, ppt, &rain, &snow, &snowmelt); - // when average temperature >= SW_Site.TminAccu2, we expect rain == ppt - EXPECT_EQ(rain, 1); - // when average temperature >= SW_Site.TminAccu2, we expect snow == 0 - EXPECT_EQ(snow, 0); - // when temp_snow > SW_Site.TmaxCrit, we expect snowmelt == 0 - EXPECT_EQ(snowmelt, 0); - } - - // Test the 'SW_SoilWater' function 'SW_SWCbulk2SWPmatric' - TEST(SWSoilWaterTest, SWCbulk2SWPmatric){ - // Note: function `SW_SWCbulk2SWPmatric` accesses `SW_Site.lyr[n]` - - RealD tol = 1e-2; // pedotransfer functions are not very exact - RealD fractionGravel = 0.2; - RealD swcBulk; - RealD res; - RealD help; - LyrIndex n = 1; - - // when swc is 0, we expect res == 0 - res = SW_SWCbulk2SWPmatric(fractionGravel, 0., n); - EXPECT_EQ(res, 0.0); - - // when swc is SW_MISSING, we expect res == 0 - res = SW_SWCbulk2SWPmatric(fractionGravel, SW_MISSING, n); - EXPECT_EQ(res, 0.0); - - // if swc > field capacity, then we expect res < 0.33 bar - res = SW_SWCbulk2SWPmatric(SW_Site.lyr[n]->fractionVolBulk_gravel, - SW_Site.lyr[n]->swcBulk_fieldcap + 0.1, n); - EXPECT_LT(res, 0.33 + tol); - - // if swc = field capacity, then we expect res == 0.33 bar - res = SW_SWCbulk2SWPmatric(SW_Site.lyr[n]->fractionVolBulk_gravel, - SW_Site.lyr[n]->swcBulk_fieldcap, n); - EXPECT_NEAR(res, 0.33, tol); - - // if field capacity > swc > wilting point, then - // we expect 15 bar > res > 0.33 bar - swcBulk = (SW_Site.lyr[n]->swcBulk_fieldcap + - SW_Site.lyr[n]->swcBulk_wiltpt) / 2; - res = SW_SWCbulk2SWPmatric(SW_Site.lyr[n]->fractionVolBulk_gravel, - swcBulk, n); - EXPECT_GT(res, 0.33 - tol); - EXPECT_LT(res, 15 + tol); - - // if swc = wilting point, then we expect res == 15 bar - res = SW_SWCbulk2SWPmatric(SW_Site.lyr[n]->fractionVolBulk_gravel, - SW_Site.lyr[n]->swcBulk_wiltpt, n); - EXPECT_NEAR(res, 15., tol); - - // if swc < wilting point, then we expect res > 15 bar - swcBulk = (SW_Site.lyr[n]->swcBulk_wiltpt) / 2; - res = SW_SWCbulk2SWPmatric(SW_Site.lyr[n]->fractionVolBulk_gravel, - swcBulk, n); - EXPECT_GT(res, 15. - tol); - - - // ------ meddling with internal value - // if fractionGravel == 1: no soil volume available to hold any soil water - // this would also lead to theta1 == 0 and division by zero - // this situation does normally not occur because it is - // checked during input by function `_read_layers` - // Note: this situation is tested by the death test - // `SWSWCbulk2SWPmatricDeathTest`: we cannot test it here because the - // Address Sanitizer would complain with `UndefinedBehaviorSanitizer` - // see [issue #231](https://github.com/DrylandEcology/SOILWAT2/issues/231) - // res = SW_SWCbulk2SWPmatric(1., SW_Site.lyr[n]->swcBulk_fieldcap, n); - // EXPECT_DOUBLE_EQ(res, 0.); // SWP "ought to be" infinity [bar] - - // if theta(sat, matric; Cosby et al. 1984) == 0: would be division by zero - // this situation does normally not occur because it is - // checked during input by function `water_eqn` - help = SW_Site.lyr[n]->thetasMatric; - SW_Site.lyr[n]->thetasMatric = 0.; - res = SW_SWCbulk2SWPmatric(SW_Site.lyr[n]->fractionVolBulk_gravel, 0., n); - EXPECT_DOUBLE_EQ(res, 0.); // SWP "ought to be" infinity [bar] - SW_Site.lyr[n]->thetasMatric = help; - - // if lyr->width == 0: would be division by zero - // this situation does normally not occur because it is - // checked during input by function `_read_layers` - help = SW_Site.lyr[n]->bMatric; - SW_Site.lyr[n]->width = 0.; - res = SW_SWCbulk2SWPmatric(SW_Site.lyr[n]->fractionVolBulk_gravel, 0., n); - EXPECT_DOUBLE_EQ(res, 0.); // swc < width - SW_Site.lyr[n]->width = help; - - - // No need to reset to previous global states because we didn't change any - // global states - // Reset_SOILWAT2_after_UnitTest(); - } - - TEST(SWSoilWaterDeathTest, SWCbulk2SWPmatricDeathTest) { - LyrIndex n = 1; - RealD help; - - // we expect fatal errors and write to log under two situations: - - // if swc < 0: water content can physically not be negative - EXPECT_DEATH_IF_SUPPORTED( - SW_SWCbulk2SWPmatric(SW_Site.lyr[n]->fractionVolBulk_gravel, -1., n), - "@ generic.c LogError" - ); - - // if theta1 == 0 (i.e., gravel == 1) && lyr->bMatric == 0: - // would be division by NaN - // note: this case is in normally prevented due to checks of inputs by - // function `water_eqn` for `bMatric` and function `_read_layers` for - // `gravelFraction` - help = SW_Site.lyr[n]->bMatric; - SW_Site.lyr[n]->bMatric = 0.; - EXPECT_DEATH_IF_SUPPORTED( - SW_SWCbulk2SWPmatric(1., SW_Site.lyr[n]->swcBulk_fieldcap, n), - "@ generic.c LogError" - ); - SW_Site.lyr[n]->bMatric = help; - - // Reset to previous global states - Reset_SOILWAT2_after_UnitTest(); - } - - // Test the 'SW_SoilWater' function 'SW_SWPmatric2VWCBulk' - TEST(SWSoilWaterTest, SWPmatric2VWCBulk){ - // set up mock variables - RealD fractionGravel = .1, swpMatric = 15.0, p = 0.11656662532982573, - psisMatric = 18.608013, binverseMatric = 0.188608, thetaMatric = 41.37; - RealD tExpect, t, actualExpectDiff; - int i; - LyrIndex n = 0; - SW_Site.lyr[n]->thetasMatric = thetaMatric; - SW_Site.lyr[n]->psisMatric = psisMatric; - SW_Site.lyr[n]->bMatric = binverseMatric; - - // set gravel fractions on the interval [.0, .8], step .05 - for (i = 0; i <= 16; i++){ - fractionGravel = i / 20.; - tExpect = p * (1 - fractionGravel); - - t = SW_SWPmatric2VWCBulk(fractionGravel, swpMatric, n); - actualExpectDiff = fabs(t - tExpect); - - // when fractionGravel is between [.0, .8], we expect t = p * (1 - fractionGravel) - EXPECT_LT(actualExpectDiff, tol6); - - } - Reset_SOILWAT2_after_UnitTest(); - - fractionGravel = 1; - - t = SW_SWPmatric2VWCBulk(fractionGravel, swpMatric, n); - // when fractionGravel is 1, we expect t == 0 - EXPECT_EQ(t, 0); - // Reset to previous global states - Reset_SOILWAT2_after_UnitTest(); - } -} diff --git a/test/test_SW_VegProd.cc b/test/test_SW_VegProd.cc deleted file mode 100644 index 1c04cd203..000000000 --- a/test/test_SW_VegProd.cc +++ /dev/null @@ -1,162 +0,0 @@ -#include "gtest/gtest.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../generic.h" -#include "../myMemory.h" -#include "../filefuncs.h" -#include "../rands.h" -#include "../Times.h" -#include "../SW_Defines.h" -#include "../SW_Times.h" -#include "../SW_Files.h" -#include "../SW_Carbon.h" -#include "../SW_Site.h" -#include "../SW_VegProd.h" -#include "../SW_VegEstab.h" -#include "../SW_Model.h" -#include "../SW_SoilWater.h" -#include "../SW_Weather.h" -#include "../SW_Markov.h" -#include "../SW_Sky.h" - -#include "sw_testhelpers.h" - - - - -static void assert_decreasing_SWPcrit(void); -static void assert_decreasing_SWPcrit(void) -{ - int rank, vegtype; - - for (rank = 0; rank < NVEGTYPES - 1; rank++) - { - vegtype = SW_VegProd.rank_SWPcrits[rank]; - - /* - swprintf("Rank=%d is vegtype=%d with SWPcrit=%f\n", - rank, vegtype, - SW_VegProd.critSoilWater[vegtype]); - */ - - // Check that SWPcrit of `vegtype` is larger or equal to - // SWPcrit of the vegetation type with the next larger rank - ASSERT_GE( - SW_VegProd.critSoilWater[vegtype], - SW_VegProd.critSoilWater[SW_VegProd.rank_SWPcrits[rank + 1]]); - } -} - - -namespace { - SW_VEGPROD *v = &SW_VegProd; - int k; - - // Test the SW_VEGPROD constructor 'SW_VPD_construct' - TEST(VegTest, Constructor) { - SW_VPD_construct(); - SW_VPD_init_run(); - - ForEachVegType(k) { - EXPECT_DOUBLE_EQ(1., v->veg[k].co2_multipliers[BIO_INDEX][0]); - EXPECT_DOUBLE_EQ(1., v->veg[k].co2_multipliers[BIO_INDEX][MAX_NYEAR - 1]); - - EXPECT_DOUBLE_EQ(1., v->veg[k].co2_multipliers[WUE_INDEX][0]); - EXPECT_DOUBLE_EQ(1., v->veg[k].co2_multipliers[WUE_INDEX][MAX_NYEAR - 1]); - } - - // Reset to previous global state - Reset_SOILWAT2_after_UnitTest(); - } - - - // Test the application of the biomass CO2-effect - TEST(VegTest, BiomassCO2effect) { - int i; - double x; - double biom1[12], biom2[12]; - - for (i = 0; i < 12; i++) { - biom1[i] = i + 1.; - } - - // One example - x = v->veg[SW_GRASS].co2_multipliers[BIO_INDEX][SW_Model.startyr + SW_Model.addtl_yr]; - apply_biomassCO2effect(biom2, biom1, x); - - for (i = 0; i < 12; i++) { - EXPECT_DOUBLE_EQ(biom2[i], biom1[i] * x); - } - - // Reset to previous global state - Reset_SOILWAT2_after_UnitTest(); - } - - - // Test summing values across vegetation types - TEST(VegTest, Summing) { - double x0[NVEGTYPES] = {0.}; - double x1[NVEGTYPES] = {0.25, 0.25, 0.25, 0.25}; - - EXPECT_DOUBLE_EQ(sum_across_vegtypes(x0), 0.); - EXPECT_DOUBLE_EQ(sum_across_vegtypes(x1), 1.); - } - - - // Check `get_critical_rank` - TEST(VegTest, rank) { - int k; - // Check `get_critical_rank` for normal inputs, e.g., -2.0, -2.0, -3.5, -3.9 - get_critical_rank(); - assert_decreasing_SWPcrit(); - - - // Check `get_critical_rank` for constant values - ForEachVegType(k) - { - SW_VegProd.critSoilWater[k] = 0.; - } - - get_critical_rank(); - assert_decreasing_SWPcrit(); - - - // Check `get_critical_rank` for increasing values - ForEachVegType(k) - { - SW_VegProd.critSoilWater[k] = k; - } - - get_critical_rank(); - assert_decreasing_SWPcrit(); - - - // Check `get_critical_rank` for decreasing values - ForEachVegType(k) - { - SW_VegProd.critSoilWater[k] = NVEGTYPES - k; - } - - get_critical_rank(); - assert_decreasing_SWPcrit(); - - - // Reset to previous global state - Reset_SOILWAT2_after_UnitTest(); - } - -} // namespace diff --git a/test/test_WaterBalance.cc b/test/test_WaterBalance.cc deleted file mode 100644 index 2b4533818..000000000 --- a/test/test_WaterBalance.cc +++ /dev/null @@ -1,192 +0,0 @@ -#include "gtest/gtest.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../generic.h" -#include "../myMemory.h" -#include "../filefuncs.h" -#include "../rands.h" -#include "../Times.h" -#include "../SW_Defines.h" -#include "../SW_Times.h" -#include "../SW_Files.h" -#include "../SW_Carbon.h" -#include "../SW_Site.h" -#include "../SW_VegProd.h" -#include "../SW_VegEstab.h" -#include "../SW_Model.h" -#include "../SW_SoilWater.h" -#include "../SW_Weather.h" -#include "../SW_Markov.h" -#include "../SW_Sky.h" -#include "../SW_Control.h" - -#include "sw_testhelpers.h" - - - -namespace { - /* Test daily water balance and water cycling: - i) Call function 'SW_CTL_main' which calls 'SW_CTL_run_current_year' for each year - which calls 'SW_SWC_water_flow' for each day - ii) Summarize checks added to debugging code of 'SW_SWC_water_flow' (which is - compiled if flag 'SWDEBUG' is defined) - */ - TEST(WaterBalanceTest, Example1) { // default run == 'testing' example1 - int i; - - // Run the simulation - SW_CTL_main(); - - // Collect and output from daily checks - for (i = 0; i < N_WBCHECKS; i++) { - EXPECT_EQ(0, SW_Soilwat.wbError[i]) << - "Water balance error in test " << - i << ": " << (char*)SW_Soilwat.wbErrorNames[i]; - } - - // Reset to previous global state - Reset_SOILWAT2_after_UnitTest(); - } - - - TEST(WaterBalanceTest, WithSoilTemperature) { - int i; - - // Turn on soil temperature simulations - SW_Site.use_soil_temp = swTRUE; - - // Run the simulation - SW_CTL_main(); - - // Collect and output from daily checks - for (i = 0; i < N_WBCHECKS; i++) { - EXPECT_EQ(0, SW_Soilwat.wbError[i]) << - "Water balance error in test " << - i << ": " << (char*)SW_Soilwat.wbErrorNames[i]; - } - - // Reset to previous global state - Reset_SOILWAT2_after_UnitTest(); - } - - - TEST(WaterBalanceTest, WithPondedWaterRunonRunoff) { - int i; - - // Turn on impermeability of first soil layer, runon, and runoff - SW_Site.lyr[0]->impermeability = 0.95; - SW_Site.percentRunoff = 0.5; - SW_Site.percentRunon = 1.25; - - // Run the simulation - SW_CTL_main(); - - // Collect and output from daily checks - for (i = 0; i < N_WBCHECKS; i++) { - EXPECT_EQ(0, SW_Soilwat.wbError[i]) << - "Water balance error in test " << - i << ": " << (char*)SW_Soilwat.wbErrorNames[i]; - } - - // Reset to previous global state - Reset_SOILWAT2_after_UnitTest(); - } - - - - TEST(WaterBalanceTest, WithWeatherGeneratorOnly) { - int i; - - // Turn on Markov weather generator (and turn off use of historical weather) - SW_Soilwat.hist_use = swFALSE; - SW_Weather.yr.first = SW_Model.endyr + 1; - SW_Weather.use_weathergenerator = swTRUE; - - // Read Markov weather generator input files (they are not normally read) - SW_MKV_setup(); - - // Run the simulation - SW_CTL_main(); - - // Collect and output from daily checks - for (i = 0; i < N_WBCHECKS; i++) { - EXPECT_EQ(0, SW_Soilwat.wbError[i]) << - "Water balance error in test " << - i << ": " << (char*)SW_Soilwat.wbErrorNames[i]; - } - - // Reset to previous global state - Reset_SOILWAT2_after_UnitTest(); - } - - - TEST(WaterBalanceTest, WithWeatherGeneratorForSomeMissingValues) { - int i; - - // Turn on Markov weather generator - SW_Weather.use_weathergenerator = swTRUE; - - // Read Markov weather generator input files (they are not normally read) - SW_MKV_setup(); - - // Point to partial weather data - strcpy(SW_Weather.name_prefix, "Input/data_weather_missing/weath"); - - // Run the simulation - SW_CTL_main(); - - // Collect and output from daily checks - for (i = 0; i < N_WBCHECKS; i++) { - EXPECT_EQ(0, SW_Soilwat.wbError[i]) << - "Water balance error in test " << - i << ": " << (char*)SW_Soilwat.wbErrorNames[i]; - } - - // Reset to previous global state - Reset_SOILWAT2_after_UnitTest(); - } - - - TEST(WaterBalanceTest, WithHighGravelVolume) { - int i; - LyrIndex s; - - // Set high gravel volume in all soil layers - ForEachSoilLayer(s) - { - SW_Site.lyr[s]->fractionVolBulk_gravel = 0.99; - } - - // Re-calculate soils - SW_SIT_init_run(); - - // Run the simulation - SW_CTL_main(); - - // Collect and output from daily checks - for (i = 0; i < N_WBCHECKS; i++) { - EXPECT_EQ(0, SW_Soilwat.wbError[i]) << - "Water balance error in test " << - i << ": " << (char*)SW_Soilwat.wbErrorNames[i]; - } - - // Reset to previous global state - Reset_SOILWAT2_after_UnitTest(); - } - - -} // namespace diff --git a/test/test_generic.cc b/test/test_generic.cc deleted file mode 100644 index c89a40ed7..000000000 --- a/test/test_generic.cc +++ /dev/null @@ -1,62 +0,0 @@ -#include "gtest/gtest.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../generic.h" -#include "../SW_Defines.h" - - -namespace { - const unsigned int N = 9; - unsigned int k; - double - x[N] = {-4., -3., -2., -1., 0., 1., 2., 3., 4.}, - // m calculated in R with `for (k in seq_along(x)) print(mean(x[1:k]))` - m[N] = {-4, -3.5, -3, -2.5, -2, -1.5, -1, -0.5, 0}, - // sd calculated in R with `for (k in seq_along(x)) print(sd(x[1:k]))` - sd[N] = {SW_MISSING, 0.7071068, 1., 1.290994, 1.581139, 1.870829, 2.160247, - 2.44949, 2.738613}; - float tol = 1e-6; - - TEST(RunningAggregatorsTest, RunningMean) { - double m_at_k = 0.; - - for (k = 0; k < N; k++) - { - m_at_k = get_running_mean(k + 1, m_at_k, x[k]); - EXPECT_DOUBLE_EQ(m_at_k, m[k]); - } - } - - TEST(RunningAggregatorsTest, RunningSD) { - double ss, sd_at_k; - - for (k = 0; k < N; k++) - { - if (k == 0) - { - ss = get_running_sqr(0., m[k], x[k]); - - } else { - ss += get_running_sqr(m[k - 1], m[k], x[k]); - - sd_at_k = final_running_sd(k + 1, ss); // k is base0 - EXPECT_NEAR(sd_at_k, sd[k], tol); - } - } - } - -} // namespace diff --git a/testing/Input/weathsetup.in b/testing/Input/weathsetup.in deleted file mode 100755 index f663d2161..000000000 --- a/testing/Input/weathsetup.in +++ /dev/null @@ -1,42 +0,0 @@ -#------ Input file for weather-related parameters and weather generator settings - -#--- Activate/deactivate simulation of snow-related processes -1 # 1 = do/don't simulate snow processes -0.0 # snow drift/blowing snow (% per snow event): > 0 is adding snow, < 0 is removing snow -0.0 # runoff/runon of snowmelt water (% per snowmelt event): > 0 is runoff, < 0 is runon - - -#--- Activate/deactivate weather generator -0 # 0 = use historical data only - # 1 = use weather generator for (partially) missing weather inputs - # 2 = use weather generator for all weather (don't check weather inputs) - -7 # Seed random number generator for weather generator (only used if SOILWAT2) - # (seed with 0 to use current time) - -#--- Historical daily weather inputs --1 # first year to begin historical weather - # if -1, then use first year of simulation (see `years.in`) - - -#--- Monthly scaling parameters: -# Month 1 = January, Month 2 = February, etc. -# PPT = multiplicative for daily PPT; max(0, scale * ppt) -# MaxT = additive for daily max temperature [C]; scale + maxtemp -# MinT = additive for daily min temperature [C]; scale + mintemp -# SkyCover = additive for mean monthly sky cover [%]; min(100, max(0, scale + sky cover)) -# Wind = multiplicative for mean monthly wind speed; max(0, scale * wind speed) -# rH = additive for mean monthly relative humidity [%]; min(100, max(0, scale + rel. Humidity)) -#Mon PPT MaxT MinT SkyCover Wind rH -1 1.000 0.00 0.00 0.0 1.0 0.0 -2 1.000 0.00 0.00 0.0 1.0 0.0 -3 1.000 0.00 0.00 0.0 1.0 0.0 -4 1.000 0.00 0.00 0.0 1.0 0.0 -5 1.000 0.00 0.00 0.0 1.0 0.0 -6 1.000 0.00 0.00 0.0 1.0 0.0 -7 1.000 0.00 0.00 0.0 1.0 0.0 -8 1.000 0.00 0.00 0.0 1.0 0.0 -9 1.000 0.00 0.00 0.0 1.0 0.0 -10 1.000 0.00 0.00 0.0 1.0 0.0 -11 1.000 0.00 0.00 0.0 1.0 0.0 -12 1.000 0.00 0.00 0.0 1.0 0.0 diff --git a/testing/Input/carbon.in b/tests/example/Input/carbon.in similarity index 100% rename from testing/Input/carbon.in rename to tests/example/Input/carbon.in diff --git a/testing/Input/climate.in b/tests/example/Input/climate.in similarity index 100% rename from testing/Input/climate.in rename to tests/example/Input/climate.in diff --git a/testing/Input/data_weather/weath.1980 b/tests/example/Input/data_weather/weath.1980 similarity index 100% rename from testing/Input/data_weather/weath.1980 rename to tests/example/Input/data_weather/weath.1980 diff --git a/testing/Input/data_weather/weath.1981 b/tests/example/Input/data_weather/weath.1981 similarity index 100% rename from testing/Input/data_weather/weath.1981 rename to tests/example/Input/data_weather/weath.1981 diff --git a/testing/Input/data_weather/weath.1982 b/tests/example/Input/data_weather/weath.1982 similarity index 100% rename from testing/Input/data_weather/weath.1982 rename to tests/example/Input/data_weather/weath.1982 diff --git a/testing/Input/data_weather/weath.1983 b/tests/example/Input/data_weather/weath.1983 similarity index 100% rename from testing/Input/data_weather/weath.1983 rename to tests/example/Input/data_weather/weath.1983 diff --git a/testing/Input/data_weather/weath.1984 b/tests/example/Input/data_weather/weath.1984 similarity index 100% rename from testing/Input/data_weather/weath.1984 rename to tests/example/Input/data_weather/weath.1984 diff --git a/testing/Input/data_weather/weath.1985 b/tests/example/Input/data_weather/weath.1985 similarity index 100% rename from testing/Input/data_weather/weath.1985 rename to tests/example/Input/data_weather/weath.1985 diff --git a/testing/Input/data_weather/weath.1986 b/tests/example/Input/data_weather/weath.1986 similarity index 100% rename from testing/Input/data_weather/weath.1986 rename to tests/example/Input/data_weather/weath.1986 diff --git a/testing/Input/data_weather/weath.1987 b/tests/example/Input/data_weather/weath.1987 similarity index 100% rename from testing/Input/data_weather/weath.1987 rename to tests/example/Input/data_weather/weath.1987 diff --git a/testing/Input/data_weather/weath.1988 b/tests/example/Input/data_weather/weath.1988 similarity index 100% rename from testing/Input/data_weather/weath.1988 rename to tests/example/Input/data_weather/weath.1988 diff --git a/testing/Input/data_weather/weath.1989 b/tests/example/Input/data_weather/weath.1989 similarity index 100% rename from testing/Input/data_weather/weath.1989 rename to tests/example/Input/data_weather/weath.1989 diff --git a/testing/Input/data_weather/weath.1990 b/tests/example/Input/data_weather/weath.1990 similarity index 100% rename from testing/Input/data_weather/weath.1990 rename to tests/example/Input/data_weather/weath.1990 diff --git a/testing/Input/data_weather/weath.1991 b/tests/example/Input/data_weather/weath.1991 similarity index 100% rename from testing/Input/data_weather/weath.1991 rename to tests/example/Input/data_weather/weath.1991 diff --git a/testing/Input/data_weather/weath.1992 b/tests/example/Input/data_weather/weath.1992 similarity index 100% rename from testing/Input/data_weather/weath.1992 rename to tests/example/Input/data_weather/weath.1992 diff --git a/testing/Input/data_weather/weath.1993 b/tests/example/Input/data_weather/weath.1993 similarity index 100% rename from testing/Input/data_weather/weath.1993 rename to tests/example/Input/data_weather/weath.1993 diff --git a/testing/Input/data_weather/weath.1994 b/tests/example/Input/data_weather/weath.1994 similarity index 100% rename from testing/Input/data_weather/weath.1994 rename to tests/example/Input/data_weather/weath.1994 diff --git a/testing/Input/data_weather/weath.1995 b/tests/example/Input/data_weather/weath.1995 similarity index 100% rename from testing/Input/data_weather/weath.1995 rename to tests/example/Input/data_weather/weath.1995 diff --git a/testing/Input/data_weather/weath.1996 b/tests/example/Input/data_weather/weath.1996 similarity index 100% rename from testing/Input/data_weather/weath.1996 rename to tests/example/Input/data_weather/weath.1996 diff --git a/testing/Input/data_weather/weath.1997 b/tests/example/Input/data_weather/weath.1997 similarity index 100% rename from testing/Input/data_weather/weath.1997 rename to tests/example/Input/data_weather/weath.1997 diff --git a/testing/Input/data_weather/weath.1998 b/tests/example/Input/data_weather/weath.1998 similarity index 100% rename from testing/Input/data_weather/weath.1998 rename to tests/example/Input/data_weather/weath.1998 diff --git a/testing/Input/data_weather/weath.1999 b/tests/example/Input/data_weather/weath.1999 similarity index 100% rename from testing/Input/data_weather/weath.1999 rename to tests/example/Input/data_weather/weath.1999 diff --git a/testing/Input/data_weather/weath.2000 b/tests/example/Input/data_weather/weath.2000 similarity index 100% rename from testing/Input/data_weather/weath.2000 rename to tests/example/Input/data_weather/weath.2000 diff --git a/testing/Input/data_weather/weath.2001 b/tests/example/Input/data_weather/weath.2001 similarity index 100% rename from testing/Input/data_weather/weath.2001 rename to tests/example/Input/data_weather/weath.2001 diff --git a/testing/Input/data_weather/weath.2002 b/tests/example/Input/data_weather/weath.2002 similarity index 100% rename from testing/Input/data_weather/weath.2002 rename to tests/example/Input/data_weather/weath.2002 diff --git a/testing/Input/data_weather/weath.2003 b/tests/example/Input/data_weather/weath.2003 similarity index 100% rename from testing/Input/data_weather/weath.2003 rename to tests/example/Input/data_weather/weath.2003 diff --git a/testing/Input/data_weather/weath.2004 b/tests/example/Input/data_weather/weath.2004 similarity index 100% rename from testing/Input/data_weather/weath.2004 rename to tests/example/Input/data_weather/weath.2004 diff --git a/testing/Input/data_weather/weath.2005 b/tests/example/Input/data_weather/weath.2005 similarity index 100% rename from testing/Input/data_weather/weath.2005 rename to tests/example/Input/data_weather/weath.2005 diff --git a/testing/Input/data_weather/weath.2006 b/tests/example/Input/data_weather/weath.2006 similarity index 100% rename from testing/Input/data_weather/weath.2006 rename to tests/example/Input/data_weather/weath.2006 diff --git a/testing/Input/data_weather/weath.2007 b/tests/example/Input/data_weather/weath.2007 similarity index 100% rename from testing/Input/data_weather/weath.2007 rename to tests/example/Input/data_weather/weath.2007 diff --git a/testing/Input/data_weather/weath.2008 b/tests/example/Input/data_weather/weath.2008 similarity index 100% rename from testing/Input/data_weather/weath.2008 rename to tests/example/Input/data_weather/weath.2008 diff --git a/testing/Input/data_weather/weath.2009 b/tests/example/Input/data_weather/weath.2009 similarity index 100% rename from testing/Input/data_weather/weath.2009 rename to tests/example/Input/data_weather/weath.2009 diff --git a/testing/Input/data_weather/weath.2010 b/tests/example/Input/data_weather/weath.2010 similarity index 100% rename from testing/Input/data_weather/weath.2010 rename to tests/example/Input/data_weather/weath.2010 diff --git a/tests/example/Input/data_weather_daymet/weath.1980 b/tests/example/Input/data_weather_daymet/weath.1980 new file mode 100644 index 000000000..ff9cfa491 --- /dev/null +++ b/tests/example/Input/data_weather_daymet/weath.1980 @@ -0,0 +1,368 @@ +# weather for site daymet example at -105.58 / 39.59 year = 1980 +# DOY, Tmax_C, Tmin_C, PPT_cm, vp_kPa, rsds_WPERm2 +1 -0.37 -9.20 0.28 0.30 160.21 +2 -4.78 -14.96 0.35 0.19 179.14 +3 -3.79 -18.36 0.00 0.13 283.01 +4 -1.30 -11.12 0.00 0.25 230.08 +5 3.08 -8.41 0.00 0.27 253.18 +6 -5.61 -10.70 0.00 0.27 137.65 +7 -8.23 -17.36 0.67 0.16 171.22 +8 -3.68 -17.02 0.31 0.16 215.17 +9 1.01 -12.80 0.36 0.23 219.36 +10 -3.06 -9.67 0.57 0.29 135.10 +11 -1.79 -19.50 0.00 0.13 317.62 +12 -0.30 -13.73 0.54 0.21 222.45 +13 1.26 -6.50 0.00 0.38 205.60 +14 2.69 -5.21 0.00 0.41 208.96 +15 -0.81 -9.70 0.00 0.29 231.93 +16 -1.36 -14.57 0.00 0.20 296.91 +17 -1.22 -13.83 0.00 0.21 292.40 +18 -7.26 -15.12 0.70 0.19 166.46 +19 -5.09 -17.63 0.00 0.15 298.45 +20 0.53 -14.62 0.00 0.20 324.21 +21 -2.89 -14.40 0.00 0.20 288.61 +22 -4.01 -20.04 0.00 0.12 337.75 +23 0.15 -17.90 0.00 0.15 350.72 +24 3.75 -11.23 0.00 0.25 332.46 +25 -3.21 -10.99 0.49 0.26 173.65 +26 -10.02 -23.09 0.39 0.10 248.51 +27 -5.48 -17.48 0.12 0.15 314.07 +28 -6.69 -18.95 0.27 0.14 244.74 +29 -4.01 -16.48 0.29 0.17 248.87 +30 -5.41 -15.51 0.54 0.18 222.64 +31 1.22 -18.80 0.00 0.14 387.42 +32 -0.07 -15.41 0.00 0.18 361.83 +33 -0.65 -9.10 0.00 0.31 257.91 +34 4.30 -7.15 0.00 0.36 317.39 +35 0.21 -6.28 0.00 0.38 211.96 +36 1.02 -10.30 0.00 0.28 322.83 +37 4.99 -9.69 0.00 0.28 368.80 +38 -4.79 -11.23 1.09 0.26 164.79 +39 -2.02 -23.78 0.00 0.09 426.90 +40 0.21 -21.39 0.00 0.11 427.85 +41 0.94 -16.41 0.00 0.17 407.83 +42 -1.09 -17.11 0.00 0.16 399.48 +43 -0.76 -15.28 0.00 0.19 382.90 +44 1.31 -12.04 0.00 0.24 366.80 +45 4.35 -11.54 0.00 0.23 399.98 +46 0.11 -7.11 0.06 0.36 236.38 +47 0.66 -9.39 0.00 0.30 313.71 +48 0.53 -12.77 0.00 0.22 378.66 +49 1.24 -8.00 0.38 0.33 225.90 +50 1.85 -7.81 0.00 0.34 314.88 +51 -0.41 -7.88 0.00 0.34 260.39 +52 -0.49 -6.47 0.00 0.38 220.52 +53 -2.40 -11.30 0.00 0.26 319.19 +54 -3.84 -11.40 0.29 0.26 217.13 +55 -4.72 -13.35 0.00 0.22 322.80 +56 3.77 -13.94 0.00 0.18 479.35 +57 4.28 -9.70 0.00 0.26 439.03 +58 8.00 -10.93 0.00 0.21 492.09 +59 11.31 -5.17 0.00 0.32 470.39 +60 1.20 -7.16 0.28 0.36 238.18 +61 1.49 -17.55 0.00 0.13 509.16 +62 4.80 -12.69 0.00 0.19 498.46 +63 3.96 -5.71 0.00 0.38 359.98 +64 -1.76 -9.96 0.19 0.29 324.01 +65 0.85 -10.32 0.12 0.26 407.37 +66 1.16 -8.36 0.58 0.33 277.35 +67 -2.02 -10.27 0.88 0.28 255.68 +68 -1.89 -14.65 0.00 0.20 461.08 +69 -4.09 -13.99 0.00 0.21 402.17 +70 2.61 -8.83 0.00 0.29 450.49 +71 3.09 -11.44 0.00 0.22 517.76 +72 -4.28 -11.58 0.00 0.25 338.37 +73 0.36 -15.45 0.00 0.16 548.45 +74 5.66 -9.32 0.00 0.24 537.70 +75 3.95 -4.68 0.00 0.41 393.45 +76 -5.61 -13.72 0.97 0.21 289.64 +77 2.84 -21.70 0.00 0.09 618.91 +78 5.47 -11.21 0.00 0.21 574.09 +79 1.56 -8.19 0.00 0.32 431.33 +80 1.43 -13.38 0.00 0.19 554.01 +81 4.96 -10.45 0.00 0.23 560.44 +82 -3.43 -10.32 0.35 0.28 244.31 +83 -2.51 -14.53 0.00 0.20 499.92 +84 0.03 -15.70 0.00 0.16 573.30 +85 -0.59 -11.09 0.12 0.26 453.12 +86 -4.00 -15.90 0.00 0.18 503.90 +87 -2.18 -13.98 0.63 0.21 380.74 +88 -6.52 -12.29 0.80 0.24 222.23 +89 -3.92 -15.95 0.00 0.18 537.04 +90 1.30 -14.05 0.47 0.21 454.23 +91 -5.25 -9.90 0.76 0.29 191.83 +92 -6.20 -17.85 0.74 0.15 416.37 +93 -4.78 -14.89 0.44 0.19 382.41 +94 -1.89 -17.05 0.00 0.16 633.52 +95 4.71 -11.30 0.00 0.23 643.26 +96 5.55 -9.18 0.00 0.27 619.53 +97 1.75 -10.11 0.00 0.27 553.30 +98 -2.39 -13.20 0.32 0.22 395.94 +99 -0.65 -15.84 0.00 0.17 638.42 +100 4.96 -9.01 0.00 0.27 610.26 +101 0.59 -7.48 0.56 0.35 318.67 +102 -1.99 -8.69 1.15 0.32 275.89 +103 -3.12 -20.97 0.45 0.11 526.79 +104 -1.13 -20.03 0.00 0.12 715.07 +105 5.83 -11.08 0.00 0.22 684.63 +106 7.01 -7.52 0.00 0.29 635.65 +107 3.67 -7.28 0.00 0.33 548.54 +108 9.57 -10.53 0.00 0.21 730.03 +109 11.41 -5.58 0.00 0.30 689.78 +110 12.16 -4.29 0.00 0.32 680.45 +111 12.30 -2.62 0.00 0.38 651.22 +112 12.71 -1.08 0.00 0.43 618.53 +113 10.48 -1.81 0.00 0.43 579.14 +114 4.56 -1.05 2.42 0.57 230.59 +115 -2.92 -5.64 2.62 0.40 127.23 +116 0.25 -13.05 1.33 0.22 479.09 +117 1.56 -10.67 0.70 0.27 458.27 +118 6.14 -9.27 0.00 0.29 685.49 +119 9.08 -6.38 0.00 0.34 682.40 +120 9.69 -2.82 0.00 0.46 606.67 +121 1.71 -3.16 2.01 0.48 207.60 +122 1.97 -7.38 2.43 0.35 381.13 +123 5.11 -6.91 2.10 0.36 462.03 +124 8.67 -4.91 1.62 0.42 496.72 +125 8.89 -3.47 0.00 0.47 630.90 +126 6.97 -1.79 0.98 0.54 370.57 +127 8.40 -2.26 0.29 0.52 431.89 +128 7.01 -1.65 1.00 0.54 370.11 +129 4.44 -1.94 0.79 0.53 290.12 +130 7.68 -2.75 0.00 0.50 585.74 +131 8.40 -2.36 0.00 0.51 595.07 +132 4.42 -2.41 0.36 0.51 309.53 +133 1.01 -8.21 0.44 0.33 411.83 +134 5.89 -8.34 0.20 0.33 726.56 +135 4.51 -6.03 0.63 0.39 463.59 +136 0.67 -3.80 1.02 0.46 229.75 +137 0.97 -5.05 0.66 0.42 306.47 +138 -0.21 -4.98 0.81 0.42 258.21 +139 6.67 -8.87 0.00 0.31 788.15 +140 8.75 -4.38 0.00 0.44 738.41 +141 10.52 1.30 0.00 0.67 601.70 +142 15.82 -0.97 0.00 0.55 792.28 +143 16.81 1.87 0.40 0.70 563.92 +144 13.71 0.56 0.00 0.64 719.11 +145 11.87 -1.48 0.00 0.55 718.78 +146 8.69 -8.14 0.00 0.33 791.58 +147 9.18 -6.26 0.00 0.38 766.24 +148 12.47 -2.00 0.00 0.53 740.22 +149 12.66 -1.28 0.00 0.56 728.08 +150 10.74 -2.68 0.00 0.50 717.71 +151 12.57 -2.84 0.00 0.49 751.03 +152 14.94 -2.30 0.00 0.50 774.35 +153 11.77 -3.90 0.00 0.46 749.83 +154 12.61 -1.87 0.00 0.53 720.47 +155 16.23 1.22 0.00 0.65 721.77 +156 16.56 0.53 0.00 0.62 736.33 +157 16.32 3.33 0.00 0.78 655.81 +158 15.56 1.96 0.00 0.70 670.49 +159 12.75 -2.12 0.00 0.52 702.55 +160 15.04 1.20 0.00 0.66 666.23 +161 14.49 -0.25 0.00 0.59 687.65 +162 15.13 1.71 0.00 0.69 642.20 +163 18.71 2.91 0.00 0.72 690.37 +164 18.03 2.31 0.00 0.70 687.31 +165 18.27 3.64 0.00 0.78 654.04 +166 17.41 1.27 0.00 0.65 684.75 +167 15.04 -1.97 0.00 0.51 700.41 +168 14.68 -0.67 0.00 0.58 652.98 +169 18.07 0.97 0.00 0.63 685.36 +170 18.59 3.88 0.00 0.79 621.57 +171 16.75 3.51 0.00 0.79 577.21 +172 17.92 1.90 0.00 0.68 651.99 +173 16.49 2.94 0.00 0.75 586.24 +174 18.57 2.53 0.00 0.71 646.51 +175 22.55 3.98 0.00 0.76 685.87 +176 22.38 7.78 0.00 1.03 591.24 +177 22.05 5.77 0.00 0.88 633.79 +178 21.73 6.18 0.00 0.91 613.69 +179 19.12 7.58 0.00 1.04 496.67 +180 18.93 3.56 0.00 0.76 611.46 +181 21.24 5.36 0.00 0.85 616.26 +182 20.76 8.44 0.00 1.10 514.91 +183 18.36 6.05 0.87 0.94 389.32 +184 16.97 5.75 0.77 0.92 364.40 +185 18.07 3.13 0.00 0.74 598.98 +186 21.19 4.94 0.00 0.82 621.13 +187 21.81 5.27 0.00 0.84 621.26 +188 22.81 5.65 0.00 0.85 627.05 +189 20.40 6.51 0.39 0.97 410.21 +190 18.40 4.23 0.63 0.83 418.25 +191 21.28 5.28 0.00 0.86 597.42 +192 21.67 7.12 0.47 1.01 414.44 +193 17.90 6.66 1.08 0.98 348.09 +194 19.84 4.96 0.61 0.87 423.39 +195 17.63 4.56 0.73 0.85 390.26 +196 18.77 2.69 0.00 0.74 595.79 +197 19.79 4.22 0.00 0.82 580.58 +198 22.41 4.27 0.00 0.80 622.41 +199 20.86 6.23 0.00 0.95 549.69 +200 19.98 5.01 0.00 0.87 557.98 +201 20.82 7.28 0.00 1.02 515.35 +202 19.35 5.33 0.00 0.89 531.24 +203 20.36 4.76 0.00 0.86 563.05 +204 22.31 5.71 0.00 0.91 576.17 +205 19.82 5.42 0.24 0.90 397.92 +206 19.05 5.71 0.00 0.92 457.72 +207 18.42 2.39 0.62 0.73 385.27 +208 17.76 3.04 0.00 0.75 491.58 +209 20.06 4.93 0.00 0.84 493.05 +210 22.13 5.79 0.00 0.88 511.41 +211 22.43 7.31 0.00 0.99 486.22 +212 19.63 8.46 0.23 1.11 293.89 +213 21.18 5.79 0.27 0.92 366.67 +214 20.12 6.80 0.00 0.88 448.95 +215 20.00 5.83 0.00 0.77 470.62 +216 20.35 6.15 0.00 0.78 471.38 +217 19.01 2.83 0.00 0.58 514.60 +218 21.17 5.84 0.00 0.71 495.04 +219 23.51 7.63 0.00 0.74 500.71 +220 23.74 7.14 0.00 0.67 512.47 +221 20.62 6.64 0.87 0.85 343.69 +222 22.82 6.98 0.97 0.86 367.61 +223 22.14 7.01 0.00 0.77 479.91 +224 22.33 7.52 0.00 0.79 471.72 +225 22.32 6.87 0.00 0.74 482.80 +226 19.25 6.08 0.00 0.74 438.56 +227 17.68 6.87 1.07 0.93 281.40 +228 15.28 7.61 0.95 1.04 212.49 +229 14.62 4.29 0.00 0.72 376.02 +230 16.61 1.72 0.00 0.51 487.44 +231 17.03 3.82 0.00 0.62 449.42 +232 18.37 5.72 0.00 0.72 432.68 +233 14.77 0.92 0.00 0.51 465.47 +234 19.63 -0.83 0.00 0.37 556.54 +235 21.32 4.24 0.00 0.56 510.40 +236 17.24 7.23 0.00 0.88 355.19 +237 15.22 3.28 0.80 0.72 307.97 +238 13.70 3.32 0.77 0.77 277.80 +239 14.13 1.82 0.77 0.69 316.47 +240 16.38 2.53 0.00 0.63 456.81 +241 18.42 4.57 0.00 0.72 452.46 +242 17.38 4.16 0.00 0.71 437.26 +243 13.34 1.66 0.00 0.64 406.54 +244 14.64 0.10 0.00 0.53 466.12 +245 14.24 -1.12 0.00 0.49 478.22 +246 18.26 2.03 0.00 0.59 483.90 +247 18.46 4.49 0.00 0.73 442.57 +248 19.18 3.21 0.00 0.64 474.89 +249 19.29 3.98 0.00 0.69 461.57 +250 19.86 3.78 0.00 0.67 471.07 +251 16.78 3.43 0.00 0.70 424.25 +252 15.05 3.42 1.32 0.78 289.84 +253 6.76 2.47 0.60 0.73 126.47 +254 14.36 3.04 0.26 0.76 288.79 +255 13.04 0.20 0.00 0.61 421.13 +256 13.49 2.38 0.00 0.72 379.45 +257 15.05 2.78 0.00 0.74 401.45 +258 18.41 2.83 0.00 0.70 450.62 +259 17.05 2.52 0.00 0.70 431.11 +260 12.64 2.93 0.36 0.75 248.57 +261 15.13 2.63 0.00 0.74 392.98 +262 19.83 1.10 0.00 0.61 473.45 +263 19.62 7.49 0.47 1.04 278.29 +264 15.89 1.31 0.00 0.67 423.09 +265 15.17 1.21 0.00 0.67 413.10 +266 11.38 -1.62 0.00 0.54 396.65 +267 12.60 -3.04 0.00 0.49 432.71 +268 11.80 -3.34 0.00 0.48 421.70 +269 13.11 -3.04 0.00 0.49 429.46 +270 16.83 -1.66 0.00 0.53 446.31 +271 16.30 0.65 0.00 0.64 409.50 +272 16.71 0.96 0.00 0.65 406.54 +273 15.83 1.51 0.00 0.68 382.61 +274 18.56 1.11 0.00 0.65 416.99 +275 16.49 2.66 0.00 0.74 367.05 +276 12.20 -2.92 0.00 0.49 390.28 +277 14.10 -0.80 0.00 0.58 381.56 +278 15.18 -0.19 0.00 0.60 383.60 +279 13.73 -0.60 0.00 0.58 368.05 +280 17.52 -0.99 0.00 0.56 409.36 +281 17.94 0.52 0.00 0.62 393.94 +282 17.08 0.90 0.00 0.65 375.38 +283 15.34 0.63 0.00 0.64 350.85 +284 11.26 -2.62 0.00 0.50 339.10 +285 14.19 -2.56 0.00 0.49 370.84 +286 11.27 0.23 0.00 0.62 281.57 +287 8.30 -1.61 0.00 0.54 259.41 +288 11.31 -2.02 0.00 0.53 319.55 +289 6.25 -2.89 0.19 0.49 243.09 +290 -1.97 -9.27 0.00 0.30 202.67 +291 0.17 -7.87 0.00 0.34 219.84 +292 0.78 -9.09 0.00 0.31 262.18 +293 7.43 -7.75 0.00 0.34 340.51 +294 9.51 -8.62 0.00 0.32 364.11 +295 8.59 -8.46 0.00 0.32 351.31 +296 5.30 -6.26 0.00 0.38 278.96 +297 -2.82 -14.22 0.00 0.20 279.06 +298 3.79 -14.50 0.00 0.20 355.11 +299 10.12 -9.03 0.00 0.31 354.00 +300 2.43 -5.85 0.52 0.39 159.43 +301 -3.67 -10.65 0.35 0.27 155.57 +302 -3.75 -16.77 0.00 0.16 331.27 +303 3.81 -15.30 0.00 0.19 387.24 +304 9.81 -7.24 0.00 0.35 364.26 +305 9.68 -5.70 0.00 0.40 343.04 +306 12.20 -4.41 0.00 0.44 350.22 +307 8.47 -3.01 0.00 0.49 283.44 +308 7.09 -5.31 0.00 0.41 298.20 +309 6.36 -4.80 0.00 0.43 276.62 +310 11.36 -4.86 0.00 0.43 337.71 +311 10.32 -2.52 0.00 0.51 268.04 +312 10.51 0.30 0.00 0.62 229.34 +313 8.80 -1.15 0.00 0.56 226.22 +314 12.11 -1.45 0.00 0.55 271.85 +315 14.45 -3.18 0.00 0.47 302.27 +316 12.56 -2.26 0.00 0.52 278.41 +317 8.74 -2.54 0.42 0.51 180.06 +318 0.43 -8.48 0.41 0.32 171.58 +319 -7.58 -15.00 0.22 0.19 148.95 +320 -6.57 -21.93 0.00 0.11 313.12 +321 -10.52 -21.39 0.07 0.11 255.14 +322 -0.58 -20.91 0.00 0.12 335.56 +323 0.74 -15.79 0.00 0.18 309.74 +324 -0.33 -15.88 0.00 0.18 299.73 +325 1.92 -15.28 0.00 0.19 309.70 +326 3.66 -12.94 0.00 0.23 301.17 +327 -2.50 -13.69 0.00 0.21 241.66 +328 -2.79 -11.32 0.00 0.26 199.07 +329 -8.55 -13.50 0.36 0.22 97.12 +330 -2.62 -20.14 0.00 0.12 305.21 +331 -5.47 -16.64 0.00 0.17 240.04 +332 -0.86 -18.92 0.00 0.14 302.05 +333 1.06 -14.53 0.00 0.20 283.49 +334 5.19 -6.51 0.00 0.37 240.64 +335 4.62 -3.36 0.00 0.48 182.04 +336 1.05 -7.53 0.00 0.35 196.75 +337 8.05 -13.60 0.00 0.19 307.81 +338 6.35 -5.64 0.00 0.40 240.04 +339 5.93 -1.56 0.00 0.54 168.63 +340 0.87 -7.73 0.00 0.34 193.47 +341 -0.86 -9.77 0.00 0.29 200.14 +342 -2.61 -11.04 0.29 0.26 146.72 +343 -2.71 -16.66 0.28 0.17 202.77 +344 -5.32 -16.86 0.00 0.16 239.19 +345 0.42 -14.02 0.00 0.19 268.55 +346 3.58 -7.64 0.00 0.31 233.65 +347 6.91 -8.15 0.00 0.27 269.93 +348 4.95 -9.79 0.00 0.24 265.91 +349 5.01 -9.83 0.00 0.24 264.25 +350 5.73 -7.08 0.00 0.31 244.60 +351 6.57 -6.35 0.00 0.31 244.86 +352 8.86 -4.29 0.00 0.34 248.36 +353 4.91 -8.70 0.00 0.26 255.05 +354 1.25 -9.82 0.00 0.24 229.19 +355 5.08 -9.78 0.00 0.21 267.97 +356 0.44 -9.63 0.00 0.26 218.92 +357 1.56 -7.74 0.00 0.30 207.35 +358 -2.10 -10.18 0.00 0.27 187.68 +359 2.31 -10.79 0.00 0.21 254.05 +360 5.37 -5.93 0.00 0.30 235.22 +361 4.40 -5.17 0.00 0.34 211.93 +362 10.81 -4.53 0.00 0.28 275.20 +363 6.39 -5.22 0.00 0.31 243.28 +364 6.81 -10.64 0.00 0.18 289.69 +365 8.94 -8.32 0.00 0.21 287.19 +366 8.94 -8.32 0.00 0.21 287.19 diff --git a/tests/example/Input/data_weather_daymet/weath.1981 b/tests/example/Input/data_weather_daymet/weath.1981 new file mode 100644 index 000000000..e06ee2ff5 --- /dev/null +++ b/tests/example/Input/data_weather_daymet/weath.1981 @@ -0,0 +1,367 @@ +# weather for site daymet example at -105.58 / 39.59 year = 1981 +# DOY, Tmax_C, Tmin_C, PPT_cm, vp_kPa, rsds_WPERm2 +1 5.81 -9.20 0.00 0.30 278.89 +2 1.59 -7.56 0.00 0.35 206.71 +3 3.08 -9.22 0.00 0.30 253.62 +4 1.88 -8.46 0.00 0.32 228.01 +5 -2.21 -10.00 0.13 0.29 185.62 +6 -0.11 -15.82 0.00 0.18 292.82 +7 5.26 -11.83 0.00 0.25 301.90 +8 4.33 -10.60 0.00 0.27 287.50 +9 3.09 -11.15 0.00 0.26 283.59 +10 1.49 -11.71 0.00 0.25 274.66 +11 1.30 -13.99 0.00 0.21 297.40 +12 4.83 -12.22 0.00 0.24 311.02 +13 2.62 -11.34 0.00 0.26 287.56 +14 0.69 -12.67 0.00 0.23 283.54 +15 -1.29 -13.72 0.00 0.21 274.71 +16 -4.58 -14.17 0.07 0.20 233.90 +17 1.12 -14.41 0.00 0.20 312.07 +18 -1.21 -12.53 0.00 0.23 264.96 +19 -2.53 -16.64 0.00 0.17 303.68 +20 -2.82 -14.06 0.00 0.21 268.07 +21 5.06 -14.92 0.00 0.19 348.38 +22 7.86 -10.78 0.00 0.27 343.57 +23 7.27 -8.10 0.00 0.33 321.96 +24 3.28 -9.00 0.00 0.31 290.18 +25 -6.18 -15.43 0.00 0.18 243.79 +26 -7.86 -17.79 0.00 0.15 259.69 +27 -2.46 -13.96 0.00 0.21 288.21 +28 1.10 -10.55 0.00 0.27 291.45 +29 0.24 -10.82 0.00 0.27 284.56 +30 -0.12 -11.20 0.00 0.26 287.31 +31 -11.37 -16.09 0.13 0.17 147.91 +32 -12.45 -18.35 0.03 0.14 183.89 +33 -5.96 -16.95 0.00 0.16 303.04 +34 -1.57 -16.96 0.00 0.16 362.88 +35 -1.51 -15.79 0.00 0.18 351.38 +36 -2.22 -14.65 0.00 0.20 331.13 +37 -5.59 -13.98 0.00 0.21 257.99 +38 -5.69 -14.89 0.00 0.19 282.54 +39 -5.46 -14.31 0.00 0.20 279.65 +40 -2.54 -13.96 0.76 0.21 257.04 +41 -7.93 -26.00 0.47 0.07 320.74 +42 0.00 -29.69 0.00 0.05 452.77 +43 -2.63 -10.25 0.00 0.28 254.73 +44 3.53 -8.57 0.00 0.32 357.60 +45 7.13 -7.34 0.00 0.33 394.24 +46 3.20 -7.91 0.00 0.34 343.19 +47 7.50 -8.28 0.00 0.30 416.27 +48 3.19 -5.87 0.00 0.39 303.11 +49 3.34 -8.68 0.00 0.31 373.45 +50 6.92 -5.68 0.00 0.37 384.48 +51 3.75 -5.25 0.00 0.41 315.69 +52 -5.77 -16.64 0.00 0.17 374.94 +53 -1.92 -16.22 0.00 0.17 437.01 +54 4.87 -9.90 0.00 0.26 442.57 +55 5.61 -7.74 0.00 0.30 423.41 +56 5.84 -9.16 0.00 0.26 449.42 +57 0.98 -9.57 0.00 0.29 374.60 +58 2.18 -11.65 0.00 0.23 441.17 +59 2.80 -12.19 0.00 0.21 459.97 +60 0.15 -11.29 0.25 0.26 303.72 +61 1.20 -10.54 0.18 0.26 407.94 +62 -4.52 -8.55 1.07 0.32 128.90 +63 -2.71 -11.97 0.48 0.24 270.80 +64 3.64 -15.40 0.00 0.17 526.38 +65 0.83 -11.19 0.63 0.26 329.09 +66 -4.81 -11.91 0.42 0.25 226.53 +67 -0.32 -14.40 0.00 0.20 486.32 +68 -0.64 -16.42 0.00 0.17 512.72 +69 0.67 -14.66 0.00 0.19 506.16 +70 -0.73 -14.73 0.00 0.19 486.33 +71 0.90 -13.03 0.46 0.22 371.21 +72 -0.49 -13.40 0.42 0.22 368.35 +73 3.19 -12.22 0.00 0.22 530.55 +74 2.64 -13.83 0.00 0.19 547.48 +75 5.29 -12.01 0.00 0.21 559.92 +76 -2.22 -10.38 0.00 0.28 355.73 +77 -2.25 -13.22 0.00 0.22 453.17 +78 5.04 -13.50 0.00 0.18 585.41 +79 3.72 -7.15 0.37 0.36 336.38 +80 -2.68 -9.27 0.56 0.30 230.68 +81 1.86 -14.92 0.00 0.16 581.04 +82 2.50 -9.37 0.00 0.26 486.75 +83 -0.76 -8.88 0.99 0.31 281.13 +84 5.37 -15.43 0.00 0.14 635.84 +85 8.35 -9.23 0.00 0.22 604.73 +86 1.12 -5.03 0.00 0.42 295.62 +87 -6.54 -11.07 1.46 0.26 175.19 +88 0.55 -11.68 0.00 0.22 533.24 +89 1.27 -10.06 0.00 0.25 513.50 +90 -0.70 -14.86 0.00 0.16 587.68 +91 8.84 -11.07 0.00 0.16 663.36 +92 7.44 -2.68 0.34 0.45 345.89 +93 -3.41 -7.27 0.00 0.35 204.34 +94 -5.04 -14.68 0.00 0.19 470.38 +95 5.09 -14.14 0.00 0.13 672.43 +96 5.96 -8.14 0.00 0.23 590.47 +97 4.21 -8.66 0.72 0.27 424.78 +98 2.37 -8.30 0.00 0.27 507.84 +99 9.98 -8.33 0.00 0.20 674.34 +100 12.50 -1.24 0.00 0.36 592.24 +101 10.14 -2.47 0.00 0.35 568.31 +102 9.01 -3.54 0.00 0.32 569.35 +103 9.69 -4.58 0.00 0.28 616.40 +104 8.68 -5.63 0.00 0.26 621.67 +105 10.15 -1.97 0.00 0.35 569.27 +106 9.53 -3.75 0.00 0.30 599.78 +107 13.28 -2.73 0.00 0.28 655.36 +108 10.54 -1.78 0.00 0.35 575.54 +109 6.22 -2.96 0.78 0.45 350.56 +110 9.12 -5.79 0.35 0.32 477.82 +111 8.86 -2.53 0.00 0.38 550.35 +112 4.19 -6.60 0.00 0.30 535.86 +113 10.23 -7.42 0.00 0.22 693.57 +114 12.99 -3.11 0.00 0.29 670.14 +115 16.12 -0.73 0.00 0.33 681.58 +116 16.54 -0.14 0.00 0.34 672.13 +117 13.75 0.06 0.00 0.39 602.83 +118 13.01 -1.81 0.00 0.33 631.00 +119 13.96 -1.92 0.00 0.32 650.76 +120 14.06 0.49 0.00 0.41 595.16 +121 15.64 -0.31 0.00 0.34 654.39 +122 12.95 2.16 1.02 0.61 378.34 +123 9.42 0.47 0.34 0.61 323.40 +124 7.38 -4.62 0.48 0.40 410.12 +125 11.14 -4.73 1.13 0.36 486.66 +126 7.85 -2.81 0.49 0.49 379.11 +127 5.84 -6.04 0.00 0.34 556.72 +128 1.16 -6.91 0.54 0.36 309.78 +129 0.24 -8.65 0.58 0.32 345.76 +130 8.21 -9.24 0.00 0.24 704.26 +131 8.94 -5.24 0.00 0.33 635.39 +132 5.46 -5.51 0.55 0.40 403.01 +133 5.19 -9.66 0.00 0.24 658.46 +134 12.16 -6.51 0.00 0.26 721.33 +135 8.88 -3.17 0.00 0.40 572.65 +136 1.61 -2.70 0.00 0.50 240.24 +137 3.25 -3.14 2.25 0.48 263.25 +138 2.40 -3.30 0.60 0.48 240.89 +139 10.64 -4.10 0.00 0.37 663.89 +140 10.60 -1.99 0.00 0.46 607.36 +141 7.57 -1.15 0.00 0.55 465.19 +142 7.55 -1.55 0.00 0.53 483.81 +143 7.19 -1.20 0.00 0.55 460.40 +144 11.63 -3.71 0.66 0.43 510.78 +145 11.66 -1.22 0.00 0.48 629.99 +146 13.77 -1.08 0.00 0.45 677.98 +147 14.56 1.62 0.00 0.57 628.12 +148 12.59 1.39 1.17 0.67 428.65 +149 7.41 0.04 0.30 0.61 318.67 +150 13.89 -0.75 1.06 0.55 501.99 +151 12.86 2.18 0.31 0.71 420.96 +152 13.44 -1.99 0.00 0.45 692.88 +153 15.45 0.59 0.90 0.60 498.63 +154 12.29 0.66 1.04 0.64 437.71 +155 11.73 0.92 0.00 0.61 565.36 +156 14.29 0.84 0.00 0.56 640.20 +157 18.19 2.44 0.00 0.58 679.74 +158 18.66 2.74 0.00 0.60 675.18 +159 19.60 7.85 0.00 0.92 560.27 +160 20.75 10.99 0.00 1.20 486.95 +161 19.86 6.06 0.00 0.78 621.25 +162 20.93 5.13 0.00 0.69 657.72 +163 20.48 6.26 0.00 0.76 623.30 +164 16.60 7.34 0.00 0.95 475.75 +165 9.00 -1.75 0.00 0.50 547.82 +166 7.87 -5.71 0.00 0.36 627.17 +167 17.18 -2.42 0.00 0.40 707.54 +168 18.29 5.42 0.00 0.76 571.62 +169 15.89 -0.42 0.00 0.48 654.41 +170 20.32 3.87 0.00 0.63 645.07 +171 20.17 4.73 0.00 0.67 618.87 +172 21.59 4.06 0.00 0.62 648.25 +173 22.95 5.23 0.00 0.67 640.73 +174 21.49 7.80 0.00 0.84 555.31 +175 20.88 6.53 0.00 0.77 570.35 +176 23.05 7.23 0.00 0.77 596.36 +177 22.71 7.96 0.38 1.01 377.59 +178 20.14 6.15 0.29 0.89 368.56 +179 15.36 4.24 0.44 0.83 315.90 +180 16.43 2.57 1.11 0.72 369.52 +181 18.32 3.40 0.00 0.68 515.88 +182 18.42 4.57 0.46 0.84 364.69 +183 11.77 7.85 1.10 1.06 126.47 +184 14.74 5.16 0.80 0.88 285.46 +185 15.47 4.32 0.00 0.82 431.36 +186 19.46 3.84 0.00 0.72 533.40 +187 22.86 7.94 0.00 0.95 512.21 +188 22.00 9.05 0.29 1.15 347.67 +189 14.76 5.82 0.62 0.92 270.35 +190 18.63 6.64 0.53 0.98 335.25 +191 19.37 6.79 0.00 0.94 465.73 +192 20.84 7.27 0.55 1.02 362.76 +193 18.52 6.75 0.46 0.98 333.30 +194 15.71 6.11 0.50 0.94 287.31 +195 19.29 7.39 0.38 1.03 333.63 +196 19.68 6.85 0.28 0.99 351.28 +197 14.93 6.94 0.51 1.00 250.73 +198 17.01 8.00 0.50 1.07 277.47 +199 16.46 3.84 0.29 0.80 362.08 +200 19.88 4.02 0.00 0.77 547.55 +201 22.02 4.89 0.00 0.80 561.20 +202 21.90 7.89 0.00 1.02 506.28 +203 21.24 7.03 0.00 0.96 514.14 +204 22.14 8.15 0.00 1.04 506.55 +205 18.34 5.50 0.27 0.90 364.72 +206 18.17 4.65 0.36 0.85 377.73 +207 15.38 5.18 0.90 0.88 317.45 +208 15.00 3.19 0.00 0.77 474.56 +209 19.51 2.91 0.00 0.73 558.61 +210 20.37 7.01 0.00 1.00 495.71 +211 20.89 8.13 0.00 1.08 481.01 +212 21.52 6.83 0.00 0.97 520.34 +213 18.42 8.24 0.00 1.09 408.61 +214 21.29 5.27 0.00 0.85 536.12 +215 21.25 5.81 0.00 0.88 522.96 +216 22.07 8.12 0.00 1.03 492.15 +217 22.69 6.85 0.00 0.91 526.54 +218 18.08 7.12 0.00 1.01 424.07 +219 15.88 3.82 0.00 0.80 456.01 +220 17.69 3.59 0.00 0.75 499.03 +221 8.51 3.41 0.43 0.78 168.10 +222 9.20 2.80 0.45 0.75 210.48 +223 12.21 2.68 0.53 0.74 295.33 +224 11.21 5.72 0.63 0.92 182.48 +225 13.76 2.91 0.44 0.75 325.50 +226 14.73 2.64 0.70 0.74 348.81 +227 14.03 5.50 0.95 0.90 267.66 +228 13.67 2.95 0.58 0.75 319.62 +229 15.75 2.07 0.00 0.71 494.49 +230 17.94 2.18 0.00 0.69 526.75 +231 19.31 3.47 0.00 0.75 524.67 +232 19.14 5.06 0.17 0.86 492.10 +233 20.14 5.44 0.40 0.90 373.26 +234 13.42 4.42 0.19 0.84 370.34 +235 17.03 2.44 0.00 0.72 502.81 +236 16.68 5.12 0.00 0.88 437.76 +237 18.12 3.32 0.00 0.77 498.96 +238 17.73 3.78 0.00 0.80 481.15 +239 16.70 2.73 0.00 0.74 483.74 +240 18.46 3.16 0.00 0.74 501.27 +241 18.16 4.59 0.00 0.82 468.11 +242 18.35 3.94 0.00 0.77 482.16 +243 14.30 4.12 0.27 0.82 291.55 +244 16.27 1.09 0.55 0.66 370.53 +245 19.28 4.34 0.00 0.78 484.62 +246 14.30 2.52 0.00 0.73 428.35 +247 16.61 5.06 0.00 0.87 419.96 +248 16.00 4.12 0.47 0.82 319.71 +249 12.00 2.85 0.00 0.75 358.57 +250 7.49 2.53 0.00 0.73 215.03 +251 12.89 1.92 0.48 0.70 305.77 +252 14.56 2.42 0.72 0.73 319.93 +253 13.33 1.49 0.29 0.68 313.79 +254 12.45 3.00 0.00 0.76 352.35 +255 13.91 2.20 0.00 0.72 407.01 +256 15.63 0.97 0.00 0.66 456.83 +257 15.79 2.59 0.21 0.74 320.19 +258 14.64 1.31 0.00 0.67 426.69 +259 10.86 2.09 0.00 0.71 320.94 +260 14.17 -0.81 0.00 0.58 452.56 +261 17.55 0.64 0.00 0.64 469.11 +262 17.21 1.50 0.00 0.68 451.55 +263 15.66 3.39 0.00 0.78 394.83 +264 16.06 3.11 0.00 0.76 402.57 +265 15.70 2.72 0.00 0.74 402.19 +266 16.44 2.83 0.12 0.75 408.72 +267 14.17 3.44 0.00 0.78 352.44 +268 14.52 2.33 0.00 0.72 382.82 +269 11.40 0.19 0.00 0.62 365.42 +270 17.02 -0.61 0.00 0.58 450.94 +271 17.52 3.82 0.00 0.80 396.99 +272 15.67 4.82 0.00 0.86 344.46 +273 12.66 3.06 0.00 0.76 316.60 +274 15.63 -0.28 0.00 0.60 423.44 +275 13.45 1.56 0.00 0.68 363.27 +276 6.40 1.99 0.00 0.70 163.27 +277 8.11 -1.33 0.00 0.55 315.44 +278 8.06 -2.91 0.00 0.49 347.99 +279 11.78 -3.84 0.00 0.46 410.80 +280 13.62 -0.22 0.00 0.60 379.00 +281 10.26 1.71 0.00 0.69 274.10 +282 6.33 -0.90 0.00 0.57 241.56 +283 11.02 -2.20 0.00 0.52 366.21 +284 12.32 1.29 0.00 0.67 322.74 +285 6.22 -1.70 0.00 0.54 255.42 +286 7.49 -1.82 0.34 0.53 219.08 +287 4.05 -1.40 0.56 0.55 141.20 +288 0.08 -2.01 1.20 0.53 64.36 +289 1.84 -4.04 0.00 0.45 205.44 +290 2.27 -6.88 0.00 0.36 295.45 +291 6.41 -8.75 0.00 0.32 382.65 +292 9.48 -7.53 0.00 0.35 390.48 +293 8.17 -5.58 0.00 0.40 358.88 +294 3.36 -6.76 0.00 0.37 305.80 +295 2.25 -6.11 0.00 0.39 268.35 +296 5.94 -8.29 0.00 0.33 358.93 +297 -1.16 -5.27 0.58 0.41 110.19 +298 0.09 -13.73 0.00 0.21 353.16 +299 10.49 -10.97 0.00 0.26 382.87 +300 11.09 -5.46 0.00 0.41 359.00 +301 9.75 -2.10 0.00 0.52 310.47 +302 7.66 -2.15 0.00 0.52 276.88 +303 -1.17 -9.43 1.09 0.30 210.85 +304 -2.36 -12.06 0.00 0.24 312.77 +305 4.31 -13.24 0.00 0.22 394.40 +306 6.11 -8.24 0.00 0.33 363.34 +307 6.76 -7.26 0.00 0.35 354.56 +308 9.66 -6.88 0.00 0.36 369.73 +309 6.40 -7.90 0.00 0.34 349.22 +310 8.47 -3.80 0.00 0.46 320.97 +311 5.37 -5.29 0.00 0.41 294.03 +312 1.24 -6.69 0.42 0.37 180.70 +313 2.92 -11.01 0.00 0.26 335.69 +314 6.11 -10.20 0.00 0.28 349.18 +315 6.97 -8.88 0.00 0.31 340.06 +316 7.16 -8.77 0.00 0.31 335.90 +317 8.81 -8.48 0.00 0.32 338.67 +318 8.40 -2.42 0.00 0.51 260.00 +319 8.79 -4.61 0.00 0.43 291.12 +320 10.07 -0.55 0.00 0.59 246.65 +321 10.87 -2.37 0.00 0.51 282.00 +322 -3.79 -9.38 0.00 0.30 151.55 +323 -2.40 -13.24 0.00 0.22 257.83 +324 2.58 -12.89 0.00 0.23 305.82 +325 3.25 -6.36 0.00 0.38 228.39 +326 3.09 -6.43 0.00 0.38 227.05 +327 3.99 -8.80 0.00 0.31 269.89 +328 9.01 -3.54 0.00 0.47 262.51 +329 4.39 -4.86 0.44 0.43 166.34 +330 -5.11 -16.21 0.00 0.17 252.33 +331 -0.15 -13.65 0.00 0.21 277.55 +332 -0.73 -15.76 0.00 0.18 288.37 +333 -2.64 -11.39 0.39 0.26 157.53 +334 -10.54 -15.54 0.00 0.18 130.99 +335 -7.61 -18.47 0.00 0.14 244.44 +336 -3.29 -14.86 0.00 0.19 252.78 +337 -1.42 -7.62 0.00 0.34 159.16 +338 5.04 -10.96 0.00 0.26 289.66 +339 3.42 -7.17 0.00 0.36 237.39 +340 5.66 -4.66 0.00 0.43 232.06 +341 7.81 -5.96 0.00 0.39 268.54 +342 8.01 -6.29 0.00 0.38 270.10 +343 9.24 -7.90 0.00 0.31 287.28 +344 4.70 -4.50 0.00 0.44 209.68 +345 4.30 -7.34 0.27 0.35 188.09 +346 -0.19 -9.60 0.00 0.30 218.77 +347 -1.10 -11.03 0.16 0.26 229.68 +348 -2.53 -10.62 0.00 0.27 200.11 +349 -0.28 -7.12 0.64 0.36 135.16 +350 -7.79 -13.47 0.51 0.22 118.68 +351 -8.25 -17.02 0.00 0.16 221.18 +352 -0.51 -14.70 0.00 0.20 278.60 +353 1.71 -8.25 0.00 0.33 233.07 +354 2.01 -5.83 0.50 0.40 152.75 +355 -5.49 -8.51 0.69 0.32 69.31 +356 -10.30 -14.80 0.59 0.19 101.61 +357 -14.37 -22.71 0.00 0.10 223.36 +358 -7.76 -23.50 0.00 0.09 297.45 +359 -10.09 -14.99 0.39 0.19 111.77 +360 -10.33 -21.03 0.73 0.11 200.76 +361 -12.47 -17.67 0.61 0.15 122.25 +362 -11.77 -21.53 0.00 0.11 257.72 +363 -2.98 -20.20 0.00 0.12 311.08 +364 -1.81 -12.51 0.82 0.23 202.83 +365 -2.95 -14.10 0.69 0.21 209.22 diff --git a/tests/example/Input/data_weather_gridmet/weath.1980 b/tests/example/Input/data_weather_gridmet/weath.1980 new file mode 100644 index 000000000..df390099d --- /dev/null +++ b/tests/example/Input/data_weather_gridmet/weath.1980 @@ -0,0 +1,368 @@ +# weather for site gridmet example at -105.58 / 39.59 year = 1980 +# DOY, Tmax_C, Tmin_C, PPT_cm, sfcWind_mPERs, hursmax_pct, hursmin_pct, rsds_WPERm2 +1 -3.18 -12.32 0.75 3.16 74.17 31.42 93.43 +2 -7.47 -17.53 0.16 3.08 100.00 45.43 62.40 +3 -5.49 -19.73 0.09 5.08 83.53 38.31 97.25 +4 -4.09 -14.61 0.00 4.83 76.36 48.42 98.57 +5 -0.49 -12.20 0.00 5.93 75.18 37.81 99.56 +6 -4.01 -15.73 0.66 8.55 63.22 28.94 101.83 +7 -8.99 -18.31 0.29 4.77 94.24 48.81 83.69 +8 -6.56 -18.60 0.55 6.18 83.57 50.66 84.40 +9 -3.28 -14.32 0.19 5.05 75.06 53.04 101.27 +10 -2.58 -11.95 0.49 10.07 88.49 51.53 94.01 +11 -7.69 -19.43 0.07 5.44 79.21 33.63 102.55 +12 -0.66 -11.63 0.39 7.90 91.83 55.05 94.64 +13 -0.34 -6.65 0.09 5.76 84.86 53.88 102.87 +14 1.84 -7.11 0.00 4.16 84.82 49.71 101.95 +15 -1.47 -9.66 0.00 3.19 88.35 34.35 104.86 +16 -3.63 -12.83 0.00 2.49 86.95 37.00 91.15 +17 -3.64 -14.38 0.60 3.09 95.44 43.91 100.81 +18 -5.05 -14.03 0.96 3.79 98.84 47.13 89.72 +19 -10.02 -18.38 0.24 2.96 100.00 60.81 99.18 +20 -4.19 -16.38 0.04 2.97 91.31 42.67 114.47 +21 -4.76 -15.25 0.05 2.89 77.27 42.25 103.52 +22 -8.50 -22.42 0.00 4.27 89.88 34.86 119.05 +23 -3.60 -17.24 0.00 5.18 60.97 33.60 116.35 +24 -0.78 -11.33 0.08 6.76 63.83 39.22 117.15 +25 -4.70 -15.67 0.77 4.15 84.19 37.65 106.71 +26 -8.91 -20.27 0.40 3.00 99.57 47.62 106.83 +27 -4.93 -16.87 0.18 5.32 76.73 53.29 112.25 +28 -6.23 -14.42 0.51 2.80 98.13 51.16 101.26 +29 -4.05 -14.35 0.54 5.47 98.82 62.55 108.96 +30 -8.08 -18.67 0.02 3.90 89.93 46.95 127.95 +31 -3.79 -18.50 0.00 3.37 68.57 26.31 128.77 +32 -2.49 -12.90 0.00 5.06 62.46 24.41 130.80 +33 -2.37 -12.73 0.00 5.36 96.58 44.84 131.59 +34 2.40 -9.98 0.00 4.75 89.05 32.52 128.80 +35 -1.27 -10.03 0.00 5.71 86.31 33.75 130.29 +36 -0.61 -12.21 0.00 4.40 65.03 23.50 138.31 +37 1.91 -8.59 0.00 3.43 52.21 13.37 126.21 +38 -4.52 -18.49 1.88 6.56 100.00 36.54 118.06 +39 -10.17 -23.67 0.15 2.47 100.00 55.46 145.41 +40 -3.49 -20.98 0.00 3.79 89.15 9.98 146.30 +41 -2.89 -15.48 0.00 4.14 63.68 25.16 147.20 +42 -5.12 -17.45 0.00 2.74 72.97 26.97 145.36 +43 -3.78 -16.58 0.00 4.67 85.32 51.28 148.48 +44 -1.75 -13.73 0.00 4.85 80.28 50.62 148.80 +45 0.05 -11.08 0.37 3.82 86.85 49.06 142.58 +46 -1.45 -10.11 0.35 2.23 100.00 59.03 117.92 +47 -2.31 -10.87 0.08 2.85 100.00 60.20 143.15 +48 -1.16 -12.21 0.08 4.26 99.00 63.65 144.30 +49 2.20 -7.80 0.38 5.34 96.32 58.82 124.41 +50 -1.14 -9.63 0.32 4.85 100.00 54.18 144.18 +51 -1.44 -10.05 0.03 5.55 96.60 56.92 133.29 +52 -2.33 -11.37 0.13 4.96 95.97 52.45 161.09 +53 -3.14 -13.66 0.03 3.89 97.29 51.53 145.03 +54 -4.55 -14.93 0.36 3.36 99.36 47.35 153.70 +55 -6.17 -17.17 0.07 5.26 100.00 48.15 170.69 +56 2.02 -13.49 0.00 3.47 75.78 16.33 172.49 +57 1.43 -8.89 0.09 5.05 52.12 16.81 169.01 +58 5.86 -6.28 0.00 4.87 65.97 30.37 168.90 +59 6.66 -4.41 0.20 3.96 50.11 16.99 172.12 +60 -0.92 -13.90 0.29 4.06 92.38 21.80 139.78 +61 -5.09 -17.13 0.00 2.96 85.19 39.84 183.86 +62 -0.19 -14.01 0.00 4.08 75.93 24.28 177.34 +63 1.41 -8.83 0.26 4.04 74.69 40.87 168.01 +64 -2.72 -13.34 0.22 5.32 90.79 44.32 166.63 +65 -2.86 -13.92 0.00 5.04 79.72 47.75 186.75 +66 -1.59 -10.78 1.22 3.42 93.57 46.37 172.48 +67 -3.89 -13.75 0.33 3.42 99.71 48.86 158.09 +68 -4.59 -16.15 0.00 5.04 83.60 44.83 187.56 +69 -3.70 -13.28 0.00 8.32 71.16 32.58 193.26 +70 2.28 -9.96 0.00 5.53 59.05 32.43 192.94 +71 0.88 -9.43 0.09 2.95 60.81 16.03 184.41 +72 -2.62 -13.33 0.00 7.83 76.50 25.65 151.62 +73 1.38 -14.33 0.00 5.80 53.56 28.01 200.06 +74 3.80 -9.29 0.00 4.94 59.75 28.41 200.05 +75 3.81 -7.55 0.49 6.43 46.97 23.79 200.76 +76 -4.67 -16.70 0.43 3.81 95.92 20.36 182.52 +77 -2.34 -19.77 0.00 4.66 77.00 38.28 212.47 +78 2.31 -10.13 0.00 4.86 56.67 18.43 210.86 +79 -0.78 -12.05 0.00 4.89 61.92 24.48 178.78 +80 -2.22 -12.59 0.03 3.35 80.20 36.80 214.76 +81 0.15 -13.74 0.00 4.16 58.96 35.91 216.12 +82 -3.08 -11.31 0.52 4.59 98.57 37.34 184.55 +83 -3.71 -14.38 0.18 2.38 100.00 52.81 201.63 +84 -2.89 -16.94 0.00 3.38 90.33 55.87 214.32 +85 -3.19 -13.38 0.24 4.53 96.52 43.72 183.28 +86 -5.39 -17.97 0.34 3.35 86.35 38.14 193.47 +87 -3.92 -18.08 1.00 2.81 98.79 41.92 190.94 +88 -6.60 -14.47 0.41 5.50 100.00 61.95 193.69 +89 -5.34 -15.38 0.28 4.71 98.07 58.99 188.78 +90 -1.62 -16.86 0.79 3.98 99.31 39.29 220.88 +91 -4.97 -13.20 1.10 3.38 98.32 48.20 210.65 +92 -6.08 -16.35 0.23 3.40 98.66 50.78 193.54 +93 -6.44 -14.82 0.52 3.69 98.72 59.84 185.20 +94 -4.69 -16.38 0.00 3.24 82.65 50.36 204.12 +95 -0.36 -13.54 0.00 3.45 75.10 47.17 219.58 +96 2.85 -9.46 0.00 4.64 63.50 34.55 207.00 +97 -0.34 -9.11 0.44 6.86 72.94 33.26 182.93 +98 -5.15 -14.42 0.18 6.48 91.55 30.11 142.60 +99 -2.56 -16.73 0.00 5.38 59.36 33.26 230.03 +100 2.63 -10.73 0.08 4.45 60.34 32.50 208.68 +101 1.55 -9.52 0.00 4.58 60.35 28.48 201.34 +102 -5.24 -13.57 1.37 5.02 98.15 29.22 201.30 +103 -6.64 -19.40 0.02 4.09 97.22 49.39 234.90 +104 -3.71 -18.80 0.00 4.40 72.95 45.16 241.11 +105 1.78 -13.30 0.00 4.69 60.95 27.74 241.31 +106 3.39 -7.92 0.00 3.89 54.34 35.76 238.01 +107 0.18 -10.22 0.00 5.69 53.93 27.45 243.39 +108 3.69 -10.81 0.00 3.08 51.13 19.68 245.49 +109 7.39 -6.61 0.00 3.39 53.15 15.97 246.12 +110 8.59 -4.43 0.00 2.99 46.97 20.42 243.62 +111 9.18 -4.23 0.00 4.01 44.70 15.78 244.40 +112 9.59 -2.73 0.00 3.61 54.13 20.27 228.56 +113 9.47 -3.05 0.08 3.09 56.47 27.97 223.06 +114 5.16 -2.09 2.73 4.09 91.19 29.26 216.76 +115 0.23 -7.66 2.71 6.64 99.94 40.50 185.58 +116 -1.22 -12.02 0.50 3.88 94.26 48.84 239.64 +117 0.76 -13.38 0.08 2.38 82.13 48.60 244.22 +118 4.26 -11.17 0.04 1.70 80.08 43.79 248.04 +119 6.96 -6.73 0.07 2.56 72.67 33.25 248.32 +120 9.06 -3.33 0.61 2.70 58.13 33.15 213.05 +121 2.98 -4.09 3.28 5.19 99.05 29.71 185.18 +122 -1.19 -6.28 1.43 3.86 98.27 56.11 172.81 +123 2.22 -9.43 0.46 2.20 91.57 48.37 244.22 +124 5.99 -6.26 0.27 2.18 80.24 35.06 278.25 +125 6.48 -5.06 0.58 1.88 81.39 32.87 261.04 +126 5.88 -3.76 0.15 2.30 79.48 34.55 259.05 +127 6.77 -4.17 0.41 2.46 71.97 37.72 239.99 +128 4.68 -4.33 0.85 2.49 80.63 37.15 200.81 +129 4.36 -2.29 0.67 1.98 93.90 43.04 206.90 +130 6.69 -5.18 0.00 4.87 83.45 30.53 268.59 +131 5.21 -4.26 0.11 4.44 63.45 30.79 230.70 +132 4.08 -2.34 0.42 3.40 81.11 37.10 164.56 +133 -0.75 -8.88 0.24 5.85 73.08 40.26 239.64 +134 3.39 -11.23 0.13 2.38 65.99 35.25 274.53 +135 5.57 -6.90 1.23 2.60 65.74 35.18 262.67 +136 0.57 -4.40 1.03 4.11 98.55 33.30 239.24 +137 2.78 -5.68 1.56 3.30 97.94 56.46 242.22 +138 1.73 -5.38 0.61 2.16 94.09 44.07 236.48 +139 4.79 -7.78 0.11 2.99 71.50 41.08 294.09 +140 7.49 -5.06 0.00 3.87 61.32 35.06 287.17 +141 9.21 -2.86 0.00 3.59 60.76 32.63 296.41 +142 12.50 -1.19 0.00 2.49 57.76 28.49 300.21 +143 14.99 1.61 0.23 3.30 61.94 25.36 289.56 +144 10.48 1.47 0.00 4.16 63.14 26.04 301.78 +145 9.32 -2.58 0.00 6.76 44.54 23.41 299.10 +146 4.99 -7.41 0.00 6.86 45.66 21.81 311.50 +147 8.50 -6.69 0.00 5.06 42.91 21.34 304.56 +148 9.52 -2.01 0.00 5.40 41.01 22.61 269.86 +149 9.60 -2.28 0.00 4.86 47.84 23.68 308.92 +150 6.69 -2.99 0.00 4.86 41.99 25.58 289.47 +151 9.79 -3.13 0.00 3.76 56.83 29.00 308.01 +152 10.08 -2.49 0.00 5.76 37.77 21.12 310.27 +153 9.58 -3.41 0.00 4.95 47.43 17.85 291.03 +154 10.07 -3.89 0.00 4.66 49.07 17.48 292.42 +155 14.77 -1.79 0.00 4.67 40.10 16.03 300.45 +156 15.17 -0.67 0.00 5.65 33.56 15.63 299.94 +157 14.57 -0.27 0.00 5.35 34.96 14.84 300.24 +158 14.79 0.69 0.00 6.83 35.06 16.59 297.76 +159 13.45 -1.18 0.00 2.81 46.61 16.40 298.50 +160 15.34 -0.79 0.00 3.00 60.07 21.44 296.72 +161 15.36 -1.56 0.00 2.72 78.49 22.30 296.62 +162 16.98 0.20 0.00 3.52 73.38 24.15 293.44 +163 17.97 2.41 0.00 4.36 65.05 16.54 299.03 +164 16.27 1.62 0.00 5.15 33.29 16.24 300.13 +165 16.47 0.92 0.00 4.65 40.13 14.28 302.93 +166 16.67 1.51 0.00 5.64 30.28 13.47 303.55 +167 13.05 -1.67 0.00 3.06 33.39 13.08 298.37 +168 13.47 -2.19 0.00 2.24 51.86 17.81 299.44 +169 16.45 -1.06 0.00 2.65 41.29 18.97 296.13 +170 18.17 2.61 0.00 2.35 47.27 18.62 268.71 +171 16.65 3.32 0.00 2.73 39.76 16.76 285.82 +172 17.16 2.11 0.00 2.29 37.92 17.94 291.24 +173 16.88 2.51 0.00 4.27 53.56 15.96 268.45 +174 17.86 1.01 0.00 2.56 44.58 16.44 296.91 +175 20.38 2.62 0.00 5.16 42.62 13.79 301.75 +176 19.37 3.84 0.00 4.28 38.77 13.29 293.04 +177 20.87 3.63 0.00 3.56 36.94 16.04 297.93 +178 21.67 5.42 0.00 5.06 41.96 15.76 275.74 +179 17.65 5.52 0.00 6.53 38.21 15.36 296.96 +180 17.26 2.14 0.00 4.55 36.99 13.96 299.75 +181 19.67 3.82 0.00 3.86 35.87 14.76 278.86 +182 20.63 4.74 0.29 3.87 42.25 17.88 246.18 +183 17.02 4.12 0.79 2.79 68.01 20.71 248.38 +184 16.71 1.31 0.29 3.37 99.65 28.96 279.23 +185 16.92 1.03 0.00 3.08 72.51 27.95 263.98 +186 18.02 2.23 0.00 3.27 66.89 22.67 290.46 +187 19.22 1.74 0.00 4.25 52.97 18.71 291.26 +188 20.22 2.54 0.15 3.87 35.23 16.52 281.49 +189 19.32 4.44 0.36 3.31 58.51 17.00 252.48 +190 17.64 3.59 0.00 3.95 79.13 24.05 284.29 +191 19.62 2.74 0.11 2.68 62.57 27.72 277.48 +192 20.12 5.82 0.82 2.89 81.14 25.93 272.26 +193 19.53 5.81 0.56 2.29 68.22 23.75 274.72 +194 17.94 5.72 0.82 3.98 76.35 24.13 259.83 +195 16.83 3.52 0.14 3.59 74.90 26.43 248.06 +196 16.82 1.73 0.00 3.19 73.18 30.25 277.55 +197 17.22 2.52 0.00 4.69 60.42 21.25 287.18 +198 17.84 2.12 0.00 3.09 42.91 21.94 284.95 +199 19.32 3.63 0.00 3.57 54.57 21.46 281.75 +200 19.61 4.90 0.00 3.01 44.95 21.64 274.09 +201 18.92 5.22 0.00 5.37 54.99 20.63 276.02 +202 18.41 3.72 0.00 2.88 51.91 19.57 279.13 +203 18.02 3.80 0.04 1.90 68.83 22.47 279.13 +204 19.22 3.42 0.05 2.58 80.44 25.76 271.05 +205 19.12 4.73 0.37 2.47 62.29 22.28 258.30 +206 18.84 5.12 0.32 3.49 51.68 19.58 259.45 +207 18.11 3.44 0.13 2.16 68.42 20.75 248.87 +208 16.62 2.72 0.00 3.53 61.83 21.00 277.79 +209 17.34 2.12 0.00 3.17 47.85 19.78 278.04 +210 19.32 2.61 0.00 2.79 46.04 20.38 273.96 +211 20.54 4.63 0.00 4.59 38.47 21.04 259.63 +212 17.43 5.02 0.21 3.19 55.36 19.97 247.22 +213 17.55 4.67 0.00 3.49 67.35 22.65 271.59 +214 19.22 6.06 0.11 4.09 57.24 26.24 257.56 +215 18.23 5.58 0.00 3.67 60.85 27.13 267.05 +216 18.25 5.59 0.00 5.76 58.60 25.05 263.43 +217 16.52 2.76 0.00 4.61 62.32 22.09 270.68 +218 19.22 3.66 0.07 4.47 47.88 23.78 259.01 +219 20.44 5.08 0.03 3.81 55.81 22.89 259.87 +220 20.14 6.57 0.19 3.49 50.32 20.32 252.28 +221 19.12 8.31 0.58 2.82 56.20 23.29 249.06 +222 19.92 5.96 0.10 2.88 92.21 23.59 262.98 +223 19.22 4.97 0.00 3.59 55.52 22.59 253.62 +224 18.21 3.05 0.00 2.99 76.84 21.41 261.87 +225 19.12 4.85 0.06 2.79 64.53 20.39 247.85 +226 18.13 5.05 0.23 2.51 48.20 19.79 234.41 +227 16.62 4.09 1.27 3.09 91.48 27.91 227.74 +228 14.49 3.60 0.46 3.23 100.00 36.09 251.48 +229 13.53 1.28 0.00 4.87 62.22 28.72 247.44 +230 15.02 0.27 0.00 3.96 60.88 30.18 254.37 +231 16.45 2.18 0.00 5.67 60.70 24.75 253.27 +232 15.12 2.87 0.00 5.17 53.68 23.48 238.37 +233 10.81 -0.72 0.08 5.26 63.86 26.91 254.77 +234 14.02 -2.51 0.00 3.16 59.87 28.62 252.06 +235 16.53 0.57 0.00 3.89 52.18 21.79 250.67 +236 16.03 4.49 0.46 3.59 68.83 21.59 213.72 +237 13.72 2.48 0.91 3.27 83.10 32.36 167.84 +238 13.03 1.65 0.88 2.69 93.26 38.18 185.41 +239 13.23 0.75 0.71 2.95 99.87 33.66 242.61 +240 14.02 0.46 0.00 3.48 77.04 31.68 236.94 +241 15.34 1.46 0.00 4.77 65.05 24.16 241.47 +242 13.92 2.07 0.06 4.97 58.72 20.99 225.94 +243 13.14 1.49 0.11 4.76 59.76 25.54 214.67 +244 12.13 -0.09 0.19 4.45 67.72 27.04 220.19 +245 12.45 -1.09 0.00 3.93 58.00 25.23 251.37 +246 15.65 0.22 0.00 4.76 47.20 22.08 250.38 +247 15.95 2.01 0.00 5.26 45.22 21.87 246.77 +248 15.95 1.93 0.00 2.36 43.96 20.59 244.48 +249 17.55 2.99 0.00 2.68 43.76 21.49 242.69 +250 17.15 4.28 0.00 2.20 54.44 21.68 220.99 +251 16.42 3.28 0.52 2.89 63.52 25.53 220.64 +252 14.87 2.49 0.54 4.29 83.64 31.72 204.40 +253 7.42 2.18 0.98 3.48 100.00 32.16 103.24 +254 13.91 1.58 0.19 3.65 98.67 40.16 198.10 +255 12.14 -0.51 0.00 5.67 74.58 30.65 232.39 +256 11.32 -1.36 0.03 4.06 71.75 31.94 210.75 +257 14.15 0.47 0.00 3.89 75.69 29.58 223.98 +258 14.44 0.81 0.12 3.28 57.92 22.70 227.67 +259 14.76 1.59 0.00 4.96 62.82 23.15 226.09 +260 11.34 1.04 0.28 5.92 69.32 22.38 221.79 +261 13.35 -1.49 0.00 4.88 48.96 21.57 225.89 +262 16.36 -0.86 0.00 3.98 39.97 21.09 219.97 +263 15.65 3.61 0.13 6.45 48.14 19.79 217.76 +264 11.35 1.40 0.42 4.35 73.44 20.19 218.16 +265 11.74 -1.37 0.00 5.06 54.36 21.59 215.55 +266 8.12 -3.16 0.00 2.11 66.08 20.98 214.98 +267 10.04 -5.23 0.00 3.05 60.36 19.91 215.07 +268 10.03 -4.31 0.00 3.95 57.57 19.73 214.16 +269 10.80 -4.53 0.00 2.98 54.65 21.01 210.96 +270 13.46 -1.73 0.00 2.74 63.91 20.70 208.18 +271 12.75 -1.32 0.00 2.79 37.15 19.10 205.78 +272 13.65 -0.89 0.00 2.79 41.82 19.90 203.29 +273 13.54 0.11 0.00 3.21 42.68 19.81 201.67 +274 17.11 0.06 0.00 3.67 45.99 17.93 200.78 +275 13.11 0.65 0.00 4.06 36.62 18.66 206.22 +276 8.69 -3.86 0.00 2.26 64.50 19.66 207.12 +277 12.22 -2.16 0.00 3.48 41.53 23.92 203.82 +278 12.72 -0.94 0.00 3.97 43.17 19.29 204.13 +279 11.42 -1.62 0.00 3.81 44.65 19.97 197.13 +280 12.74 -2.44 0.00 2.00 59.12 21.09 198.91 +281 14.14 -0.15 0.00 3.30 44.47 23.03 194.12 +282 13.12 -0.93 0.00 1.99 43.10 20.47 194.34 +283 12.82 -1.26 0.00 3.69 41.91 18.91 193.52 +284 9.58 -4.35 0.00 2.29 61.89 20.66 188.93 +285 10.22 -3.84 0.00 3.53 59.59 21.57 189.33 +286 10.02 -2.44 0.00 4.36 58.57 23.34 160.09 +287 7.92 -1.27 0.00 4.37 69.32 27.72 167.62 +288 7.32 -3.63 0.71 4.58 72.61 28.51 175.41 +289 3.02 -6.93 0.70 6.34 86.42 22.77 161.80 +290 -1.71 -10.47 0.29 5.30 93.66 24.94 144.64 +291 -3.49 -10.98 0.00 6.93 79.88 37.19 155.32 +292 -1.08 -12.03 0.00 4.98 81.40 45.42 172.76 +293 3.63 -9.96 0.00 2.50 86.84 30.76 174.74 +294 5.32 -7.46 0.00 2.17 52.30 23.79 174.85 +295 5.32 -8.06 0.09 3.17 57.04 23.80 172.13 +296 4.24 -7.43 0.06 4.47 56.98 23.30 165.24 +297 -3.99 -13.34 0.00 2.88 70.43 23.60 140.86 +298 3.79 -13.79 0.00 5.04 70.83 15.26 166.54 +299 7.59 -7.58 0.23 3.36 25.34 14.01 164.45 +300 2.72 -7.56 0.45 3.14 70.59 14.58 139.93 +301 -5.90 -13.86 0.63 4.40 99.77 26.22 95.77 +302 -5.31 -16.45 0.00 2.68 97.96 62.30 154.31 +303 0.17 -14.47 0.00 4.09 97.11 44.25 156.05 +304 6.32 -7.28 0.00 2.96 64.15 27.95 154.43 +305 8.66 -5.19 0.00 3.96 47.00 16.84 155.34 +306 9.16 -4.49 0.00 2.87 37.03 17.27 148.87 +307 6.46 -3.96 0.00 5.19 34.29 16.06 144.26 +308 4.65 -7.06 0.00 4.06 59.17 18.76 142.19 +309 6.53 -6.36 0.00 3.97 81.60 26.95 140.67 +310 9.55 -4.29 0.00 3.27 80.96 26.06 141.38 +311 9.26 -2.19 0.00 5.86 45.76 20.65 138.17 +312 7.85 -1.61 0.00 6.43 55.01 22.23 135.87 +313 5.84 -4.05 0.00 8.03 60.44 26.46 135.99 +314 8.43 -5.07 0.00 3.89 56.19 21.78 134.69 +315 11.43 -3.19 0.00 3.55 40.17 13.30 136.07 +316 9.94 -2.56 0.00 3.16 26.44 12.49 133.59 +317 5.86 -3.17 0.08 3.86 59.38 15.67 112.77 +318 0.04 -12.64 1.22 2.08 100.00 35.36 95.14 +319 -6.10 -13.81 0.22 1.48 100.00 57.89 99.87 +320 -8.06 -19.19 0.03 1.69 98.79 58.61 130.73 +321 -10.46 -23.73 0.00 2.21 100.00 54.96 128.45 +322 -4.46 -23.15 0.00 3.29 100.00 32.30 129.60 +323 -2.54 -14.67 0.00 2.20 53.39 16.30 126.88 +324 -1.27 -12.49 0.00 2.77 51.66 27.55 123.59 +325 -1.64 -12.89 0.00 2.80 62.76 30.17 119.81 +326 0.95 -10.90 0.04 4.19 60.43 15.51 123.40 +327 -1.46 -10.36 0.00 4.16 42.39 18.86 110.00 +328 -5.60 -14.24 0.04 1.79 100.00 31.79 116.99 +329 -7.76 -15.29 0.58 3.36 100.00 42.30 99.88 +330 -6.27 -17.78 0.13 2.99 100.00 54.26 118.19 +331 -6.67 -17.25 0.00 4.37 85.10 26.45 116.35 +332 -5.40 -19.73 0.00 6.33 88.58 48.40 102.88 +333 0.54 -8.99 0.00 5.84 79.65 35.73 112.20 +334 3.42 -8.68 0.00 6.13 77.07 30.53 111.69 +335 3.96 -6.00 0.00 6.82 50.36 19.98 112.09 +336 -1.59 -12.59 0.10 4.88 52.15 17.90 89.59 +337 1.69 -12.87 0.00 5.11 73.93 38.81 105.97 +338 4.73 -6.31 0.00 4.86 70.35 32.69 103.77 +339 3.63 -6.50 0.00 5.85 54.56 21.50 104.38 +340 0.74 -8.45 0.00 4.34 87.39 31.83 100.72 +341 -0.77 -10.55 0.29 3.53 100.00 36.03 83.84 +342 -3.42 -12.07 0.39 1.99 100.00 49.81 64.59 +343 -5.88 -15.13 0.00 1.79 100.00 56.64 102.70 +344 -7.09 -16.40 0.00 3.97 95.94 49.86 95.71 +345 -1.31 -15.11 0.00 5.48 85.46 37.39 104.07 +346 1.74 -10.18 0.00 4.89 59.27 30.74 100.79 +347 3.95 -7.10 0.00 2.68 62.77 28.65 101.10 +348 0.59 -9.71 0.00 2.60 56.79 26.88 104.08 +349 3.22 -9.40 0.00 4.29 61.90 23.55 103.87 +350 3.07 -8.38 0.00 5.28 81.00 23.07 98.89 +351 6.03 -5.04 0.00 4.98 79.19 33.55 99.69 +352 8.73 -3.12 0.00 5.17 57.95 18.46 101.39 +353 4.72 -7.08 0.00 3.65 43.94 17.36 100.49 +354 0.11 -11.11 0.00 1.85 79.72 22.30 98.16 +355 -0.19 -11.97 0.00 3.18 79.87 38.47 100.29 +356 -0.97 -11.52 0.00 4.09 86.56 45.42 97.28 +357 0.24 -8.41 0.06 6.32 80.52 47.96 99.19 +358 -3.36 -11.92 0.11 5.28 93.06 35.04 99.37 +359 0.23 -12.02 0.00 5.19 76.27 22.31 100.27 +360 3.61 -5.19 0.00 9.60 59.22 18.22 97.70 +361 5.71 -5.56 0.00 5.88 87.32 32.98 99.19 +362 9.82 -3.42 0.00 3.89 77.26 30.36 100.09 +363 4.62 -5.91 0.00 3.10 55.35 27.79 95.48 +364 2.02 -9.06 0.00 2.69 82.81 22.67 104.86 +365 6.54 -4.84 0.00 4.98 36.53 8.57 103.98 +366 0.84 -7.48 0.00 3.06 73.51 10.85 102.39 diff --git a/tests/example/Input/data_weather_gridmet/weath.1981 b/tests/example/Input/data_weather_gridmet/weath.1981 new file mode 100644 index 000000000..1184a629f --- /dev/null +++ b/tests/example/Input/data_weather_gridmet/weath.1981 @@ -0,0 +1,367 @@ +# weather for site gridmet example at -105.58 / 39.59 year = 1981 +# DOY, Tmax_C, Tmin_C, PPT_cm, sfcWind_mPERs, hursmax_pct, hursmin_pct, rsds_WPERm2 +1 2.71 -10.11 0.00 2.36 99.40 28.41 99.27 +2 2.80 -8.72 0.00 3.97 63.93 25.73 96.38 +3 1.79 -10.50 0.00 2.68 100.00 28.94 98.07 +4 0.12 -9.22 0.09 3.06 95.93 36.06 81.88 +5 -1.52 -11.45 0.06 2.40 100.00 47.65 69.44 +6 -1.72 -15.41 0.00 3.29 100.00 33.13 102.36 +7 4.18 -10.14 0.00 2.77 58.44 20.25 101.18 +8 3.50 -10.83 0.00 3.17 46.22 15.02 102.48 +9 0.96 -11.21 0.00 2.27 45.06 14.44 102.26 +10 1.08 -11.45 0.00 2.60 66.43 27.06 103.27 +11 -0.93 -13.84 0.00 2.37 87.25 22.67 106.95 +12 3.21 -12.43 0.00 3.79 45.98 7.46 107.78 +13 1.79 -11.71 0.00 3.70 21.38 9.27 108.26 +14 1.66 -12.33 0.00 2.38 58.78 13.32 107.17 +15 -2.03 -14.32 0.04 2.59 59.25 20.52 94.57 +16 -7.31 -17.90 0.26 2.08 100.00 32.22 102.40 +17 -3.03 -16.90 0.00 2.56 100.00 67.60 109.84 +18 -3.59 -15.30 0.00 3.39 100.00 60.90 110.76 +19 -2.62 -15.88 0.09 3.79 100.00 40.86 112.96 +20 -3.71 -15.29 0.00 4.59 100.00 35.51 114.25 +21 2.31 -14.50 0.00 3.88 66.32 13.46 113.68 +22 5.21 -9.73 0.00 3.19 60.01 21.41 116.26 +23 6.12 -8.21 0.00 3.19 43.76 16.41 118.05 +24 1.79 -11.01 0.05 5.06 49.84 12.65 118.08 +25 -6.10 -16.03 0.00 6.16 88.70 24.23 120.18 +26 -7.92 -19.11 0.00 4.55 92.77 34.91 121.38 +27 -3.92 -16.81 0.03 4.81 98.18 40.73 121.27 +28 1.26 -13.13 0.00 2.96 100.00 44.68 115.35 +29 -3.00 -13.83 0.00 3.76 99.95 39.29 116.40 +30 -1.89 -14.12 0.10 5.93 87.97 28.81 115.10 +31 -8.68 -17.63 0.20 4.58 100.00 28.04 96.87 +32 -13.04 -20.77 0.17 6.00 95.34 46.09 87.84 +33 -7.56 -21.39 0.00 7.31 80.95 54.69 134.00 +34 -3.54 -16.12 0.00 3.39 65.71 33.80 134.91 +35 -4.49 -16.24 0.00 2.66 74.25 36.71 126.66 +36 -5.84 -17.14 0.00 2.85 76.31 41.83 139.51 +37 -5.05 -15.16 0.00 5.88 70.17 35.64 111.03 +38 -8.17 -16.91 0.00 6.58 82.99 35.31 128.49 +39 -5.47 -14.79 0.00 8.37 68.67 40.39 135.44 +40 -4.64 -15.04 0.39 4.64 94.93 38.55 124.58 +41 -11.12 -25.86 1.30 4.96 100.00 52.03 88.37 +42 -5.66 -25.05 0.00 7.00 74.05 43.67 147.92 +43 -4.45 -15.90 0.00 4.48 89.49 45.32 147.60 +44 -0.01 -13.34 0.00 3.59 83.48 41.91 149.31 +45 4.78 -8.85 0.00 4.95 66.64 14.31 144.39 +46 0.46 -7.74 0.00 4.17 62.75 16.74 152.01 +47 4.16 -9.62 0.00 3.58 60.12 31.71 152.02 +48 4.17 -7.16 0.00 4.19 71.50 32.84 132.05 +49 3.06 -9.20 0.00 5.33 75.22 17.86 156.21 +50 6.17 -6.46 0.00 4.67 64.85 21.93 155.92 +51 3.36 -8.75 0.69 5.24 55.12 25.67 139.03 +52 -6.64 -14.23 0.43 5.81 100.00 30.51 142.81 +53 -2.58 -14.81 0.00 6.24 91.24 47.18 164.22 +54 4.45 -9.55 0.00 3.47 67.27 18.01 166.29 +55 5.78 -8.06 0.00 3.35 47.32 17.44 170.22 +56 5.09 -7.74 0.00 3.58 46.65 13.61 170.02 +57 1.29 -7.55 0.00 6.06 51.88 15.62 135.31 +58 -0.46 -11.36 0.00 3.77 72.94 21.88 174.20 +59 2.28 -11.03 0.07 2.28 51.48 23.03 174.92 +60 0.91 -11.13 0.19 2.50 95.29 20.65 139.69 +61 2.09 -12.22 0.68 4.56 100.00 34.70 160.63 +62 -1.88 -12.42 0.94 4.51 100.00 32.90 152.01 +63 -5.22 -17.51 0.52 6.19 100.00 51.75 144.42 +64 -1.42 -17.72 0.19 2.88 92.51 27.07 187.29 +65 -2.31 -14.30 0.44 2.68 99.30 47.09 164.73 +66 -6.00 -15.13 0.69 3.48 100.00 47.11 160.08 +67 -3.53 -17.33 0.06 2.98 100.00 53.74 180.90 +68 -2.30 -15.61 0.06 2.39 97.84 44.57 189.66 +69 -2.11 -13.59 0.14 2.18 99.48 41.05 145.58 +70 -1.69 -14.19 0.28 3.08 97.91 45.16 183.90 +71 0.09 -13.23 0.47 2.98 100.00 39.80 179.68 +72 -2.18 -12.43 0.00 2.89 99.96 39.08 100.21 +73 0.80 -12.32 0.00 3.10 87.66 32.86 188.64 +74 1.81 -11.33 0.00 3.46 84.93 27.85 195.66 +75 3.89 -10.62 0.07 4.18 75.85 24.74 196.58 +76 -2.67 -11.80 0.03 6.29 83.56 21.59 158.65 +77 -3.93 -13.14 0.09 1.98 100.00 26.57 132.58 +78 0.67 -15.48 0.11 2.84 96.26 42.40 189.99 +79 1.82 -9.71 0.33 4.42 85.73 38.07 161.22 +80 -3.07 -12.38 0.31 7.49 100.00 33.41 162.84 +81 -1.09 -13.82 0.00 2.55 100.00 46.44 191.11 +82 0.33 -12.18 0.73 3.59 86.62 36.45 174.08 +83 1.20 -10.39 0.39 3.18 100.00 34.45 190.50 +84 3.58 -13.02 0.00 3.58 99.33 20.41 209.21 +85 6.22 -7.21 0.00 4.86 58.50 23.13 185.08 +86 2.53 -7.39 0.54 7.73 69.41 19.30 174.26 +87 -1.39 -13.99 1.05 5.21 99.27 23.37 194.00 +88 0.68 -15.24 0.06 5.88 100.00 39.26 213.70 +89 2.39 -11.51 0.05 7.05 89.60 20.81 202.53 +90 -3.64 -15.33 0.00 9.05 74.51 22.66 230.29 +91 4.96 -10.70 0.04 5.53 54.51 18.32 216.65 +92 5.89 -7.32 0.00 5.46 56.81 21.06 199.70 +93 -1.01 -10.83 1.01 4.27 89.12 23.51 189.98 +94 -6.35 -13.61 0.06 6.36 82.00 27.06 142.55 +95 1.94 -14.84 0.00 4.73 59.01 23.72 224.52 +96 4.66 -7.33 0.13 5.73 43.75 18.22 219.39 +97 4.77 -6.11 0.10 5.80 54.54 23.92 217.14 +98 0.45 -7.74 0.46 4.22 96.84 23.50 200.95 +99 7.06 -7.11 0.00 5.13 64.73 29.66 225.62 +100 8.56 -4.09 0.00 4.75 46.88 19.02 227.82 +101 8.47 -3.91 0.00 4.35 47.70 17.82 208.10 +102 8.46 -4.57 0.00 4.26 47.33 21.16 228.82 +103 6.47 -4.98 0.00 2.59 52.39 21.87 171.19 +104 8.07 -5.86 0.00 4.49 76.00 27.21 229.73 +105 9.37 -3.43 0.00 3.86 74.99 27.53 202.21 +106 9.77 -4.28 0.00 5.37 70.52 22.97 234.64 +107 12.39 -3.08 0.10 3.09 63.27 19.72 232.21 +108 9.88 -2.51 0.33 3.09 52.22 18.52 225.24 +109 6.06 -3.78 1.57 3.30 98.78 30.78 191.72 +110 7.55 -6.53 0.43 3.26 99.42 33.03 231.77 +111 6.46 -5.70 0.11 5.23 67.16 27.71 221.69 +112 2.76 -6.61 0.03 4.47 71.88 30.52 216.14 +113 8.97 -6.22 0.00 2.49 62.37 28.61 242.44 +114 11.07 -3.70 0.00 2.76 61.10 25.22 244.45 +115 14.18 -1.69 0.00 3.08 56.01 21.67 236.71 +116 13.17 -1.22 0.00 4.16 49.86 17.72 247.52 +117 11.19 -2.78 0.00 2.40 45.56 15.55 203.64 +118 10.37 -1.64 0.00 3.07 73.38 22.13 250.81 +119 12.15 -2.33 0.00 3.66 53.49 26.69 249.02 +120 9.79 -1.47 0.00 3.09 57.29 25.34 241.29 +121 10.19 -2.11 0.12 3.00 68.93 36.51 269.34 +122 10.42 1.10 0.48 4.28 71.10 32.29 256.94 +123 7.14 -0.41 0.97 3.04 90.06 31.12 251.35 +124 4.28 -5.93 0.74 2.69 70.50 40.10 284.31 +125 6.99 -6.50 0.71 3.89 98.80 35.46 264.57 +126 5.10 -2.72 0.36 4.26 91.97 31.65 277.31 +127 1.89 -5.56 0.17 4.93 57.80 26.46 273.04 +128 0.78 -8.58 0.68 4.45 54.75 24.26 254.74 +129 -2.40 -10.72 0.19 4.49 89.06 28.69 254.58 +130 4.58 -8.73 0.00 2.76 75.16 33.65 269.86 +131 5.69 -3.60 0.33 5.66 68.49 27.49 245.06 +132 3.91 -7.36 0.35 4.13 62.05 23.05 210.90 +133 0.67 -8.39 0.17 2.98 92.24 35.15 289.80 +134 8.57 -6.26 0.00 3.24 74.43 30.14 289.96 +135 8.08 -1.34 0.20 5.46 53.43 23.06 262.64 +136 3.46 -3.91 1.08 2.18 76.04 23.76 235.65 +137 0.77 -5.81 0.93 4.89 88.13 44.95 229.58 +138 2.44 -5.74 0.17 4.61 95.88 58.59 274.83 +139 9.33 -5.38 0.04 3.10 94.52 35.34 288.01 +140 8.09 -1.56 0.05 6.45 64.47 26.74 231.83 +141 1.78 -2.64 0.08 7.65 57.05 30.90 254.72 +142 4.88 -4.41 0.24 5.46 71.96 39.13 271.60 +143 7.38 -3.71 0.30 3.10 82.65 38.88 294.47 +144 8.99 -2.42 0.16 2.16 77.53 35.20 288.30 +145 8.80 -1.28 0.09 2.67 64.84 32.16 246.32 +146 10.67 -0.88 0.09 2.76 71.04 36.86 243.96 +147 10.79 -0.11 0.11 2.38 68.76 35.04 280.64 +148 11.28 1.99 1.35 2.40 85.08 32.70 275.50 +149 7.22 -0.72 1.72 3.19 93.26 36.62 239.54 +150 9.79 -2.12 0.60 2.60 97.28 45.37 284.08 +151 11.54 -0.34 0.10 2.96 69.63 34.76 278.25 +152 11.94 -1.23 0.17 2.27 61.72 24.77 274.53 +153 13.32 -1.32 0.71 2.31 50.13 21.61 289.14 +154 10.66 1.24 1.06 2.40 76.84 21.71 247.46 +155 12.35 -1.34 0.18 2.11 76.30 33.06 290.10 +156 13.23 -1.14 0.00 2.61 58.07 33.66 284.34 +157 16.55 0.67 0.00 2.87 69.02 24.22 294.53 +158 17.45 4.06 0.00 5.45 44.38 20.15 284.42 +159 17.55 4.98 0.00 6.25 38.84 19.56 288.84 +160 17.65 4.68 0.00 5.13 41.39 19.38 287.11 +161 18.01 3.67 0.00 2.74 50.18 19.84 277.25 +162 17.54 4.54 0.05 3.32 42.55 17.99 295.17 +163 17.45 3.54 0.00 5.04 51.53 14.47 299.03 +164 15.52 2.84 0.00 5.83 35.05 13.37 300.13 +165 8.59 -2.49 0.00 4.52 41.40 13.65 268.69 +166 5.92 -8.16 0.00 4.58 55.69 17.49 303.28 +167 13.02 -5.44 0.00 3.96 45.15 16.65 303.45 +168 15.03 1.20 0.00 6.43 27.61 14.05 302.16 +169 13.77 0.45 0.00 2.89 33.57 13.75 269.39 +170 15.94 1.84 0.00 4.85 33.12 18.28 295.64 +171 18.14 3.65 0.00 4.55 38.55 17.99 294.64 +172 18.03 5.69 0.00 4.62 38.14 16.91 295.73 +173 18.24 3.76 0.05 2.87 46.05 15.63 300.14 +174 20.03 4.96 0.00 3.67 42.61 14.62 295.35 +175 20.12 6.33 0.04 2.14 34.94 12.95 286.70 +176 19.42 4.62 0.25 3.50 53.02 17.16 288.46 +177 19.85 6.05 0.30 3.16 58.47 17.69 274.10 +178 18.65 5.87 0.20 2.69 57.95 17.11 258.35 +179 16.73 5.86 0.40 3.07 61.42 20.16 260.67 +180 15.66 3.45 0.63 3.09 67.39 22.11 272.71 +181 17.02 4.14 0.29 3.00 72.93 26.67 282.81 +182 14.93 4.83 1.03 3.21 82.93 31.89 230.63 +183 12.16 4.26 0.56 3.30 95.31 41.64 164.31 +184 15.40 3.15 0.21 2.38 96.70 38.56 273.17 +185 14.22 3.56 0.19 3.90 71.49 32.28 271.26 +186 17.34 3.56 0.00 2.98 65.52 26.45 290.34 +187 19.82 4.25 0.03 2.58 49.12 21.50 289.95 +188 19.82 7.36 0.64 2.27 49.36 21.42 273.76 +189 16.01 7.33 0.23 4.12 63.01 24.37 258.81 +190 17.52 4.24 0.38 3.30 58.54 31.18 263.24 +191 17.90 4.65 0.20 2.67 83.86 31.36 268.72 +192 18.21 4.86 0.45 2.70 65.00 33.76 246.60 +193 17.03 6.16 0.51 2.79 85.61 33.06 264.85 +194 15.62 5.79 0.25 2.66 80.89 32.45 238.31 +195 16.22 4.47 0.03 3.30 69.47 34.95 256.73 +196 17.52 4.55 0.71 2.78 67.92 33.73 259.61 +197 16.12 4.54 0.26 2.88 81.08 30.41 243.01 +198 14.41 3.46 0.38 1.69 88.00 31.17 206.70 +199 14.91 3.16 0.21 1.99 87.18 35.66 264.15 +200 17.61 4.13 0.00 4.56 67.93 24.40 280.85 +201 18.62 4.25 0.00 3.37 35.88 19.53 282.27 +202 18.91 5.98 0.04 4.69 37.81 16.47 282.27 +203 19.11 6.07 0.07 3.36 38.44 17.24 269.44 +204 18.52 5.54 0.11 2.79 45.27 19.83 265.98 +205 17.21 5.84 0.15 3.10 48.71 21.30 262.27 +206 15.73 4.06 0.40 3.08 72.85 25.06 224.74 +207 13.27 2.75 0.55 3.29 94.34 30.15 208.82 +208 14.99 2.07 0.08 2.67 90.04 31.38 272.19 +209 15.82 1.62 0.03 2.55 74.03 24.60 276.24 +210 17.11 3.54 0.00 2.90 48.52 20.62 273.03 +211 18.63 4.35 0.12 2.89 43.69 21.31 268.03 +212 17.14 5.04 0.00 2.79 50.79 20.42 265.00 +213 16.52 5.64 0.09 2.59 67.78 27.21 235.40 +214 17.12 4.44 0.42 3.10 68.77 30.89 258.36 +215 18.02 5.06 0.05 3.40 84.09 31.29 258.23 +216 18.82 5.95 0.00 2.49 75.82 32.40 263.85 +217 18.39 5.75 0.07 3.10 45.12 23.01 263.51 +218 16.59 5.43 0.00 3.30 73.99 26.38 267.45 +219 14.02 1.77 0.00 2.70 99.49 30.40 264.69 +220 13.92 0.75 0.32 2.29 83.75 36.73 257.39 +221 10.71 1.96 0.76 3.17 99.42 36.51 207.48 +222 10.60 -1.06 0.43 2.28 100.00 44.96 230.30 +223 9.52 -1.56 0.66 2.60 100.00 45.78 223.21 +224 8.62 2.71 0.70 2.30 100.00 57.54 155.38 +225 12.52 0.61 0.67 3.27 100.00 43.33 248.38 +226 14.00 1.27 0.40 2.99 87.54 40.04 248.38 +227 13.62 3.04 0.71 1.88 96.87 39.69 216.97 +228 12.12 2.54 0.81 2.80 93.67 41.26 223.09 +229 12.62 0.35 0.22 1.80 100.00 44.36 245.55 +230 13.52 1.33 0.09 2.98 98.48 34.51 254.96 +231 15.12 1.68 0.00 2.79 64.88 28.98 254.25 +232 16.41 2.45 0.00 2.08 65.79 29.94 245.37 +233 17.03 5.22 0.72 2.48 72.96 29.04 242.97 +234 13.52 4.15 0.35 3.28 83.70 28.14 228.13 +235 15.12 1.94 0.14 2.58 64.86 34.77 238.16 +236 15.05 3.84 0.07 3.09 60.23 28.91 215.46 +237 15.43 3.67 0.26 3.19 72.84 30.83 225.18 +238 15.21 4.06 0.14 2.50 58.54 30.16 237.15 +239 13.60 1.84 0.11 2.20 83.91 33.31 226.52 +240 14.92 1.93 0.04 2.47 90.24 35.00 232.30 +241 15.33 4.05 0.07 3.87 62.04 32.97 225.34 +242 15.64 3.97 0.18 3.97 69.78 32.83 220.34 +243 14.55 4.41 0.50 2.38 79.48 29.99 204.64 +244 13.85 2.20 0.36 3.45 79.26 28.72 233.71 +245 16.25 2.42 0.30 3.08 54.71 30.55 229.00 +246 13.09 3.32 0.30 2.98 81.12 25.01 207.71 +247 15.66 1.67 0.00 3.48 81.41 29.08 239.59 +248 13.34 3.38 0.27 3.19 68.48 27.40 193.89 +249 10.30 0.60 0.56 3.59 82.05 34.70 190.96 +250 6.19 0.61 0.60 1.68 100.00 46.54 115.70 +251 13.15 -1.26 0.55 2.29 92.15 39.61 233.69 +252 12.87 0.50 0.43 2.47 86.03 32.77 224.97 +253 11.06 1.71 0.33 2.90 79.17 33.11 199.91 +254 11.85 0.22 0.00 3.48 72.68 33.90 205.47 +255 12.46 0.51 0.00 2.59 65.27 34.48 218.19 +256 13.56 2.21 0.05 3.20 63.58 29.17 226.28 +257 13.56 2.48 0.09 1.81 50.65 25.10 221.61 +258 12.65 0.67 0.04 2.92 75.25 24.31 219.34 +259 10.62 -1.79 0.00 2.20 86.64 34.96 222.93 +260 10.74 -3.01 0.00 2.66 85.87 24.16 227.26 +261 14.37 -2.12 0.00 2.89 37.70 14.85 226.09 +262 13.76 0.51 0.00 4.59 28.11 14.65 221.87 +263 14.05 1.81 0.00 3.87 48.56 17.09 209.27 +264 12.55 1.42 0.00 4.17 60.58 25.39 197.06 +265 14.46 1.91 0.00 4.30 62.26 27.67 205.31 +266 12.15 0.89 0.05 2.90 57.56 25.92 159.31 +267 11.46 0.41 0.10 2.29 69.77 27.87 194.24 +268 10.77 -0.71 0.00 5.86 74.36 25.60 200.77 +269 9.34 -2.72 0.00 4.43 67.18 17.80 209.88 +270 12.17 -2.57 0.00 3.78 44.48 20.66 205.17 +271 13.76 0.89 0.00 4.66 41.26 20.45 202.99 +272 13.36 1.41 0.00 4.86 56.62 20.56 191.94 +273 10.95 1.18 0.00 3.69 56.65 22.92 177.60 +274 10.93 -1.65 0.00 2.58 88.97 29.42 202.83 +275 10.94 0.84 0.07 3.86 75.87 34.97 171.22 +276 5.63 -1.54 0.06 5.06 82.78 46.24 173.66 +277 8.11 -3.80 0.14 4.16 93.87 45.23 180.53 +278 5.82 -2.13 0.09 4.53 88.47 33.46 197.41 +279 8.82 -3.73 0.00 3.08 85.04 35.92 199.02 +280 9.62 -3.35 0.00 3.76 71.56 30.82 197.83 +281 7.04 -1.72 0.03 5.35 69.91 31.12 153.41 +282 4.53 -2.34 0.00 2.91 84.56 34.94 185.94 +283 7.94 -5.41 0.00 4.06 79.67 40.82 188.42 +284 8.65 -0.55 0.03 7.53 68.11 31.09 182.08 +285 5.02 -4.22 0.00 5.64 80.35 29.83 159.39 +286 4.22 -1.57 0.19 3.93 72.08 37.25 116.29 +287 4.34 -3.67 0.83 3.44 99.30 36.34 165.91 +288 -0.75 -4.53 1.72 4.15 100.00 36.34 127.80 +289 -1.25 -8.33 0.25 4.86 100.00 54.53 134.67 +290 0.13 -9.21 0.00 5.08 90.87 58.46 168.39 +291 3.81 -8.63 0.00 3.06 96.17 34.70 178.12 +292 6.82 -5.76 0.00 4.27 56.71 18.44 179.84 +293 6.44 -5.14 0.00 3.77 34.23 17.93 176.62 +294 3.12 -7.19 0.00 2.07 77.81 20.68 171.62 +295 0.69 -8.19 0.00 2.73 74.43 28.92 170.22 +296 2.99 -8.69 0.00 3.83 74.00 32.52 167.94 +297 -0.27 -8.17 0.60 3.15 76.89 31.63 131.26 +298 -4.56 -11.06 0.10 3.26 100.00 45.65 163.11 +299 8.01 -8.55 0.03 4.15 87.27 24.54 160.04 +300 7.73 -4.05 0.00 4.14 48.27 24.46 153.13 +301 6.53 -4.16 0.00 4.84 56.53 26.50 143.11 +302 4.22 -3.26 0.32 7.64 76.47 34.03 136.53 +303 -4.16 -9.43 0.85 3.70 94.71 34.62 133.34 +304 -3.39 -11.28 0.43 3.79 100.00 65.71 155.62 +305 3.20 -9.87 0.00 5.89 91.13 39.28 148.56 +306 3.74 -6.89 0.00 4.90 88.57 39.67 144.27 +307 5.79 -6.79 0.00 2.76 79.11 30.82 146.34 +308 7.49 -5.79 0.00 3.47 72.67 19.40 145.85 +309 5.39 -6.66 0.00 2.26 48.95 19.46 141.06 +310 7.00 -4.63 0.00 3.25 56.71 25.95 125.78 +311 4.31 -4.79 0.49 3.89 81.26 28.43 120.71 +312 0.40 -7.55 0.08 2.80 95.14 33.01 106.21 +313 1.48 -11.47 0.00 3.07 79.63 39.89 138.46 +314 3.53 -8.59 0.00 2.10 71.00 24.38 138.38 +315 3.41 -9.29 0.00 2.06 54.09 22.47 135.46 +316 5.49 -8.01 0.00 3.47 51.50 23.15 132.78 +317 7.30 -5.58 0.00 3.97 45.59 24.05 127.58 +318 6.81 -3.97 0.00 5.83 62.68 26.15 126.58 +319 5.10 -5.02 0.00 5.12 70.34 29.19 118.54 +320 8.80 -3.75 0.00 3.46 73.04 29.63 124.40 +321 9.71 -3.67 0.00 4.58 52.09 14.07 125.89 +322 -0.19 -11.46 0.28 7.57 85.61 13.18 92.24 +323 -5.52 -14.99 0.00 4.06 90.85 37.40 122.49 +324 2.01 -13.99 0.00 3.94 81.89 21.70 121.30 +325 2.08 -10.66 0.00 5.04 79.01 24.84 115.97 +326 2.90 -7.77 0.00 5.96 76.56 30.12 115.27 +327 2.36 -8.96 0.51 5.11 97.95 32.26 104.27 +328 6.48 -5.19 0.00 5.93 73.26 21.48 117.69 +329 2.01 -11.26 0.76 4.56 54.85 20.99 105.10 +330 -6.43 -16.56 0.11 2.46 100.00 26.82 115.50 +331 -3.73 -14.84 0.18 3.04 90.12 42.00 110.72 +332 -3.62 -13.81 0.08 2.96 92.94 49.85 110.61 +333 -1.90 -12.81 0.77 2.45 97.16 54.14 95.37 +334 -8.95 -15.61 0.07 8.73 100.00 43.87 108.60 +335 -7.99 -18.67 0.07 7.93 77.06 65.76 105.67 +336 -3.66 -13.83 0.00 8.99 70.78 61.10 106.37 +337 -3.18 -9.69 0.00 6.40 75.58 54.21 107.38 +338 1.07 -9.91 0.00 3.66 62.63 28.75 106.67 +339 1.22 -6.58 0.00 4.97 48.77 30.35 105.77 +340 4.64 -5.28 0.00 5.36 50.21 26.46 104.18 +341 5.05 -4.66 0.00 3.46 46.66 23.10 102.77 +342 4.63 -5.48 0.00 3.56 52.09 25.35 102.39 +343 7.11 -4.09 0.00 4.38 43.30 16.42 100.27 +344 4.74 -4.84 0.00 4.39 36.70 19.85 99.09 +345 -0.63 -6.98 0.36 3.28 77.00 25.27 70.58 +346 -1.58 -9.41 0.00 3.23 81.93 33.78 102.77 +347 -3.79 -11.57 0.28 3.46 81.31 34.75 93.12 +348 -3.21 -12.90 0.14 6.13 97.40 54.06 90.01 +349 -1.79 -10.58 0.34 6.80 80.08 52.20 96.26 +350 -5.27 -13.96 0.25 6.03 90.71 49.65 97.05 +351 -10.99 -17.07 0.25 3.65 94.98 53.46 103.87 +352 -2.92 -15.68 0.00 4.48 67.16 39.11 101.39 +353 -0.68 -8.41 0.03 5.23 69.96 33.94 86.34 +354 0.34 -3.94 0.31 8.60 79.44 58.12 88.88 +355 -3.50 -10.79 1.35 2.56 98.55 55.58 46.35 +356 -10.31 -17.88 0.87 2.78 99.91 55.28 82.27 +357 -14.81 -23.36 0.00 3.86 80.44 49.50 103.48 +358 -10.79 -23.39 0.00 4.66 68.63 38.01 104.29 +359 -9.27 -17.27 0.23 4.55 76.66 44.12 97.88 +360 -13.79 -20.56 0.18 4.66 87.05 51.14 101.67 +361 -10.05 -19.28 1.43 2.96 99.58 59.20 68.52 +362 -13.88 -22.27 0.00 4.23 80.12 49.11 93.80 +363 -7.10 -20.25 0.00 5.12 73.70 60.02 103.29 +364 -3.16 -9.97 0.65 5.44 90.23 60.83 91.79 +365 -6.69 -12.42 0.57 3.72 90.42 53.39 95.28 diff --git a/tests/example/Input/data_weather_maca/weath.1980 b/tests/example/Input/data_weather_maca/weath.1980 new file mode 100644 index 000000000..19a661b06 --- /dev/null +++ b/tests/example/Input/data_weather_maca/weath.1980 @@ -0,0 +1,368 @@ +# weather for site maca example at -105.58 / 39.59 year = 1980 +# DOY, Tmax_C, Tmin_C, PPT_cm, uas_mPERs, vas_mPERs, hursmax_pct, hursmin_pct, rsds_WPERm2 +1 -0.01 -11.99 0.00 3.31 -0.85 83.82 33.27 107.19 +2 1.27 -10.98 0.00 4.20 0.16 87.07 33.74 103.02 +3 1.92 -8.39 0.00 4.43 -0.39 92.60 44.30 98.53 +4 -0.30 -12.19 0.00 3.55 -1.13 99.95 47.18 104.83 +5 2.59 -9.32 0.05 5.20 0.67 88.96 38.51 94.22 +6 2.36 -5.47 0.17 4.33 1.44 91.39 53.32 84.92 +7 1.31 -12.42 0.04 2.48 -2.06 95.81 50.51 101.75 +8 -1.29 -15.72 0.00 4.68 -1.43 73.09 46.72 100.37 +9 -2.03 -12.36 0.00 5.14 -1.80 71.72 34.23 100.46 +10 -7.37 -17.86 0.00 3.19 -3.01 56.63 37.96 101.56 +11 -2.26 -13.70 0.00 4.01 -2.66 87.06 32.75 98.21 +12 1.45 -8.76 0.04 3.86 -2.18 94.92 42.37 97.27 +13 -0.45 -11.69 0.00 2.20 1.27 71.93 51.41 88.14 +14 -1.86 -12.80 0.08 1.74 -2.15 78.19 48.84 99.31 +15 -4.31 -17.34 0.00 2.82 -0.40 80.55 32.28 110.65 +16 -3.82 -14.63 0.00 3.86 0.28 68.77 30.43 113.33 +17 -4.41 -13.59 0.00 3.84 0.27 71.64 24.64 114.64 +18 -4.50 -15.44 0.29 1.91 -1.95 99.88 36.41 112.11 +19 -5.80 -16.78 0.00 3.72 -1.73 94.95 33.64 114.54 +20 -2.88 -14.31 0.04 3.80 -2.31 71.88 37.54 118.84 +21 -3.49 -14.30 0.00 2.86 -3.47 55.24 13.55 128.65 +22 3.97 -13.27 0.00 4.47 -1.21 51.60 11.65 130.84 +23 5.25 -8.02 0.00 4.77 -0.21 50.84 10.07 123.71 +24 5.25 -7.08 0.00 4.30 -0.61 62.34 28.04 112.36 +25 2.88 -5.61 0.00 4.81 -0.06 70.15 43.75 96.92 +26 6.18 -3.81 0.29 4.29 1.89 99.07 49.79 99.92 +27 4.90 -7.22 0.25 3.99 0.66 94.92 43.74 111.12 +28 -1.29 -12.68 0.00 3.91 -0.54 99.94 38.58 134.32 +29 -2.04 -10.49 0.03 5.41 0.25 60.06 26.82 106.29 +30 -3.91 -14.06 0.00 4.54 0.59 43.69 22.55 110.88 +31 -2.46 -10.47 0.33 4.68 3.05 58.61 36.49 118.97 +32 -2.62 -23.24 0.68 2.88 -2.45 100.00 40.54 143.27 +33 -11.72 -29.23 0.00 4.43 -0.86 99.97 36.37 151.17 +34 -11.07 -17.25 0.37 2.03 0.38 99.65 46.57 115.73 +35 -11.53 -22.21 0.10 2.64 -2.40 93.27 52.70 131.28 +36 -13.15 -23.32 0.00 2.47 -4.48 96.67 46.23 130.91 +37 -9.55 -19.67 0.00 3.05 -2.56 75.23 44.16 135.24 +38 -3.85 -20.32 0.00 2.81 -0.25 88.25 29.08 157.53 +39 -6.22 -17.65 0.03 1.51 -0.45 75.62 48.17 116.59 +40 -8.05 -16.28 0.00 1.81 0.33 89.77 41.52 135.56 +41 -6.60 -17.63 0.00 2.52 -1.15 99.96 47.30 140.08 +42 -3.13 -16.05 0.00 3.27 -1.47 84.17 43.49 146.14 +43 0.76 -16.86 0.00 3.81 -1.37 75.50 33.91 158.94 +44 2.59 -9.04 0.00 5.89 -2.59 82.05 35.03 153.32 +45 2.25 -9.35 0.00 5.63 -3.66 65.35 33.35 147.54 +46 2.78 -8.99 0.17 7.78 -1.58 59.51 29.14 154.36 +47 1.04 -9.79 0.00 8.10 -2.28 48.27 23.34 147.47 +48 8.07 -6.99 0.10 10.05 -0.14 48.75 22.84 169.41 +49 5.85 -6.87 0.00 9.81 0.50 50.55 23.94 164.50 +50 5.74 -4.76 0.00 9.48 1.56 50.58 25.19 154.78 +51 4.82 -3.81 0.63 10.06 0.36 57.11 24.43 178.37 +52 7.51 -4.73 0.00 7.64 2.38 49.94 24.60 168.90 +53 8.68 -1.79 0.00 7.19 5.75 46.51 22.26 167.14 +54 8.41 -9.35 0.15 7.49 -1.00 26.91 14.34 161.69 +55 -0.12 -12.41 0.25 9.27 -0.28 43.16 18.88 165.76 +56 -2.23 -15.39 0.00 4.50 -4.24 44.79 20.82 173.74 +57 -3.58 -20.16 0.00 3.83 -1.24 44.38 21.69 182.45 +58 4.74 -9.91 0.10 4.71 -0.81 53.52 22.37 170.36 +59 9.52 -5.62 0.00 6.42 -1.04 60.10 20.80 178.84 +60 8.48 -6.21 0.00 5.58 -0.49 63.97 17.88 177.17 +61 7.43 -6.81 0.00 4.75 0.07 67.84 14.96 175.51 +62 7.62 -7.71 0.00 2.28 -0.41 65.15 24.34 192.41 +63 9.59 -8.10 0.00 2.98 1.50 56.65 24.18 203.34 +64 9.92 -5.46 0.00 4.07 1.32 61.91 20.73 198.57 +65 10.06 -3.88 0.03 4.62 1.03 67.65 22.95 170.20 +66 9.57 -2.67 0.00 3.20 0.54 99.08 27.12 192.33 +67 10.14 -5.28 0.00 2.84 4.40 99.97 21.35 199.85 +68 9.48 -6.87 0.00 5.18 1.40 51.56 21.11 199.73 +69 4.57 -10.47 0.00 3.41 1.40 50.78 19.70 212.08 +70 5.22 -9.16 0.06 3.58 -0.11 62.54 24.76 198.11 +71 7.39 -8.48 0.03 2.99 4.11 56.46 27.86 167.23 +72 6.48 -3.21 0.16 4.07 2.39 55.45 29.73 165.11 +73 3.12 -9.67 0.06 -0.63 -0.55 58.45 28.90 175.34 +74 3.92 -6.35 0.11 2.49 2.04 85.63 35.62 162.68 +75 6.94 -7.89 0.13 3.61 0.33 59.56 26.78 203.53 +76 7.35 -5.13 0.06 4.40 1.21 57.92 23.27 167.30 +77 5.98 -8.28 0.04 2.38 -0.82 61.29 30.03 193.87 +78 1.13 -9.60 1.06 -2.00 -3.47 67.48 33.88 174.78 +79 -0.92 -11.74 0.30 2.16 -4.93 71.89 34.30 194.58 +80 2.36 -10.42 0.00 4.93 -4.23 57.62 28.11 209.74 +81 2.44 -9.07 0.00 4.61 -5.36 44.03 23.28 187.04 +82 0.96 -10.67 0.05 3.76 -6.25 46.55 25.42 173.95 +83 0.87 -6.18 0.00 2.80 -6.42 67.41 33.55 180.27 +84 7.67 -5.31 0.00 2.45 -6.05 65.81 26.63 206.31 +85 10.43 -7.58 0.00 3.58 -2.47 56.89 17.48 229.14 +86 10.99 -6.15 0.00 3.00 -2.60 51.15 14.40 201.77 +87 7.31 -9.39 0.00 1.35 -0.07 47.07 23.82 211.64 +88 10.00 -7.26 0.00 2.97 -2.37 50.80 17.49 229.08 +89 11.87 -8.34 0.00 3.80 -1.68 34.97 11.90 232.16 +90 15.72 -4.49 0.00 4.62 -1.18 36.07 9.78 230.35 +91 14.84 0.13 0.00 4.75 -0.58 47.73 14.57 217.67 +92 11.92 0.12 0.06 3.88 1.93 70.53 22.95 229.55 +93 9.46 -8.27 0.00 3.96 -0.35 51.91 18.66 230.83 +94 7.31 -2.76 0.00 -0.75 1.57 57.73 17.58 171.16 +95 2.61 -4.19 0.09 -0.68 -3.81 96.20 47.36 172.95 +96 5.89 -8.29 0.06 3.28 0.50 83.58 35.84 216.06 +97 6.59 -3.89 0.00 3.96 2.03 67.42 34.88 178.43 +98 5.25 -6.96 0.00 3.60 0.24 60.27 30.46 217.21 +99 3.75 -9.55 0.00 2.08 -2.39 63.89 28.45 212.61 +100 2.20 -9.97 0.11 2.41 -0.07 57.22 29.54 207.41 +101 4.93 -12.22 0.03 2.73 -2.73 54.11 17.53 241.60 +102 12.76 -7.10 0.00 3.94 1.99 31.45 14.46 233.00 +103 12.77 2.66 0.00 5.72 4.04 38.16 12.33 191.67 +104 6.37 -2.56 0.53 4.41 -1.21 64.63 33.17 199.06 +105 5.24 -3.39 0.86 -4.18 2.20 99.97 44.78 113.21 +106 -0.03 -8.04 1.19 -3.90 0.86 79.25 61.79 143.25 +107 1.18 -12.90 0.24 -0.53 -3.43 93.69 41.35 247.94 +108 1.44 -7.06 0.04 2.05 -3.11 84.52 42.26 217.92 +109 2.82 -5.99 0.00 2.44 -3.06 98.89 42.97 216.47 +110 11.87 -5.29 0.00 4.67 -1.38 96.70 25.80 258.27 +111 12.48 -0.71 0.20 6.19 0.00 52.74 21.77 248.47 +112 11.66 -1.69 0.00 5.32 0.87 54.59 20.18 225.29 +113 10.68 0.69 0.00 6.53 3.01 40.86 20.16 248.25 +114 9.99 -4.63 0.00 4.86 1.71 53.65 20.29 222.17 +115 3.53 -7.79 0.35 3.84 -3.10 71.14 26.96 244.71 +116 5.89 -11.60 0.00 2.28 1.43 58.43 23.47 240.95 +117 7.28 0.30 0.00 3.62 1.10 59.27 29.57 230.00 +118 8.62 -7.46 0.00 3.12 -2.49 67.76 25.90 277.74 +119 12.04 -1.58 0.04 2.41 4.74 40.06 24.67 207.54 +120 11.78 0.86 0.00 4.67 1.65 70.42 26.82 225.43 +121 10.02 -4.20 0.00 4.07 -0.17 63.08 26.63 239.21 +122 10.41 -5.36 0.00 3.63 1.90 53.58 23.60 270.55 +123 11.01 -4.05 0.00 3.45 -1.37 44.76 21.55 294.79 +124 14.12 -1.29 0.06 -0.93 2.93 39.87 18.94 256.98 +125 13.92 1.50 0.00 2.53 -0.29 48.16 20.77 206.56 +126 6.96 -2.40 0.90 -1.36 1.75 71.83 36.54 202.81 +127 6.18 -1.60 1.09 2.62 -1.59 63.64 39.81 231.55 +128 2.42 -7.54 0.13 3.12 -3.19 99.97 29.86 248.95 +129 8.31 -4.07 0.00 4.71 -0.39 48.14 27.78 238.26 +130 11.59 -1.47 0.00 5.07 0.55 57.59 24.81 266.50 +131 11.22 -1.16 0.06 1.26 -0.59 50.53 25.91 240.28 +132 8.98 1.81 0.50 -2.48 3.09 60.43 28.09 200.77 +133 10.72 -1.81 0.32 3.12 1.66 54.90 26.28 292.55 +134 10.98 -1.62 0.11 -2.01 -4.19 62.71 26.74 255.87 +135 5.19 -4.44 0.83 -4.71 -0.41 76.75 33.35 221.26 +136 -0.24 -7.45 1.10 -5.54 1.90 61.54 46.22 183.91 +137 3.49 -4.55 0.42 -0.42 5.80 87.00 45.90 200.66 +138 12.26 -1.49 0.00 2.70 3.45 82.01 36.98 274.30 +139 12.82 1.88 0.00 3.40 1.84 77.13 35.10 236.68 +140 12.93 0.60 0.15 1.93 -1.68 69.41 34.83 275.95 +141 11.31 1.21 0.58 -1.47 0.22 83.30 39.40 247.80 +142 12.33 0.98 1.32 -1.46 3.27 77.58 43.03 241.73 +143 13.80 2.72 0.30 1.72 2.86 85.75 30.98 281.69 +144 15.13 1.30 0.48 1.66 2.80 60.95 26.98 289.54 +145 14.93 1.86 0.56 1.24 3.54 71.21 32.12 289.86 +146 14.90 1.45 0.44 1.67 2.34 87.28 28.96 297.24 +147 14.71 2.69 0.26 2.34 0.68 91.37 35.66 296.27 +148 15.06 4.52 0.11 1.63 -2.27 80.32 32.79 274.73 +149 14.16 3.79 1.27 0.80 -1.12 84.45 37.36 274.76 +150 10.48 1.03 0.86 2.85 -1.22 87.65 36.69 275.50 +151 17.50 2.38 0.00 3.14 3.13 71.77 23.68 292.94 +152 16.20 0.13 0.00 3.84 1.36 50.13 20.91 300.32 +153 16.60 1.67 0.00 3.82 2.63 47.36 16.35 297.57 +154 16.36 3.64 0.00 5.51 2.00 43.35 16.76 301.60 +155 15.14 -0.98 0.00 2.18 -1.84 52.70 20.27 302.33 +156 14.60 0.91 0.03 -2.25 -0.15 68.32 23.01 280.84 +157 14.77 4.14 0.17 -2.61 3.26 89.31 34.39 262.81 +158 20.08 4.20 0.00 2.32 4.23 50.53 19.17 297.52 +159 20.17 3.22 0.00 2.29 3.12 41.35 19.26 290.48 +160 21.53 5.79 0.00 4.09 4.60 33.51 14.49 302.90 +161 21.39 3.60 0.00 3.62 -0.72 37.76 13.34 305.32 +162 17.18 2.93 0.21 -2.25 1.78 48.73 17.70 288.03 +163 14.15 0.83 2.83 -3.75 -2.53 74.59 32.75 240.87 +164 13.75 -0.32 0.00 -0.72 -1.41 94.01 27.45 300.72 +165 16.82 -0.26 0.05 2.25 2.74 62.33 20.74 305.96 +166 21.31 3.94 0.00 3.21 2.37 40.07 17.50 302.26 +167 20.95 4.50 0.00 1.92 1.32 52.19 18.61 288.10 +168 20.96 4.27 0.00 2.51 0.97 47.27 16.48 298.06 +169 20.70 4.15 0.00 1.84 -2.85 44.04 17.24 298.61 +170 20.85 6.52 0.00 0.71 2.23 49.40 19.01 279.59 +171 20.04 5.98 0.19 -1.06 1.41 56.45 26.70 278.99 +172 20.42 7.57 0.00 0.72 3.27 68.80 25.17 289.00 +173 23.63 8.49 0.04 3.33 2.59 46.36 15.45 286.01 +174 23.20 7.17 0.00 4.08 2.05 43.16 13.81 286.75 +175 23.18 5.62 0.00 4.07 0.74 31.77 13.01 299.18 +176 22.75 5.27 0.00 4.01 -0.49 36.78 14.20 303.47 +177 21.91 4.24 0.00 2.97 -0.20 50.02 16.97 305.43 +178 21.85 7.13 0.00 4.25 4.13 27.25 13.36 303.61 +179 21.22 4.95 0.03 3.05 1.84 36.03 14.24 298.84 +180 20.55 6.31 0.17 1.28 2.71 50.10 19.91 273.64 +181 19.57 5.95 0.11 2.70 2.35 70.70 22.54 279.16 +182 20.55 7.11 0.08 3.26 -0.14 59.18 22.05 282.12 +183 21.25 6.87 0.00 2.91 -1.31 43.82 16.79 288.43 +184 22.50 4.83 0.00 1.61 1.12 43.45 16.70 294.69 +185 23.78 4.62 0.04 1.58 0.72 41.29 14.37 291.33 +186 25.39 7.57 0.00 2.09 1.74 43.98 15.42 292.71 +187 26.02 8.85 0.00 2.66 2.00 35.27 14.83 286.91 +188 25.60 8.96 0.12 2.78 0.91 39.13 16.60 277.59 +189 25.53 9.42 0.00 2.55 1.71 44.46 16.76 299.76 +190 25.30 9.14 0.08 1.96 2.78 38.10 18.23 295.12 +191 23.38 8.19 0.10 1.16 3.63 36.72 18.70 286.02 +192 22.54 10.08 0.31 3.44 4.82 48.48 20.68 281.61 +193 20.00 8.46 1.33 3.56 3.54 74.09 25.76 270.26 +194 18.57 8.33 0.72 2.63 0.61 75.44 31.67 271.64 +195 19.93 7.37 0.94 2.50 3.12 74.82 28.64 281.95 +196 18.03 7.29 1.14 2.38 3.39 84.72 31.82 250.04 +197 20.81 7.00 0.81 3.59 0.84 99.91 26.07 280.92 +198 21.91 5.81 0.00 5.34 0.79 42.11 14.58 291.94 +199 21.29 3.70 0.05 3.44 -1.45 37.95 14.87 291.79 +200 20.38 4.42 0.25 0.94 -0.89 48.25 18.92 279.13 +201 20.55 6.38 0.72 2.12 -1.01 47.84 21.37 266.10 +202 19.97 7.06 0.86 3.46 -0.19 46.50 23.25 252.43 +203 18.98 6.53 0.61 3.52 -0.74 55.31 25.50 263.29 +204 18.09 6.36 1.18 2.28 -1.22 75.10 39.47 263.61 +205 17.18 6.65 1.05 2.24 0.92 79.06 34.53 255.76 +206 16.73 5.70 0.66 2.38 3.54 92.04 32.52 255.20 +207 15.57 3.17 0.86 1.57 4.15 92.63 35.27 172.37 +208 13.60 3.82 1.07 2.21 3.46 99.98 49.06 212.20 +209 15.98 4.51 0.72 3.08 1.97 99.96 40.37 244.73 +210 17.08 3.17 0.00 3.45 0.51 91.65 34.52 251.73 +211 17.58 3.86 0.11 3.46 -0.13 76.21 32.65 249.90 +212 17.33 5.14 0.17 3.16 -1.42 83.64 35.76 238.46 +213 17.56 5.22 0.55 2.74 -1.66 99.95 37.30 262.74 +214 17.68 6.17 1.12 2.08 -0.09 95.89 38.87 229.10 +215 16.75 7.55 0.50 2.25 2.34 94.19 43.92 228.92 +216 17.67 7.19 0.46 2.66 2.75 99.95 36.81 225.18 +217 18.39 5.29 0.07 2.95 1.77 83.75 34.85 234.15 +218 19.07 4.74 0.18 3.19 0.75 77.60 32.33 255.75 +219 19.52 3.56 0.53 2.10 -1.19 70.06 28.54 265.60 +220 19.17 4.26 0.00 -1.47 -0.68 81.61 31.23 251.55 +221 18.62 5.77 0.31 0.13 2.83 99.94 35.45 266.45 +222 21.72 5.85 0.00 2.24 1.22 64.48 21.81 277.95 +223 23.11 4.63 0.00 2.91 -0.51 42.25 14.90 279.35 +224 22.79 5.22 0.00 2.56 -0.71 29.59 13.26 260.86 +225 21.51 8.10 0.00 -1.48 -0.52 50.93 19.65 235.06 +226 21.61 7.35 0.00 0.54 3.81 77.47 27.83 252.65 +227 22.36 7.29 0.09 2.93 1.33 59.84 22.50 265.12 +228 21.83 5.30 0.00 2.00 -0.12 63.47 23.75 256.03 +229 21.71 5.50 0.28 1.51 2.60 80.32 24.64 252.98 +230 21.95 7.36 0.07 2.83 2.53 64.62 25.93 255.10 +231 20.24 7.53 0.35 2.81 2.20 74.08 30.19 245.86 +232 19.96 7.37 0.83 3.54 1.89 73.67 30.39 260.17 +233 19.69 7.88 0.59 2.81 -0.32 72.34 32.92 238.86 +234 17.96 7.73 0.63 0.94 -2.85 93.64 37.41 235.31 +235 14.70 6.62 1.31 -1.99 -0.62 89.60 43.15 215.83 +236 18.89 6.36 0.77 1.93 -0.28 84.40 27.56 235.41 +237 19.56 3.48 0.04 3.71 0.27 57.03 23.88 247.45 +238 18.73 2.62 0.00 3.03 -1.47 61.25 23.28 241.69 +239 19.62 2.81 0.00 2.65 0.30 50.86 21.52 263.11 +240 20.03 3.22 0.00 3.34 -0.46 48.23 21.21 262.55 +241 19.16 7.30 0.00 3.78 4.16 39.22 20.85 236.62 +242 19.54 6.98 0.00 3.15 5.15 60.16 21.24 229.18 +243 18.10 3.86 0.00 3.78 4.44 52.90 22.89 242.80 +244 14.91 -0.23 0.09 2.45 -1.13 57.06 24.91 248.03 +245 14.49 0.20 0.21 -1.73 -1.18 61.37 30.32 241.79 +246 15.19 0.37 0.85 0.08 0.25 67.63 33.24 224.70 +247 15.23 4.20 2.02 0.36 -1.34 69.84 31.75 218.28 +248 15.68 2.91 0.00 7.02 -1.11 63.49 28.14 236.00 +249 14.37 -0.36 0.00 2.42 -1.64 68.15 30.65 189.86 +250 15.99 0.22 0.00 2.11 0.05 93.76 28.23 247.63 +251 19.10 3.57 0.00 3.02 3.25 58.91 24.86 244.03 +252 18.99 1.84 0.00 3.35 -1.14 50.05 24.31 246.89 +253 19.25 2.35 0.00 1.37 2.45 53.63 22.52 245.86 +254 19.59 5.91 0.00 2.30 3.38 56.80 24.20 227.63 +255 20.05 4.82 0.00 2.84 1.68 61.43 25.05 242.81 +256 19.91 5.21 0.06 3.09 2.17 57.56 24.19 240.92 +257 19.20 5.99 0.09 4.18 2.88 56.53 22.96 226.35 +258 15.44 0.90 0.35 2.32 0.71 66.47 28.35 203.15 +259 8.77 -4.05 0.27 1.52 -4.87 63.81 29.14 211.29 +260 12.44 -5.14 0.00 2.39 1.88 79.19 25.45 240.99 +261 12.74 0.52 0.00 3.24 2.87 67.88 28.35 193.59 +262 15.04 1.65 0.00 3.95 -1.60 76.94 26.72 231.36 +263 17.08 1.55 0.04 2.96 0.94 60.12 26.84 223.20 +264 17.24 3.69 0.00 3.97 0.20 62.36 26.62 205.51 +265 17.60 4.83 0.00 3.74 0.88 73.63 25.93 190.80 +266 13.82 3.49 0.24 3.82 0.00 84.67 33.21 198.75 +267 10.51 -3.05 0.00 0.84 -1.21 70.69 32.72 208.02 +268 11.87 -0.08 0.22 0.96 1.68 71.86 32.19 204.14 +269 14.00 0.50 0.06 1.69 2.20 98.21 31.31 211.54 +270 14.51 3.69 0.33 2.42 2.46 83.81 30.97 194.78 +271 16.67 3.76 0.10 3.12 3.10 69.11 27.08 208.06 +272 16.57 3.75 0.00 4.32 1.49 56.14 24.53 195.78 +273 13.47 -1.93 0.31 2.90 -0.87 78.28 27.43 158.74 +274 1.11 -10.34 1.34 -3.02 -4.31 77.82 38.39 203.05 +275 1.88 -10.70 0.00 2.17 2.36 76.76 34.25 200.86 +276 3.78 -3.82 0.57 2.57 5.59 78.20 34.05 131.38 +277 6.19 -1.97 0.26 3.98 5.50 99.95 50.16 165.34 +278 5.65 -6.05 0.10 5.47 -0.05 84.45 33.33 205.43 +279 5.92 -1.87 1.32 3.31 2.81 77.75 34.72 170.81 +280 6.79 -4.77 0.00 5.49 0.05 78.51 35.12 209.40 +281 10.24 -2.44 0.00 4.31 2.65 99.90 35.66 203.87 +282 10.51 -0.31 0.00 3.58 3.09 77.66 31.06 191.61 +283 10.50 0.22 0.00 5.56 1.15 68.42 28.16 199.32 +284 8.93 -0.02 0.52 3.10 5.04 99.88 30.35 125.12 +285 7.56 -4.97 0.17 9.97 2.09 82.29 30.10 187.20 +286 5.08 -6.74 0.04 7.44 0.18 64.87 32.80 169.79 +287 5.73 -2.59 1.02 5.01 0.28 99.93 38.47 150.71 +288 1.17 -12.14 0.34 2.51 -4.76 73.25 40.21 160.58 +289 -4.53 -14.71 0.06 3.24 -3.62 56.15 31.91 178.39 +290 0.64 -10.37 0.00 4.35 -0.95 66.80 34.62 132.68 +291 0.85 -6.05 0.00 4.39 -2.35 99.95 40.25 152.46 +292 2.45 -4.41 0.00 4.68 -1.49 99.95 44.11 135.46 +293 3.95 -4.45 0.00 4.31 -1.46 82.59 47.86 144.83 +294 5.13 -3.68 0.00 3.26 -0.92 79.62 47.52 145.86 +295 8.73 -4.12 0.00 3.99 -0.37 88.70 38.90 167.97 +296 7.66 -0.98 0.03 4.52 -2.29 89.73 31.08 170.80 +297 9.01 -3.16 0.00 5.70 -2.07 73.59 30.34 166.00 +298 9.11 -2.95 0.35 5.25 -0.92 74.36 33.64 144.77 +299 7.70 -1.85 0.53 8.05 0.55 60.61 31.34 154.72 +300 4.13 -8.76 0.04 1.58 -0.21 62.88 31.88 145.58 +301 -0.49 -11.01 0.53 2.15 0.44 74.56 31.20 171.71 +302 -2.82 -11.27 0.18 -1.44 4.15 50.51 32.61 114.87 +303 -2.54 -15.24 1.26 -4.61 -2.15 92.82 40.60 113.98 +304 -8.61 -20.93 0.28 2.51 -2.92 92.73 43.36 146.68 +305 -5.15 -17.46 0.00 2.59 -2.11 93.63 43.35 154.19 +306 -3.39 -13.66 0.05 2.51 -1.21 95.97 48.13 136.35 +307 2.16 -11.03 0.00 4.02 -2.36 92.15 48.80 151.39 +308 2.90 -8.20 0.00 3.65 -3.57 94.87 40.11 154.84 +309 6.76 -6.73 0.00 3.96 -0.98 99.93 35.19 155.93 +310 6.54 -2.24 0.03 4.92 1.10 99.94 45.35 125.77 +311 6.17 -3.40 0.15 5.70 -1.70 71.79 39.48 133.79 +312 1.96 -5.40 0.00 3.54 -1.30 71.78 38.24 130.67 +313 1.67 -5.04 0.05 4.51 -1.95 69.47 41.11 129.80 +314 1.59 -6.45 0.00 4.65 -2.34 69.20 36.66 122.47 +315 3.51 -3.75 0.00 7.33 -0.87 82.02 43.12 126.14 +316 2.20 -11.53 0.00 2.54 -3.05 81.56 27.32 130.49 +317 1.13 -13.66 0.00 3.78 0.26 50.53 20.45 143.31 +318 4.62 -8.35 0.00 3.92 0.55 49.05 18.20 127.42 +319 1.48 -10.90 0.05 2.40 -0.06 97.06 24.77 118.19 +320 1.37 -12.88 0.00 4.69 -0.82 53.15 19.92 140.39 +321 0.73 -9.23 0.00 7.28 -0.68 52.10 20.16 119.02 +322 -1.53 -12.73 0.04 3.47 3.68 67.75 20.09 95.61 +323 -2.95 -16.72 0.15 4.56 1.68 70.14 28.74 118.75 +324 -9.60 -19.83 0.10 2.89 2.88 52.61 26.38 124.83 +325 -6.69 -16.65 2.19 -4.02 -1.18 85.33 31.24 83.99 +326 -5.23 -22.59 0.00 3.34 0.11 95.97 32.41 136.64 +327 2.12 -14.27 0.00 7.58 -1.34 68.10 40.37 123.78 +328 4.32 -5.05 0.00 8.41 0.04 80.10 50.45 116.83 +329 4.04 -3.98 0.06 8.92 0.28 95.44 50.87 112.90 +330 2.04 -15.27 0.19 4.14 -4.04 64.80 37.93 111.99 +331 -6.79 -17.15 0.05 2.34 -2.04 58.06 31.37 128.19 +332 5.53 -12.00 0.52 4.97 -0.16 76.69 27.60 107.99 +333 4.68 -9.62 0.00 4.77 -6.75 69.96 44.49 101.29 +334 2.68 -7.32 0.00 4.59 -4.04 99.96 49.90 102.05 +335 6.33 -7.37 0.09 5.37 -2.84 91.36 33.50 122.83 +336 5.47 -4.72 0.00 5.53 -1.77 100.00 41.15 113.01 +337 4.78 -8.22 0.12 3.75 -2.08 68.59 46.11 101.73 +338 0.22 -13.33 0.00 3.02 -0.15 70.82 44.38 121.16 +339 -1.37 -10.98 0.00 4.22 -0.69 79.76 46.30 100.77 +340 -4.10 -11.58 0.00 2.45 -1.01 76.00 38.84 102.91 +341 -0.87 -8.93 0.40 4.79 -1.13 99.94 36.64 107.22 +342 -7.80 -18.97 0.00 2.87 -3.26 57.40 29.87 119.57 +343 -5.97 -15.37 0.00 3.92 -1.19 67.04 34.75 116.91 +344 -4.40 -13.50 0.00 3.87 -0.26 62.28 29.82 109.39 +345 -5.17 -12.14 0.05 2.66 1.44 78.87 36.12 98.29 +346 -6.15 -14.34 0.05 0.87 -0.77 99.98 49.12 84.88 +347 -7.68 -16.15 0.11 -0.02 0.41 99.95 43.92 81.65 +348 -6.25 -11.53 0.09 0.97 0.95 99.97 55.85 67.86 +349 -1.69 -15.38 0.03 3.39 0.48 90.24 52.18 98.56 +350 -0.27 -10.50 0.15 5.64 2.54 88.10 34.30 102.62 +351 1.75 -5.46 0.00 4.79 5.35 99.95 39.92 103.33 +352 -0.58 -9.89 0.11 3.19 2.87 94.12 53.09 95.10 +353 -6.38 -18.08 0.15 3.71 0.36 79.46 36.87 104.17 +354 -9.20 -21.47 0.00 4.14 0.44 96.44 34.29 103.84 +355 -3.06 -16.68 0.00 6.15 0.23 66.53 30.14 104.52 +356 2.13 -11.09 0.18 7.12 2.67 62.62 46.70 98.43 +357 3.06 -6.76 0.67 5.84 2.13 68.85 54.56 85.75 +358 -3.86 -14.60 0.00 3.38 0.14 86.90 53.28 98.67 +359 -11.69 -19.89 0.00 4.90 -0.87 63.48 39.97 103.73 +360 -6.92 -18.86 0.07 4.37 1.39 96.78 43.39 75.57 +361 2.65 -10.15 0.29 4.14 2.82 83.35 45.77 103.53 +362 2.34 -7.83 1.74 3.97 1.60 86.71 39.07 103.28 +363 -3.74 -17.65 0.05 3.52 -0.07 87.66 41.68 101.19 +364 2.77 -9.81 0.25 4.51 3.99 99.94 57.96 93.45 +365 -2.36 -8.74 0.58 4.01 2.44 99.99 59.92 69.89 +366 4.79 -2.78 0.30 4.88 1.74 99.99 47.89 95.32 diff --git a/tests/example/Input/data_weather_maca/weath.1981 b/tests/example/Input/data_weather_maca/weath.1981 new file mode 100644 index 000000000..f6e32d990 --- /dev/null +++ b/tests/example/Input/data_weather_maca/weath.1981 @@ -0,0 +1,367 @@ +# weather for site maca example at -105.58 / 39.59 year = 1981 +# DOY, Tmax_C, Tmin_C, PPT_cm, uas_mPERs, vas_mPERs, hursmax_pct, hursmin_pct, rsds_WPERm2 +1 2.90 -12.88 0.17 4.07 -3.74 88.60 32.34 104.99 +2 -6.43 -16.16 0.00 2.59 -3.29 63.83 32.60 109.03 +3 2.30 -12.13 0.00 4.66 -1.15 65.00 33.60 111.17 +4 1.95 -8.65 0.00 4.40 -1.64 99.99 41.51 101.51 +5 0.98 -8.11 0.12 4.70 -0.19 99.97 49.00 98.79 +6 1.62 -14.39 0.21 1.22 -3.08 99.94 54.40 99.31 +7 -4.83 -18.65 0.00 1.76 -0.54 65.20 33.42 113.70 +8 -0.32 -13.61 0.00 3.95 0.21 44.81 30.79 107.55 +9 -0.42 -10.01 0.00 3.80 -2.00 74.80 34.04 92.42 +10 1.53 -9.85 0.00 3.74 -1.49 74.50 47.42 103.25 +11 2.58 -6.74 0.06 4.80 0.48 89.98 51.25 97.47 +12 0.37 -9.88 0.16 1.96 -0.25 81.39 53.99 83.66 +13 -5.22 -14.55 0.00 1.30 -1.94 95.15 46.71 82.04 +14 -6.54 -14.96 0.21 -2.16 -1.05 99.91 57.94 60.66 +15 -6.70 -11.84 0.76 -4.51 2.89 79.44 61.15 60.21 +16 -5.76 -13.43 0.19 1.91 -0.18 73.35 54.25 98.41 +17 -4.17 -16.20 0.06 3.54 -0.50 78.86 54.93 109.54 +18 -1.60 -10.32 0.07 3.92 0.00 85.08 62.21 97.12 +19 -1.56 -10.35 0.00 2.89 0.00 73.25 62.06 99.38 +20 -2.43 -12.25 0.00 3.39 -1.07 99.97 56.13 101.14 +21 -4.03 -14.94 0.05 2.32 -2.07 76.63 55.38 103.79 +22 -4.94 -19.71 0.00 4.52 -1.17 84.26 42.61 131.63 +23 -5.94 -16.07 0.00 2.37 0.08 92.20 48.92 102.61 +24 -7.95 -20.08 0.21 -0.15 -2.80 99.93 39.81 133.23 +25 -5.35 -18.43 0.00 5.11 -1.83 99.89 51.35 101.46 +26 -4.70 -11.85 0.06 4.68 -1.41 80.42 47.29 104.60 +27 -4.72 -14.74 0.13 4.17 0.12 99.98 54.47 98.46 +28 -6.21 -13.98 0.15 -1.49 2.08 99.98 85.69 89.02 +29 -5.81 -14.60 0.50 -0.90 2.25 99.98 68.79 84.89 +30 -5.27 -13.77 0.00 2.54 0.96 98.46 56.65 106.18 +31 -9.36 -20.87 0.18 2.43 -1.27 87.29 50.49 134.22 +32 -11.09 -25.22 0.21 3.07 -0.18 83.73 39.95 149.11 +33 -10.14 -25.62 0.11 3.42 -0.16 86.70 47.54 143.34 +34 -8.91 -19.27 0.00 3.64 -2.19 77.51 53.03 131.14 +35 -2.64 -18.55 0.00 4.56 -1.83 78.78 32.63 153.88 +36 0.26 -12.87 0.00 5.63 -2.18 99.96 41.71 138.21 +37 0.36 -11.11 0.00 4.44 -2.23 73.60 52.51 133.10 +38 0.09 -12.22 0.00 3.72 -2.06 99.99 55.10 157.37 +39 0.29 -7.92 0.00 4.34 -2.20 93.95 58.76 129.11 +40 1.17 -7.93 0.03 3.98 -0.80 82.75 56.89 135.44 +41 2.27 -8.60 0.00 3.75 0.59 96.06 52.77 134.98 +42 1.40 -6.41 0.23 2.72 3.23 99.98 68.53 108.44 +43 -0.17 -9.80 0.25 1.65 0.13 99.99 54.09 114.70 +44 -2.27 -17.11 0.44 1.36 -1.94 88.64 35.22 156.53 +45 -1.58 -15.77 0.12 2.63 -0.53 90.13 39.08 150.79 +46 -0.26 -15.45 0.00 3.21 -1.88 65.77 35.94 170.87 +47 2.45 -12.47 0.00 3.50 -1.20 65.08 36.39 164.91 +48 4.16 -11.12 0.00 3.11 -1.17 77.01 41.61 179.35 +49 5.43 -9.77 0.00 3.38 0.86 70.36 32.71 179.58 +50 6.05 -9.40 0.03 2.50 2.09 99.99 33.85 183.91 +51 4.49 -8.35 0.00 -1.06 1.53 73.63 44.41 151.69 +52 1.99 -7.02 0.18 0.12 3.20 77.69 55.04 133.84 +53 1.62 -7.59 0.19 2.24 0.54 90.89 52.30 143.58 +54 0.85 -7.87 0.08 1.72 1.49 95.84 54.82 133.50 +55 0.55 -6.98 0.49 1.81 -0.18 83.03 78.07 129.79 +56 0.48 -10.66 0.00 1.15 1.38 82.24 53.50 151.23 +57 0.14 -7.63 0.22 0.37 2.90 99.98 55.33 115.48 +58 0.13 -6.99 1.52 -1.84 4.56 100.00 85.69 114.19 +59 -0.74 -11.35 0.20 3.98 1.10 77.41 48.67 147.77 +60 -1.68 -11.54 0.24 3.39 -2.07 99.98 46.72 160.69 +61 -0.83 -10.92 0.25 -2.21 2.17 99.99 48.90 145.82 +62 1.69 -8.51 0.71 1.81 2.53 100.00 57.51 151.62 +63 2.57 -6.08 0.22 3.67 0.15 98.03 56.28 164.52 +64 2.75 -5.82 0.00 2.12 -2.94 81.34 50.59 165.42 +65 1.61 -10.95 0.06 1.93 -4.37 99.99 43.36 180.53 +66 5.35 -9.59 0.06 3.44 -0.32 69.98 36.89 207.11 +67 4.60 -4.23 0.04 4.43 0.63 75.43 45.40 162.41 +68 5.83 -3.61 0.00 4.31 -0.54 92.95 44.51 175.52 +69 5.61 -3.38 0.00 3.88 1.49 87.00 51.25 167.17 +70 4.42 -5.81 1.05 1.24 -2.23 78.67 55.13 133.28 +71 0.51 -9.94 0.32 0.36 -4.87 99.14 45.83 167.32 +72 1.85 -11.86 0.07 2.04 -2.46 75.46 31.94 197.06 +73 3.87 -8.39 0.00 2.75 1.95 82.26 31.98 191.94 +74 5.55 -4.03 0.00 4.11 3.35 70.50 44.84 168.95 +75 5.46 -4.45 0.09 3.47 2.97 89.16 43.74 190.09 +76 4.11 -7.66 0.83 -0.99 -2.34 96.31 47.29 177.58 +77 0.57 -9.01 0.31 -3.58 -4.36 100.00 51.87 169.33 +78 3.83 -9.44 0.25 -1.27 -2.26 99.98 30.81 223.03 +79 7.95 -8.10 0.00 1.31 -0.97 99.97 25.33 228.57 +80 7.93 -6.43 0.00 2.43 -0.06 64.64 30.25 202.61 +81 3.69 -5.95 1.06 -2.65 0.22 99.47 45.89 103.77 +82 0.30 -7.73 1.81 -4.24 -4.21 99.98 61.45 101.88 +83 1.25 -11.37 0.15 -0.35 -4.03 99.95 39.06 220.58 +84 5.44 -11.04 0.00 3.21 0.54 72.21 31.94 220.39 +85 7.20 -5.17 0.03 4.92 -0.97 77.22 32.17 225.37 +86 9.69 -3.85 0.00 4.23 0.92 76.91 35.11 198.88 +87 10.96 -3.32 0.00 3.85 1.38 83.90 31.54 212.84 +88 9.48 -0.96 0.00 4.04 4.38 99.21 31.99 193.17 +89 7.34 -8.20 0.00 4.70 -2.35 99.95 28.85 206.45 +90 5.32 -9.09 0.00 3.87 1.40 55.86 22.73 201.77 +91 2.66 -9.25 0.16 3.38 -2.74 71.78 29.38 221.25 +92 2.20 -13.17 0.17 1.59 -2.89 64.61 21.35 237.62 +93 10.52 -10.45 0.00 2.94 2.28 40.98 17.37 237.39 +94 11.58 -2.86 0.00 4.39 2.34 43.53 20.49 219.36 +95 11.10 -5.32 0.00 2.95 2.27 56.64 21.38 217.78 +96 10.57 -3.91 0.00 3.04 0.11 52.50 23.26 222.01 +97 6.12 -9.47 0.29 1.53 -6.34 67.48 22.46 213.73 +98 7.27 -10.87 0.00 -2.01 -5.02 36.89 17.97 240.94 +99 11.72 -8.85 0.00 1.68 -0.31 36.07 12.98 239.67 +100 13.91 -1.75 0.06 5.03 0.40 31.61 13.25 243.65 +101 12.60 -4.39 0.05 1.02 0.24 39.88 13.69 204.56 +102 9.56 -1.44 0.25 -1.78 3.13 56.94 23.16 200.78 +103 8.53 -2.66 0.94 2.05 1.38 99.95 37.69 195.46 +104 7.10 -2.09 0.04 2.83 -2.20 90.15 39.93 222.33 +105 7.76 -6.15 0.00 1.39 0.24 86.07 37.82 207.58 +106 11.12 0.79 0.00 3.70 2.38 80.58 31.34 223.34 +107 10.02 -0.51 0.86 1.75 4.01 59.05 33.61 216.43 +108 7.89 -5.14 3.96 -0.99 3.09 91.52 47.62 195.66 +109 3.50 -6.19 2.49 -4.42 -4.81 93.45 56.34 155.51 +110 -1.40 -10.51 1.25 -1.25 -4.40 83.90 57.10 179.36 +111 -3.85 -12.27 0.87 -1.43 0.06 93.41 54.48 180.48 +112 -2.83 -9.97 1.17 -4.24 0.64 90.17 60.03 150.89 +113 -1.22 -9.57 2.02 -3.43 -1.12 99.99 57.68 183.64 +114 0.57 -7.34 0.12 0.28 2.35 90.27 48.02 193.39 +115 2.45 -6.83 0.62 2.57 4.29 81.14 46.99 186.16 +116 4.00 -1.62 0.49 2.69 1.10 79.73 54.03 206.17 +117 3.92 -6.69 0.87 -1.75 -0.94 99.97 51.16 180.44 +118 1.90 -5.09 0.44 -1.31 0.91 74.26 40.45 201.88 +119 4.44 -7.18 0.05 -0.88 1.92 81.94 33.37 234.72 +120 7.56 -5.28 0.20 2.39 0.19 79.35 32.05 236.87 +121 7.00 -5.76 0.04 2.94 1.24 77.87 29.40 274.05 +122 6.32 -2.16 0.10 2.79 2.06 76.41 29.72 200.65 +123 11.50 -2.97 0.00 2.44 2.25 75.98 30.52 241.68 +124 12.42 -2.38 0.00 3.40 1.04 77.47 26.48 265.14 +125 12.05 0.74 0.05 4.54 1.85 60.01 24.79 263.77 +126 9.08 -4.97 0.00 3.12 -4.82 70.54 25.52 289.76 +127 6.11 -8.32 0.00 1.67 -3.14 53.72 20.10 300.58 +128 12.56 -0.93 0.00 2.00 5.18 38.99 19.73 246.11 +129 13.90 -1.31 0.00 2.84 -0.94 45.69 20.29 297.98 +130 15.69 -0.98 0.00 0.64 3.70 45.39 17.89 288.32 +131 14.98 0.95 0.04 3.83 -0.25 41.80 17.61 269.97 +132 11.31 -3.90 0.07 1.83 -4.06 56.67 20.91 301.79 +133 15.82 -0.71 0.00 1.91 4.03 40.95 18.85 296.48 +134 18.91 4.95 0.07 5.06 6.90 41.96 18.63 292.57 +135 16.50 4.35 0.05 4.51 6.00 47.35 19.09 251.39 +136 15.00 2.36 0.00 4.11 6.10 42.39 17.72 292.02 +137 13.21 -1.02 0.39 2.78 -0.60 67.61 23.48 268.24 +138 8.81 -1.92 0.75 1.84 -4.17 86.84 28.96 275.06 +139 13.80 -2.11 0.00 2.54 3.52 55.95 22.77 295.07 +140 14.32 2.94 0.17 3.45 8.65 43.46 21.97 285.40 +141 16.75 1.12 0.05 2.91 5.20 46.79 14.45 299.76 +142 16.16 2.22 0.00 2.85 6.12 30.58 14.27 283.07 +143 14.32 -0.87 0.06 4.64 2.17 37.16 15.43 297.72 +144 12.75 -1.08 0.00 5.87 -1.46 49.88 17.41 276.55 +145 9.86 -1.50 0.00 -0.33 0.18 65.05 28.13 239.92 +146 8.94 0.53 0.21 -1.75 4.83 70.38 35.28 235.95 +147 12.83 -1.17 0.27 2.62 2.21 72.24 30.40 290.73 +148 15.49 2.23 0.00 3.04 5.12 54.62 17.63 298.27 +149 16.48 -1.91 0.00 4.49 2.54 43.54 15.94 301.62 +150 16.33 0.63 0.00 3.84 -0.93 47.06 16.79 292.77 +151 14.16 0.97 0.00 1.41 -1.93 39.48 17.54 299.03 +152 15.95 -0.21 0.00 -0.09 2.49 51.64 17.52 296.49 +153 19.04 0.92 0.00 3.04 1.58 38.59 12.16 305.13 +154 19.36 6.92 0.00 5.51 -1.22 28.65 12.80 301.75 +155 16.98 0.68 0.00 1.97 -1.58 41.01 17.77 288.90 +156 15.08 4.76 0.00 0.51 2.27 54.22 20.03 244.20 +157 11.57 1.15 0.92 -1.19 -0.46 88.18 32.79 285.28 +158 10.98 -0.66 0.30 -1.07 2.09 75.93 28.24 287.79 +159 14.15 -1.14 0.16 2.64 2.66 67.73 25.37 293.53 +160 16.31 4.69 1.68 2.92 3.16 69.57 23.91 280.07 +161 18.76 3.35 0.00 2.63 0.30 57.16 22.82 300.21 +162 18.89 3.98 0.00 1.66 -1.02 56.01 25.24 299.63 +163 21.40 4.46 0.00 1.82 2.41 51.39 18.11 302.53 +164 22.15 4.33 0.00 4.01 1.61 41.14 14.63 303.55 +165 21.91 3.73 0.00 1.80 -1.86 36.99 15.47 301.13 +166 23.47 5.79 0.00 -1.98 2.01 40.40 15.78 299.99 +167 22.94 5.13 0.00 0.73 1.99 47.61 16.10 299.03 +168 22.58 5.04 0.00 1.27 -1.67 49.15 19.23 300.31 +169 19.46 5.35 0.22 -1.79 -0.55 76.22 25.20 284.14 +170 18.77 4.41 0.07 1.16 4.20 69.86 24.39 293.74 +171 21.64 7.21 0.04 2.59 4.98 66.53 18.87 291.92 +172 24.43 7.34 0.00 3.48 4.50 39.16 12.04 305.69 +173 23.85 4.91 0.00 4.44 1.21 30.64 11.81 304.19 +174 23.74 5.83 0.00 3.69 2.22 29.65 11.99 305.82 +175 24.63 4.80 0.00 4.43 0.99 28.57 10.12 306.28 +176 24.02 6.36 0.00 4.67 1.48 20.96 9.92 303.15 +177 23.90 6.64 0.00 3.61 2.60 28.23 11.74 301.29 +178 23.64 8.66 0.08 3.18 3.05 39.22 14.57 292.22 +179 21.59 8.62 0.00 3.45 2.74 56.86 20.91 297.59 +180 20.53 8.26 0.03 3.63 -0.10 58.00 23.23 273.07 +181 20.43 7.35 0.14 2.31 -2.88 66.37 24.24 257.92 +182 20.06 5.14 0.22 0.90 -1.28 64.22 21.70 277.20 +183 19.38 6.13 0.15 1.65 2.03 67.22 23.33 271.60 +184 21.12 5.58 0.81 0.56 1.27 73.75 22.31 289.26 +185 23.69 5.92 0.08 1.55 0.50 50.05 15.05 292.78 +186 24.07 8.08 0.00 1.20 2.48 40.75 15.00 280.41 +187 23.64 8.11 0.00 2.86 0.12 45.14 19.23 299.63 +188 26.29 7.37 0.00 1.86 0.53 50.73 13.02 300.39 +189 25.82 8.41 0.00 3.51 -1.22 28.68 12.50 300.32 +190 19.93 4.31 0.08 -1.16 -3.46 46.21 13.68 282.24 +191 19.76 5.26 0.15 -2.45 0.04 76.10 28.41 263.14 +192 21.61 6.34 0.00 1.33 0.89 60.06 21.45 291.95 +193 21.92 6.77 0.28 0.84 -3.65 63.03 22.61 257.41 +194 15.45 -0.75 0.46 -1.94 -0.94 87.81 28.43 277.17 +195 14.77 2.57 0.17 0.82 4.88 99.99 30.99 233.42 +196 21.26 5.14 0.00 2.21 2.13 71.05 22.48 285.15 +197 21.25 5.29 0.00 4.46 -0.89 41.99 18.01 288.23 +198 21.17 5.44 0.00 4.19 -0.42 35.36 17.73 292.19 +199 21.11 4.36 0.00 2.20 -0.31 45.02 16.79 266.96 +200 21.46 8.47 0.00 2.29 1.99 44.03 20.01 256.22 +201 21.37 9.55 0.00 3.07 0.67 49.56 23.90 262.61 +202 21.46 8.54 0.35 2.74 0.36 58.98 25.32 258.91 +203 22.04 8.94 0.06 3.10 1.36 63.21 24.64 275.95 +204 22.49 9.42 0.41 3.14 1.83 54.60 21.86 269.86 +205 22.02 9.64 0.06 4.81 1.77 37.71 19.62 286.15 +206 21.52 5.11 0.09 3.51 0.28 39.33 18.98 285.67 +207 21.15 4.29 0.06 2.11 2.05 44.31 19.57 264.61 +208 21.65 8.78 0.14 3.53 2.12 54.17 23.93 272.28 +209 20.93 8.01 0.06 2.67 -0.03 53.34 26.21 269.15 +210 20.12 8.58 0.25 2.55 1.32 78.53 31.11 264.52 +211 18.91 9.01 0.79 1.88 -0.19 88.60 36.38 248.69 +212 19.12 7.51 1.11 -0.18 2.65 85.61 39.60 248.94 +213 19.60 8.19 0.67 2.51 1.53 85.33 38.89 259.98 +214 18.98 8.37 1.13 2.29 1.56 79.18 35.76 247.00 +215 18.97 6.92 0.55 1.93 -0.13 85.38 36.10 257.48 +216 19.53 6.72 0.64 2.93 0.89 83.98 33.20 263.95 +217 19.53 6.85 1.16 2.19 -2.23 85.09 39.34 211.57 +218 12.65 3.05 0.82 -1.72 -3.82 99.99 54.63 156.85 +219 12.40 1.67 0.50 1.10 -1.88 96.23 45.37 242.89 +220 17.08 2.84 0.00 1.81 1.24 96.85 35.63 263.87 +221 20.12 4.31 0.08 2.64 1.19 94.96 32.00 277.19 +222 19.62 6.95 0.03 2.08 0.39 85.75 33.11 237.05 +223 20.31 7.02 0.08 2.39 0.38 80.45 30.14 256.75 +224 20.87 5.54 0.00 2.50 -1.69 68.70 27.66 262.90 +225 19.91 5.28 0.34 -1.17 -2.12 80.81 31.05 261.04 +226 18.47 8.55 0.29 -2.74 1.19 95.50 36.82 215.52 +227 16.88 7.49 0.22 -1.34 3.23 100.00 44.80 207.37 +228 19.12 9.70 0.04 1.72 3.62 93.28 35.51 234.60 +229 19.72 8.66 0.12 3.73 0.34 99.97 28.87 244.62 +230 19.86 6.71 0.09 2.31 -2.47 62.81 28.58 229.04 +231 19.47 5.53 0.23 1.79 -0.06 78.30 28.84 255.19 +232 18.89 4.15 0.13 1.94 -2.66 63.99 26.75 269.99 +233 17.93 3.75 0.09 -2.04 -0.20 99.99 27.70 244.95 +234 17.35 5.00 0.73 -2.26 2.44 86.27 35.50 220.04 +235 17.68 6.26 0.55 0.21 3.21 83.75 35.15 232.77 +236 17.91 6.49 0.20 1.48 1.88 55.17 34.74 232.88 +237 17.94 6.76 0.50 -0.11 2.21 75.79 34.03 236.46 +238 19.43 7.50 0.04 1.75 1.87 81.15 28.36 246.53 +239 19.31 4.13 0.00 1.81 -1.31 58.74 26.01 255.34 +240 19.47 6.60 0.00 2.58 0.39 52.72 25.65 244.82 +241 20.08 3.27 0.00 0.84 -2.50 52.86 23.09 246.38 +242 18.41 3.49 0.00 -1.95 0.55 69.98 30.94 222.98 +243 20.34 7.03 0.00 2.39 3.36 58.81 24.31 231.88 +244 19.49 5.98 0.00 2.90 2.53 64.09 27.28 234.13 +245 18.02 4.98 0.10 2.74 2.72 68.39 31.88 208.81 +246 14.69 7.00 0.48 2.35 2.97 87.22 40.84 192.12 +247 16.73 6.14 0.33 2.13 3.08 99.98 35.38 217.60 +248 17.05 5.24 0.15 2.03 3.20 68.17 34.60 222.22 +249 16.96 5.11 1.08 2.54 1.23 64.38 33.33 218.46 +250 16.39 6.04 1.23 1.67 1.14 68.32 36.93 207.52 +251 16.56 4.97 0.25 2.08 0.20 80.10 33.58 218.22 +252 18.46 3.67 0.00 3.92 -2.05 82.67 24.94 238.94 +253 18.05 2.80 0.00 4.71 -1.48 56.31 24.39 247.08 +254 17.43 3.58 0.00 4.67 -2.08 53.93 25.51 232.79 +255 16.39 1.36 0.00 1.40 -2.20 62.29 24.33 239.77 +256 16.92 2.60 0.00 -0.03 4.04 54.76 24.30 210.76 +257 16.02 4.74 0.00 1.87 3.06 47.11 24.73 194.80 +258 14.37 3.23 0.00 1.78 2.42 54.51 27.38 203.15 +259 13.95 2.21 0.37 1.93 2.65 59.53 27.90 209.29 +260 16.16 2.21 0.33 3.32 2.39 66.13 29.25 213.42 +261 14.99 5.17 0.61 3.86 0.39 64.59 31.20 186.78 +262 12.38 2.71 0.33 3.48 -2.52 91.85 37.34 207.89 +263 14.02 -0.64 0.00 2.50 -1.25 81.93 29.76 229.11 +264 14.24 0.48 0.00 2.27 -0.07 64.08 30.24 205.30 +265 15.53 2.87 0.00 3.67 2.59 59.65 28.46 203.74 +266 15.36 3.50 0.15 2.90 -0.16 73.52 29.64 201.96 +267 13.40 -1.12 0.52 -2.38 2.25 90.63 41.10 160.39 +268 11.53 -0.50 0.89 -0.17 4.18 100.00 43.11 175.43 +269 14.17 3.78 0.00 3.01 2.92 99.99 36.46 200.85 +270 13.33 2.15 0.19 3.21 -0.17 76.23 34.48 214.45 +271 15.19 1.77 0.00 2.06 1.02 80.02 28.99 194.61 +272 15.98 2.12 0.00 3.18 0.37 82.65 26.13 204.25 +273 15.70 3.77 0.00 2.58 1.67 85.20 28.49 165.68 +274 15.21 3.08 0.00 3.79 0.09 84.85 30.13 198.52 +275 15.52 1.82 0.08 4.29 0.29 68.43 26.04 180.33 +276 15.10 -0.10 0.00 3.97 0.00 60.45 21.45 207.83 +277 15.24 0.73 0.00 5.13 -0.58 42.72 19.79 211.79 +278 14.79 -0.64 0.00 2.01 0.53 45.33 19.32 181.43 +279 15.80 4.01 0.00 3.60 2.25 38.46 19.82 174.00 +280 15.22 2.70 0.00 2.57 0.77 61.80 20.51 151.39 +281 11.33 0.24 0.05 1.73 2.86 87.44 31.11 201.55 +282 10.86 -3.04 0.35 1.78 -2.90 73.73 36.04 170.60 +283 6.11 -6.26 0.06 1.61 -1.97 78.18 26.84 202.93 +284 10.37 -6.39 0.00 2.24 1.45 61.26 24.98 205.30 +285 13.01 -2.20 0.00 3.77 0.12 48.14 20.87 182.27 +286 13.83 -0.23 0.00 5.40 -0.25 39.66 18.73 178.74 +287 12.73 -2.99 0.00 4.32 -0.51 54.87 20.81 191.94 +288 12.91 -2.98 0.00 3.69 -0.21 48.56 20.26 184.29 +289 11.90 -1.05 0.00 0.80 0.31 43.13 21.10 145.93 +290 11.44 -1.35 0.00 -1.48 1.23 46.86 23.31 178.52 +291 11.70 -1.68 0.22 0.46 2.25 74.91 26.39 175.79 +292 13.27 -1.04 0.00 2.40 1.92 99.98 30.98 180.63 +293 14.44 0.73 0.00 3.53 0.58 100.00 28.97 174.79 +294 13.68 1.18 0.00 3.51 0.66 97.77 32.71 188.27 +295 12.34 0.53 0.47 2.21 -0.24 71.28 32.43 135.98 +296 9.52 -0.21 0.00 0.12 2.39 89.08 37.83 143.78 +297 10.68 -1.06 0.00 3.03 1.60 92.30 33.79 175.29 +298 10.46 -0.73 0.15 1.86 0.51 71.00 30.34 159.45 +299 9.40 -0.00 0.16 1.40 2.65 69.02 40.55 139.50 +300 10.45 0.32 0.12 2.87 2.46 74.77 48.20 149.57 +301 10.57 1.74 0.19 3.09 1.90 90.97 47.75 118.51 +302 6.74 -2.71 1.40 2.21 -1.06 84.26 57.13 110.94 +303 4.21 -4.23 0.30 4.07 -3.10 74.41 43.15 123.37 +304 3.79 -6.39 0.08 4.07 -0.25 76.93 41.19 129.44 +305 1.95 -11.95 0.00 7.22 -3.96 57.72 18.33 163.44 +306 -1.59 -13.65 0.00 3.78 -1.80 39.19 12.60 169.01 +307 0.84 -7.32 0.00 4.01 0.22 57.37 13.35 129.79 +308 -0.07 -12.01 0.07 3.03 -4.45 50.13 19.09 166.57 +309 1.75 -9.86 0.00 4.05 -0.28 45.54 22.00 121.89 +310 1.38 -11.99 0.22 3.48 -5.11 60.12 26.29 140.13 +311 -5.61 -12.84 0.38 3.43 -5.70 43.74 27.48 121.37 +312 -9.23 -15.54 0.16 3.38 -4.82 60.35 33.48 122.35 +313 -7.41 -15.77 0.18 1.24 -4.02 74.99 33.35 140.09 +314 -4.11 -16.93 0.00 2.18 -1.51 68.35 29.41 156.42 +315 -1.82 -14.14 0.00 3.32 -1.63 62.35 33.67 146.70 +316 1.18 -13.01 0.00 3.16 -1.35 63.74 32.34 142.58 +317 -0.76 -9.96 0.00 1.24 1.91 57.15 34.64 104.33 +318 4.64 -6.59 0.00 3.05 2.61 92.44 42.69 113.97 +319 3.34 -6.30 0.00 4.18 0.78 99.52 50.30 121.88 +320 5.73 -3.12 0.00 3.35 1.67 99.98 55.98 114.10 +321 7.67 -2.05 0.11 3.13 2.86 83.48 53.98 103.68 +322 6.82 -2.00 0.00 3.64 1.43 78.98 56.55 96.01 +323 3.81 -6.05 0.00 4.45 0.22 90.18 50.77 126.78 +324 2.54 -5.70 0.00 3.83 1.93 96.12 59.45 106.75 +325 5.60 -3.70 0.14 2.37 5.64 85.35 84.87 90.76 +326 5.80 -6.89 0.28 4.99 1.74 99.93 34.69 121.39 +327 -1.72 -13.58 0.00 4.00 0.14 99.93 33.38 133.24 +328 5.23 -9.19 0.00 5.15 0.33 64.35 33.48 121.20 +329 5.81 -5.51 0.00 4.54 1.06 69.13 24.81 123.39 +330 2.69 -6.41 0.00 4.28 1.19 99.94 37.53 110.84 +331 1.64 -10.98 0.00 2.92 0.73 89.48 28.75 123.58 +332 1.25 -9.21 0.06 3.37 0.54 67.09 32.62 114.55 +333 1.32 -8.59 0.00 4.06 -0.41 87.07 28.42 104.34 +334 3.56 -7.44 0.00 6.81 -0.64 59.30 26.61 104.60 +335 6.45 -4.58 0.00 7.14 -0.55 70.67 30.84 100.34 +336 5.64 -3.70 0.00 8.39 -1.10 68.87 35.25 113.98 +337 4.90 -4.86 0.05 4.22 1.68 77.40 44.41 90.80 +338 8.48 -3.61 0.22 5.59 2.51 81.83 46.83 93.89 +339 7.92 -4.83 0.58 5.26 2.42 67.14 45.08 100.28 +340 -2.54 -15.34 0.00 4.25 -0.07 63.86 23.75 119.00 +341 -7.07 -12.77 0.00 4.99 1.29 45.49 19.78 101.18 +342 -3.74 -13.03 0.18 7.88 1.49 48.21 24.68 103.06 +343 -3.68 -13.48 0.00 6.09 -1.76 51.62 32.40 89.50 +344 1.34 -9.80 0.32 4.53 0.68 81.00 31.70 100.11 +345 0.34 -10.40 0.00 5.40 -2.20 58.93 29.26 99.64 +346 2.26 -11.13 0.06 5.02 -0.85 65.80 39.97 89.44 +347 2.51 -6.72 0.00 4.48 -1.28 79.79 42.49 101.05 +348 4.71 -6.40 0.00 4.35 -0.74 77.04 36.09 101.64 +349 4.52 -4.40 0.00 4.23 0.30 98.02 45.73 102.40 +350 -0.83 -9.18 0.19 3.14 0.40 92.36 49.42 84.88 +351 -4.14 -12.76 0.57 2.79 -1.00 99.96 28.77 99.87 +352 -5.36 -13.72 0.39 2.26 3.27 68.64 32.82 82.04 +353 -6.03 -19.58 0.11 2.07 -2.03 81.39 43.80 97.57 +354 -11.32 -23.77 0.13 2.43 -0.76 78.76 35.02 103.97 +355 -1.80 -17.88 0.00 4.28 1.33 81.96 48.02 89.21 +356 -3.38 -12.73 0.00 4.69 -1.58 78.41 43.07 98.89 +357 -2.25 -12.88 0.41 5.13 -0.16 99.95 52.57 88.40 +358 3.73 -4.98 0.35 8.56 0.26 99.98 37.84 102.32 +359 1.47 -5.76 0.06 6.64 -0.28 99.99 44.58 97.92 +360 4.23 -4.64 0.00 6.60 -1.25 68.40 45.88 104.11 +361 4.33 -4.08 0.05 6.04 -0.92 92.00 50.68 102.54 +362 3.23 -6.83 0.18 4.67 -0.34 90.38 38.17 100.36 +363 -4.03 -13.57 0.34 5.26 -1.34 63.48 28.97 102.21 +364 -10.55 -20.44 0.21 3.67 -1.81 54.00 27.03 100.21 +365 -12.35 -22.83 0.00 2.39 -1.04 55.75 28.49 106.85 diff --git a/testing/Input/data_weather_missing/weath.1980 b/tests/example/Input/data_weather_missing/weath.1980 similarity index 100% rename from testing/Input/data_weather_missing/weath.1980 rename to tests/example/Input/data_weather_missing/weath.1980 diff --git a/testing/Input/data_weather_missing/weath.2010 b/tests/example/Input/data_weather_missing/weath.2010 similarity index 100% rename from testing/Input/data_weather_missing/weath.2010 rename to tests/example/Input/data_weather_missing/weath.2010 diff --git a/testing/Input/estab.in b/tests/example/Input/estab.in similarity index 100% rename from testing/Input/estab.in rename to tests/example/Input/estab.in diff --git a/testing/Input/estab/bouteloua.estab b/tests/example/Input/estab/bouteloua.estab similarity index 100% rename from testing/Input/estab/bouteloua.estab rename to tests/example/Input/estab/bouteloua.estab diff --git a/testing/Input/estab/bromus.estab b/tests/example/Input/estab/bromus.estab similarity index 100% rename from testing/Input/estab/bromus.estab rename to tests/example/Input/estab/bromus.estab diff --git a/testing/Input/mkv_covar.in b/tests/example/Input/mkv_covar.in similarity index 100% rename from testing/Input/mkv_covar.in rename to tests/example/Input/mkv_covar.in diff --git a/testing/Input/mkv_prob.in b/tests/example/Input/mkv_prob.in similarity index 100% rename from testing/Input/mkv_prob.in rename to tests/example/Input/mkv_prob.in diff --git a/testing/Input/outsetup.in b/tests/example/Input/outsetup.in similarity index 100% rename from testing/Input/outsetup.in rename to tests/example/Input/outsetup.in diff --git a/testing/Input/siteparam.in b/tests/example/Input/siteparam.in similarity index 77% rename from testing/Input/siteparam.in rename to tests/example/Input/siteparam.in index 28b936e99..bce9a0557 100644 --- a/testing/Input/siteparam.in +++ b/tests/example/Input/siteparam.in @@ -9,9 +9,11 @@ #---- Soil water content initialization, minimum, and wet condition --1.0 # swc_min : cm/cm if 0 - <1.0, -bars if >= 1.0.; if < 0. then estimate residual water content for each layer -15.0 # swc_init: cm/cm if < 1.0, -bars if >= 1.0. -15.0 # swc_wet : cm/cm if < 1.0, -bars if >= 1.0. +-1.0 # swc_min : [cm/cm] if >= 0. and < 1.0 + # [-bars] if >= 1.0. + # estimate (from realistic limit and Rawls et al. 1985) if < 0. +15.0 # swc_init: [cm/cm] if < 1.0, [-bars] if >= 1.0. +15.0 # swc_wet : [cm/cm] if < 1.0, [-bars] if >= 1.0. #---- Diffuse recharge and runoff/runon 0 # reset (1/0): do/don't reset soil water content for each year @@ -83,6 +85,34 @@ NAN # aspect = surface azimuth angle (degrees): S=0, E=-90, N=±180, W=90; # Name of CO2 scenario: see input file `carbon.in` RCP85 + +# --- Soil characterization --- +# Are inputs of density representing bulk soil (type 1) or the matric component (type 0)? +0 + + +#--- Soil water retention curve (SWRC) ------ +# +# Implemented options (`swrc_name`/`ptf_name`, see `swrc2str[]`/`ptf2str[]`): +# - ptf_name = : SWRC parameters must be provided via "swrc_params.in" +# - swrc_name = "Campbell1974" (Campbell 1974) +# * ptf_name = "Cosby1984AndOthers" (Cosby et al. 1984 but `swc_sat` by Saxton et al. 2006) +# * ptf_name = "Cosby1984" (Cosby et al. 1984) +# - swrc_name = "vanGenuchten1980" (van Genuchten 1980) +# - swrc_name = "FXW" (Fredlund and Xing 1994, Wang et al. 2018) +# +# Note: option "Campbell1974"/"Cosby1984AndOthers" was hard-coded < v7.0.0 +# Note: `rSOILWAT2` may implement additional PTFs + +Campbell1974 # Specify soil water retention curve +Cosby1984AndOthers # Specify pedotransfer function + # (if not implemented, then provide SWRC parameters via "swrc_params.in") + +0 # Has SWRC parameters (see `has_swrcp`)? + # 0: Estimate with specified pedotransfer function + # 1: Use values from "swrc_params.in" + + #---- Transpiration regions # ndx : 1=shallow, 2=medium, 3=deep, 4=very deep # layer: deepest soil layer number of the region. diff --git a/testing/Input/soils.in b/tests/example/Input/soils.in similarity index 100% rename from testing/Input/soils.in rename to tests/example/Input/soils.in diff --git a/testing/Input/swcsetup.in b/tests/example/Input/swcsetup.in similarity index 100% rename from testing/Input/swcsetup.in rename to tests/example/Input/swcsetup.in diff --git a/tests/example/Input/swrc_params.in b/tests/example/Input/swrc_params.in new file mode 100644 index 000000000..f05fb9e02 --- /dev/null +++ b/tests/example/Input/swrc_params.in @@ -0,0 +1,39 @@ +#------ Input for Soil Water Retention Curves (by soil layer) ------ + +# A table with up to `MAX_LAYERS` rows (soil layers) and 6 columns: +# - the soil layers must match `soils.in` +# - the interpretation of columns (SWRC parameters) depends on the +# selected SWRC (see `siteparam.in`) +# - unused columns are ignored (if selected SWRC uses fewer than 6 parameters) + +# swrc = "Campbell1974" (default values below, from "Cosby1984") +# * param1 = air-entry suction [cm] +# * param2 = saturated volumetric water content for the matric component [cm/cm] +# * param3 = b, slope of the linear log-log retention curve [-] +# * param4 = saturated hydraulic conductivity [cm/day] + +# swrc = "vanGenuchten1980" +# * param1 = residual volumetric water content for the matric component [cm/cm] +# * param2 = saturated volumetric water content for the matric component [cm/cm] +# * param3 = alpha, related to the inverse of air entry suction [cm-1] +# * param4 = n, measure of the pore-size distribution [-] +# * param5 = saturated hydraulic conductivity [cm/day] + +# swrc = "FXW" +# * param1 = saturated volumetric water content of the matric component [cm/cm] +# * param2 = alpha, shape parameter [cm-1] +# * param3 = n, shape parameter [-] +# * param4 = m, shape parameter [-] +# * param5 = saturated hydraulic conductivity [cm / day] +# * param6 = L, tortuosity/connectivity parameter [-] + + +# param1 param2 param3 param4 param5 param6 +18.6080 0.42703 5.3020 24.03047 0.0000 0.0000 +20.4644 0.43290 7.0500 14.94351 0.0000 0.0000 +22.8402 0.44013 9.4320 13.71177 0.0000 0.0000 +24.0381 0.44291 10.0690 13.43171 0.0000 0.0000 +24.2159 0.44359 10.3860 14.14351 0.0000 0.0000 +23.3507 0.44217 10.3830 40.63764 0.0000 0.0000 +12.3880 0.41370 7.3250 37.22899 0.0000 0.0000 +12.3880 0.41370 7.3250 37.22899 0.0000 0.0000 diff --git a/tests/example/Input/swrc_params_FXW.in b/tests/example/Input/swrc_params_FXW.in new file mode 100644 index 000000000..7f1bc8df3 --- /dev/null +++ b/tests/example/Input/swrc_params_FXW.in @@ -0,0 +1,26 @@ +#------ Input for Soil Water Retention Curves (by soil layer) ------ + +# A table with up to `MAX_LAYERS` rows (soil layers) and 6 columns: +# - the soil layers must match `soils.in` +# - the interpretation of columns (SWRC parameters) depends on the +# selected SWRC (see `siteparam.in`) +# - unused columns are ignored (if selected SWRC uses fewer than 6 parameters) + +# swrc = "FXW" (values below, from "neuroFX2021") +# * param1 = saturated volumetric water content of the matric component [cm/cm] +# * param2 = alpha, shape parameter [cm-1] +# * param3 = n, shape parameter [-] +# * param4 = m, shape parameter [-] +# * param5 = saturated hydraulic conductivity [cm / day] +# * param6 = L, tortuosity/connectivity parameter [-] + + +# param1 param2 param3 param4 param5 param6 +0.437461 0.050757 1.247689 0.308681 22.985379 2.697338 +0.452401 0.103033 1.146533 0.195394 89.365566 2.843288 +0.471163 0.149055 1.143810 0.124494 332.262496 2.988864 +0.475940 0.153117 1.141559 0.112295 420.418728 3.012669 +0.480690 0.157887 1.142653 0.105748 534.172981 3.049937 +0.538088 0.174184 1.124589 0.098441 978.516197 3.287010 +0.453070 0.169900 1.308269 0.182713 672.009929 3.218662 +0.453070 0.169900 1.308269 0.182713 672.009929 3.218662 diff --git a/tests/example/Input/swrc_params_vanGenuchten1980.in b/tests/example/Input/swrc_params_vanGenuchten1980.in new file mode 100644 index 000000000..d31396bcc --- /dev/null +++ b/tests/example/Input/swrc_params_vanGenuchten1980.in @@ -0,0 +1,24 @@ +#------ Input for Soil Water Retention Curves (by soil layer) ------ + +# A table with up to `MAX_LAYERS` rows (soil layers) and 6 columns: +# - the soil layers must match `soils.in` +# - the interpretation of columns (SWRC parameters) depends on the +# selected SWRC (see `siteparam.in`) +# - unused columns are ignored (if selected SWRC uses fewer than 6 parameters) + +# swrc = "vanGenuchten1980" (values below, from "Rosetta3") +# * param1 = residual volumetric water content for the matric component [cm/cm] +# * param2 = saturated volumetric water content for the matric component [cm/cm] +# * param3 = alpha, related to the inverse of air entry suction [cm-1] +# * param4 = n, measure of the pore-size distribution [-] +# * param5 = saturated hydraulic conductivity [cm/day] + +# param1 param2 param3 param4 param5 param6 +0.07564425 0.3925437 0.010035788 1.412233 19.871040 0 +0.10061329 0.4011315 0.009425738 1.352274 9.090754 0 +0.12060752 0.4278807 0.010424896 1.287923 7.862807 0 +0.12336711 0.4393192 0.010807529 1.274654 9.100139 0 +0.12461498 0.4444546 0.011155912 1.267327 9.902011 0 +0.12480807 0.4426857 0.011408809 1.264873 9.784818 0 +0.10129327 0.3878319 0.014241212 1.311722 10.985124 0 +0.10129327 0.3878319 0.014241212 1.311722 10.985124 0 diff --git a/testing/Input/veg.in b/tests/example/Input/veg.in similarity index 94% rename from testing/Input/veg.in rename to tests/example/Input/veg.in index 51e67b316..0bde58b47 100755 --- a/testing/Input/veg.in +++ b/tests/example/Input/veg.in @@ -8,6 +8,10 @@ # describe the four available vegetation types and should not be # modified unless a vegetation type itself is altered. +#---- Select method for vegetation parameters +0 # 0 - Use values from this file + # 1 - Estimate vegetation composition from long-term climate conditions + # (values for other vegetation parameters from this file) #---- Composition of vegetation type components (0-1; must add up to 1) # Grasses Shrubs Trees Forbs BareGround diff --git a/tests/example/Input/weathsetup.in b/tests/example/Input/weathsetup.in new file mode 100755 index 000000000..74e611c9d --- /dev/null +++ b/tests/example/Input/weathsetup.in @@ -0,0 +1,78 @@ +#------ Input file for weather-related parameters and weather generator settings + +#--- Activate/deactivate simulation of snow-related processes +1 # 1 = do/don't simulate snow processes +0.0 # snow drift/blowing snow (% per snow event): > 0 is adding snow, < 0 is removing snow +0.0 # runoff/runon of snowmelt water (% per snowmelt event): > 0 is runoff, < 0 is runon + + +#--- Activate/deactivate weather generator +0 # 0 = use historical data only + # 1 = use weather generator for (partially) missing weather inputs + # 2 = use weather generator for all weather (don't check weather inputs) + # 3 = impute missing temperature with LOCF and missing precipitation as 0 + +7 # Seed random number generator for weather generator (only used if SOILWAT2) + # (seed with 0 to use current time) + +#--- Flags describing mean monthly climate input usage: +# 0 = Don't use mean monthly input +# 1 = Use mean monthly input (climate.in) and override respective flag for daily input, if flags conflict +1 # Sky cover +1 # Wind speed +1 # Relative humidity + + +#--- Flags describing daily weather input files "weath.YYYY": +# 0 = Variable is absent +# 1 = Daily variable present +# Note: The order of input values within input files must match the order of flags below (e.g., cloud cover cannot precede minimum temperature) +# Note: If maximum/minimum temperature or precipitation is set to 0 or a flag is set to 1, and the input data is not complete, the program will crash +1 # Maximum daily temperature [C] +1 # Minimum daily temperature [C] +1 # Precipitation [cm] +0 # Cloud cover [%] +0 # Wind speed [m/s] +0 # Wind speed eastward component [m/s] +0 # Wind speed northward component [m/s] +0 # Relative humidity [%] +0 # Maximum relative humidity [%] +0 # Minimum relative humidity [%] +0 # Specific humidity [%] +0 # Dew point temperature [C] +0 # Actual vapor pressure [kPa] +0 # Downward surface shortwave radiation (see `Daily weather input descriptions`) + + +#--- Daily weather input descriptions +0 # Description of downward surface shortwave radiation + # * 0: `rsds` represents daily global horizontal irradiation [MJ / m2] + # * 1: `rsds` represents flux density [W / m2] for a + # (hypothetical) flat horizon averaged over an entire day (24 hour period) + # * 2: `rsds` represents flux density [W / m2] for a + # (hypothetical) flat horizon averaged over the daylight period of the day + + +#--- Monthly scaling parameters: +# Month 1 = January, Month 2 = February, etc. +# PPT = multiplicative for daily PPT; max(0, scale * ppt) +# MaxT = additive for daily max temperature [C]; scale + maxtemp +# MinT = additive for daily min temperature [C]; scale + mintemp +# SkyCover = additive for mean monthly sky cover [%]; min(100, max(0, scale + sky cover)) +# Wind = multiplicative for mean monthly wind speed; max(0, scale * wind speed) +# rH = additive for mean monthly relative humidity [%]; min(100, max(0, scale + rel. Humidity)) +# ActVP = multiplicative for actual vapor pressure [kPa]; max(0, scale * actual vapor pressure) +# ShortWR = multiplicative for shortwave radiation [W/m2]; max(0, scale * shortwave radiation) +#Mon PPT MaxT MinT SkyCover Wind rH ActVP ShortWR +1 1.000 0.00 0.00 0.0 1.0 0.0 1.0 1.0 +2 1.000 0.00 0.00 0.0 1.0 0.0 1.0 1.0 +3 1.000 0.00 0.00 0.0 1.0 0.0 1.0 1.0 +4 1.000 0.00 0.00 0.0 1.0 0.0 1.0 1.0 +5 1.000 0.00 0.00 0.0 1.0 0.0 1.0 1.0 +6 1.000 0.00 0.00 0.0 1.0 0.0 1.0 1.0 +7 1.000 0.00 0.00 0.0 1.0 0.0 1.0 1.0 +8 1.000 0.00 0.00 0.0 1.0 0.0 1.0 1.0 +9 1.000 0.00 0.00 0.0 1.0 0.0 1.0 1.0 +10 1.000 0.00 0.00 0.0 1.0 0.0 1.0 1.0 +11 1.000 0.00 0.00 0.0 1.0 0.0 1.0 1.0 +12 1.000 0.00 0.00 0.0 1.0 0.0 1.0 1.0 diff --git a/testing/Input/years.in b/tests/example/Input/years.in similarity index 100% rename from testing/Input/years.in rename to tests/example/Input/years.in diff --git a/testing/Output/.gitignore b/tests/example/Output/.gitignore similarity index 100% rename from testing/Output/.gitignore rename to tests/example/Output/.gitignore diff --git a/testing/README.md b/tests/example/README.md similarity index 100% rename from testing/README.md rename to tests/example/README.md diff --git a/testing/files.in b/tests/example/files.in similarity index 95% rename from testing/files.in rename to tests/example/files.in index 53174483c..fe3aa484e 100755 --- a/testing/files.in +++ b/tests/example/files.in @@ -11,6 +11,7 @@ Output/logfile.log # Output file to which warnings, errors, and other important #--- Description of simulated site Input/siteparam.in # Input file for site location, initialization, and miscellaneous model parameters Input/soils.in # Input for soil information: soil layer, soil texture, etc. +Input/swrc_params.in # Input for soil water retention curve (used if pdf_type = 0, i.e., pedotransfer functions are not used) #--- Inputs of weather forcing and description of climate conditions Input/weathsetup.in # Input file for weather-related parameters and weather generator settings diff --git a/test/sw_maintest.cc b/tests/gtests/sw_maintest.cc similarity index 71% rename from test/sw_maintest.cc rename to tests/gtests/sw_maintest.cc index 93fcf6640..5c42b6ed3 100644 --- a/test/sw_maintest.cc +++ b/tests/gtests/sw_maintest.cc @@ -15,36 +15,20 @@ #include #include -#include "../myMemory.h" // externs `*logfp`, `errstr`, `logged`, `QuietMode`, `EchoInits` -#include "../generic.h" -#include "../filefuncs.h" // externs `_firstfile`, `inbuf` -#include "../rands.h" -#include "../Times.h" -#include "../SW_Defines.h" -#include "../SW_Times.h" -#include "../SW_Files.h" -#include "../SW_Carbon.h" -#include "../SW_Site.h" -#include "../SW_VegProd.h" -#include "../SW_VegEstab.h" -#include "../SW_Model.h" -#include "../SW_SoilWater.h" -#include "../SW_Weather.h" -#include "../SW_Markov.h" -#include "../SW_Sky.h" - -#include "../SW_Control.h" - -#include "sw_testhelpers.h" - - -/* The unit test code is using the SOILWAT2-standalone input files from testing/ as - example input. - The paths are relative to the unit-test executable which is located at the top level +#include "include/generic.h" +#include "include/filefuncs.h" // externs `_firstfile`, `inbuf` +#include "include/SW_Control.h" + +#include "tests/gtests/sw_testhelpers.h" + + +/* The unit test code is using the SOILWAT2-standalone input files from + tests/example/ as example inputs. + The paths are relative to the unit-test executable which is located at bin/ of the SOILWAT2 repository */ -const char * dir_test = "./testing"; +const char * dir_test = "./tests/example"; const char * masterfile_test = "files.in"; // relative to 'dir_test' @@ -66,16 +50,13 @@ int main(int argc, char **argv) { because SOILWAT2 uses (global) states. This is otherwise not comptable with the c++ approach used by googletest. */ - logged = swFALSE; - logfp = stdout; // Emulate 'sw_init_args()' if (!ChDir(dir_test)) { swprintf("Invalid project directory (%s)", dir_test); } strcpy(_firstfile, masterfile_test); - QuietMode = swTRUE; - EchoInits = swFALSE; + // Initialize SOILWAT2 variables and read values from example input file Reset_SOILWAT2_after_UnitTest(); diff --git a/test/sw_testhelpers.cc b/tests/gtests/sw_testhelpers.cc similarity index 70% rename from test/sw_testhelpers.cc rename to tests/gtests/sw_testhelpers.cc index c34054d0a..839e8720f 100644 --- a/test/sw_testhelpers.cc +++ b/tests/gtests/sw_testhelpers.cc @@ -15,44 +15,57 @@ #include #include -#include "../myMemory.h" // externs `*logfp`, `errstr`, `logged`, `QuietMode`, `EchoInits` -#include "../generic.h" -#include "../filefuncs.h" // externs `_firstfile`, `inbuf` -#include "../rands.h" -#include "../Times.h" -#include "../SW_Defines.h" -#include "../SW_Times.h" -#include "../SW_Files.h" -#include "../SW_Carbon.h" -#include "../SW_Site.h" -#include "../SW_VegProd.h" -#include "../SW_VegEstab.h" -#include "../SW_Model.h" -#include "../SW_SoilWater.h" -#include "../SW_Weather.h" -#include "../SW_Markov.h" -#include "../SW_Sky.h" -#include "../SW_Control.h" - -#include "sw_testhelpers.h" +#include "include/generic.h" +#include "include/filefuncs.h" // externs `_firstfile`, `inbuf` +#include "include/SW_Site.h" +#include "include/SW_SoilWater.h" +#include "include/SW_Weather.h" +#include "include/SW_Control.h" +#include "include/SW_Main_lib.h" // for `sw_check_log()` + +#include "tests/gtests/sw_testhelpers.h" /** Initialize SOILWAT2 variables and read values from example input file */ void Reset_SOILWAT2_after_UnitTest(void) { + /*--- Imitate 'SW_Main.c/main()': + we need to initialize and take down SOILWAT2 variables + because SOILWAT2 uses (global) states. + This is otherwise not comptable with the c++ approach used by googletest. + */ + logged = swFALSE; + logfp = NULL; + + QuietMode = swTRUE; + EchoInits = swFALSE; + SW_CTL_clear_model(swFALSE); - SW_CTL_setup_model(_firstfile); + SW_CTL_setup_model(_firstfile); // `_firstfile` is here "files.in" SW_CTL_read_inputs_from_disk(); + + /* Notes on messages during tests + - `SW_F_read()`, via SW_CTL_read_inputs_from_disk(), writes the file + "example/Output/logfile.log" to disk (based on content of "files.in") + - we close "Output/logfile.log" + - we set `logfp` to NULL to silence all non-error messages during tests + - error messages go directly to stderr (which DeathTests use to match against) + */ + sw_check_log(); + logfp = NULL; + + SW_WTH_finalize_all_weather(); SW_CTL_init_run(); - // Next two function calls will require SW_Output.c + // Next functions calls from `main()` require SW_Output.c // (see issue #85 'Make SW_Output.c comptabile with c++ to include in unit testing code') // SW_OUT_set_ncol(); // SW_OUT_set_colnames(); + // ... } @@ -68,7 +81,7 @@ void create_test_soillayers(unsigned int nlayers) { RealF dmax[MAX_LAYERS] = { 5, 6, 10, 11, 12, 20, 21, 22, 25, 30, 40, 41, 42, 50, 51, 52, 53, 54, 55, 60, 70, 80, 90, 110, 150}; - RealF matricd[MAX_LAYERS] = { + RealF bulkd[MAX_LAYERS] = { 1.430, 1.410, 1.390, 1.390, 1.380, 1.150, 1.130, 1.130, 1.430, 1.410, 1.390, 1.390, 1.380, 1.150, 1.130, 1.130, 1.430, 1.410, 1.390, 1.390, 1.380, 1.150, 1.130, 1.130, 1.400}; @@ -111,7 +124,7 @@ void create_test_soillayers(unsigned int nlayers) { int nRegions = 3; RealD regionLowerBounds[3] = {20., 50., 100.}; - set_soillayers(nlayers, dmax, matricd, f_gravel, + set_soillayers(nlayers, dmax, bulkd, f_gravel, evco, trco_grass, trco_shrub, trco_tree, trco_forb, psand, pclay, imperm, soiltemp, nRegions, regionLowerBounds); diff --git a/test/sw_testhelpers.h b/tests/gtests/sw_testhelpers.h similarity index 100% rename from test/sw_testhelpers.h rename to tests/gtests/sw_testhelpers.h diff --git a/test/test_SW_Carbon.cc b/tests/gtests/test_SW_Carbon.cc similarity index 77% rename from test/test_SW_Carbon.cc rename to tests/gtests/test_SW_Carbon.cc index 44031e8ed..2cc80af3e 100644 --- a/test/test_SW_Carbon.cc +++ b/tests/gtests/test_SW_Carbon.cc @@ -17,25 +17,25 @@ #include // for 'typeid' -#include "../generic.h" -#include "../myMemory.h" -#include "../filefuncs.h" -#include "../rands.h" -#include "../Times.h" -#include "../SW_Defines.h" -#include "../SW_Times.h" -#include "../SW_Files.h" -#include "../SW_Carbon.h" -#include "../SW_Site.h" -#include "../SW_VegProd.h" -#include "../SW_VegEstab.h" -#include "../SW_Model.h" -#include "../SW_SoilWater.h" -#include "../SW_Weather.h" -#include "../SW_Markov.h" -#include "../SW_Sky.h" - -#include "sw_testhelpers.h" +#include "include/generic.h" +#include "include/myMemory.h" +#include "include/filefuncs.h" +#include "include/rands.h" +#include "include/Times.h" +#include "include/SW_Defines.h" +#include "include/SW_Times.h" +#include "include/SW_Files.h" +#include "include/SW_Carbon.h" +#include "include/SW_Site.h" +#include "include/SW_VegProd.h" +#include "include/SW_VegEstab.h" +#include "include/SW_Model.h" +#include "include/SW_SoilWater.h" +#include "include/SW_Weather.h" +#include "include/SW_Markov.h" +#include "include/SW_Sky.h" + +#include "tests/gtests/sw_testhelpers.h" diff --git a/test/test_SW_Defines.cc b/tests/gtests/test_SW_Defines.cc similarity index 80% rename from test/test_SW_Defines.cc rename to tests/gtests/test_SW_Defines.cc index d72aaa2bd..0f3d62314 100644 --- a/test/test_SW_Defines.cc +++ b/tests/gtests/test_SW_Defines.cc @@ -15,10 +15,10 @@ #include #include -#include "../generic.h" -#include "../SW_Defines.h" +#include "include/generic.h" +#include "include/SW_Defines.h" -#include "sw_testhelpers.h" // get the re-defined `missing` +#include "tests/gtests/sw_testhelpers.h" // get the re-defined `missing` namespace { diff --git a/test/test_SW_Flow_Lib.cc b/tests/gtests/test_SW_Flow_Lib.cc similarity index 97% rename from test/test_SW_Flow_Lib.cc rename to tests/gtests/test_SW_Flow_Lib.cc index f47bfd4d9..9ba14139d 100644 --- a/test/test_SW_Flow_Lib.cc +++ b/tests/gtests/test_SW_Flow_Lib.cc @@ -17,28 +17,28 @@ #include // for 'typeid' -#include "../generic.h" -#include "../myMemory.h" -#include "../filefuncs.h" -#include "../rands.h" -#include "../Times.h" -#include "../SW_Defines.h" -#include "../SW_Times.h" -#include "../SW_Files.h" -#include "../SW_Carbon.h" -#include "../SW_Site.h" -#include "../SW_VegProd.h" -#include "../SW_VegEstab.h" -#include "../SW_Model.h" -#include "../SW_SoilWater.h" -#include "../SW_Weather.h" -#include "../SW_Markov.h" -#include "../SW_Sky.h" -#include "../pcg/pcg_basic.h" - -#include "../SW_Flow_lib.h" - -#include "sw_testhelpers.h" +#include "include/generic.h" +#include "include/myMemory.h" +#include "include/filefuncs.h" +#include "include/rands.h" +#include "include/Times.h" +#include "include/SW_Defines.h" +#include "include/SW_Times.h" +#include "include/SW_Files.h" +#include "include/SW_Carbon.h" +#include "include/SW_Site.h" +#include "include/SW_VegProd.h" +#include "include/SW_VegEstab.h" +#include "include/SW_Model.h" +#include "include/SW_SoilWater.h" +#include "include/SW_Weather.h" +#include "include/SW_Markov.h" +#include "include/SW_Sky.h" +#include "external/pcg/pcg_basic.h" + +#include "include/SW_Flow_lib.h" + +#include "tests/gtests/sw_testhelpers.h" @@ -451,7 +451,6 @@ namespace shift = 45, shape = 0.1, inflec = 0.25, range = 0.5; double swc[25], width[25], lyrEvapCo[25]; - double swc0[25] = { 0. }; // for test if swc = 0 // Loop over tests with varying number of soil layers for (k = 0; k < 2; k++) @@ -510,15 +509,6 @@ namespace "pot_soil_evap != 0 if fbse = 0 for " << nelyrs << " soil layers"; - // Begin TEST if (swc = 0) - pot_soil_evap(&bserate, nelyrs, lyrEvapCo, totagb, fbse, petday, shift, - shape, inflec, range, width, swc0, Es_param_limit); - - // expect baresoil evaporation rate = 0 if swc = 0 - EXPECT_DOUBLE_EQ(bserate, 0.) << - "pot_soil_evap != 0 if swc = 0 for " << nelyrs << " soil layers"; - - // Begin TEST if (totagb < Es_param_limit) pot_soil_evap(&bserate, nelyrs, lyrEvapCo, totagb, fbse, petday, shift, shape, inflec, range, width, swc, Es_param_limit); diff --git a/test/test_SW_Flow_Lib_PET.cc b/tests/gtests/test_SW_Flow_Lib_PET.cc similarity index 86% rename from test/test_SW_Flow_Lib_PET.cc rename to tests/gtests/test_SW_Flow_Lib_PET.cc index 2a7b02215..6e5f4f218 100644 --- a/test/test_SW_Flow_Lib_PET.cc +++ b/tests/gtests/test_SW_Flow_Lib_PET.cc @@ -19,28 +19,28 @@ #include // for 'typeid' -#include "../generic.h" -#include "../myMemory.h" -#include "../filefuncs.h" -#include "../rands.h" -#include "../Times.h" -#include "../SW_Defines.h" -#include "../SW_Times.h" -#include "../SW_Files.h" // externs `output_prefix` -#include "../SW_Carbon.h" -#include "../SW_Site.h" -#include "../SW_VegProd.h" -#include "../SW_VegEstab.h" -#include "../SW_Model.h" -#include "../SW_SoilWater.h" -#include "../SW_Weather.h" -#include "../SW_Markov.h" -#include "../SW_Sky.h" -#include "../pcg/pcg_basic.h" - -#include "../SW_Flow_lib_PET.h" - -#include "sw_testhelpers.h" +#include "include/generic.h" +#include "include/myMemory.h" +#include "include/filefuncs.h" +#include "include/rands.h" +#include "include/Times.h" +#include "include/SW_Defines.h" +#include "include/SW_Times.h" +#include "include/SW_Files.h" // externs `output_prefix` +#include "include/SW_Carbon.h" +#include "include/SW_Site.h" +#include "include/SW_VegProd.h" +#include "include/SW_VegEstab.h" +#include "include/SW_Model.h" +#include "include/SW_SoilWater.h" +#include "include/SW_Weather.h" +#include "include/SW_Markov.h" +#include "include/SW_Sky.h" +#include "external/pcg/pcg_basic.h" + +#include "include/SW_Flow_lib_PET.h" + +#include "tests/gtests/sw_testhelpers.h" @@ -696,10 +696,11 @@ namespace // Test solar radiation: global horizontal and tilted // Comparison against examples by Duffie & Beckman 2013 are expected to - // deviate in value, but show similar patterns, because (i) calculations for - // H_oh differ (see `SW2_SolarRadiation_Test.extraterrestrial`), (ii) - // we calculate H_gh while they use measured H_gh values, and (iii) - // separation models differ, etc. + // deviate in value, but show similar patterns, because + // (i) calculations for H_oh differ + // (see `SW2_SolarRadiation_Test.extraterrestrial`), + // (ii) we calculate H_gh while they use measured H_gh values, and + // (iii) separation models differ, etc. TEST(SW2SolarRadiationTest, global) { unsigned int k; @@ -708,38 +709,56 @@ namespace unsigned int doys_Table1_6_1[12] = { 17, 47, 75, 105, 135, 162, 198, 228, 258, 288, 318, 344 }; + unsigned int desc_rsds = 0; // `rsds` represents daily irradiation [MJ / m2] + double H_gt, H_ot, H_oh, H_gh, + rsds, + cc, actual_vap_pressure, + // Duffie & Beckman 2013: Example 2.19.1 H_Ex2_19_1[3][12] = { - // H_oh + // H_oh [MJ / m2] {13.37, 18.81, 26.03, 33.78, 39.42, 41.78, 40.56, 35.92, 28.80, 20.90, 14.62, 11.91}, - // H_gh + // H_gh [MJ / m2] {6.44, 9.89, 12.86, 16.05, 21.36, 23.04, 22.58, 20.33, 14.59, 10.48, 6.37, 5.74}, - // H_gt + // H_gt [MJ / m2] {13.7, 17.2, 15.8, 14.7, 16.6, 16.5, 16.8, 17.5, 15.6, 15.2, 11.4, 12.7} }, albedo[12] = {0.7, 0.7, 0.4, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.4}, + // Climate normals for Madison, WI // "WMO Climate Normals for MADISON/DANE CO REGIONAL ARPT, WI 1961–1990". // National Oceanic and Atmospheric Administration. Retrieved Jul 3, 2020. // ftp://ftp.atdd.noaa.gov/pub/GCOS/WMO-Normals/TABLES/REG_IV/US/GROUP4/72641.TXT - cloud_cover[12] = + cloud_cover1[12] = // Element 20: Sky Cover (Cloud Cover) // {66.25, 66.25, 70, 67.5, 65, 60, 57.5, 57.5, 60, 63.75, 72.5, 71.25}, // replaced observed with estimated values to match `H_Ex2_19_1`: // replaced ~ -61 + 1.661 * observed {53., 47.5, 54., 53., 40., 35., 35., 30., 46., 50., 63., 52.}, - // Element 11: Relative Humidity (%), MN3HRLY (Statistic 94): Mean of 3-Hourly Observations + cloud_cover2[12] = + // derived from observed `rsds` (`H_Ex2_19_1["H_gh"][]`) and calculated `H_gh` + // note: this should be identical to `cloud_cover1[]` + {39.9, 37.7, 45.6, 49.0, 36.2, 32.9, 30.6, 28.7, 40.6, 41.8, 50.7, 37.6}, + // Element 11: Relative Humidity (%), MN3HRLY (Statistic 94): Mean of 3-Hourly Observations rel_humidity[12] = {74.5, 73.1, 71.4, 66.3, 65.8, 68.3, 71.0, 74.4, 76.8, 73.2, 76.9, 78.5}, - // Element 01: Dry Bulb Temperature (deg C) + // Element 01: Dry Bulb Temperature (deg C) air_temp_mean[12] = {-8.9, -6.3, 0.2, 7.4, 13.6, 19, 21.7, 20.2, 15.4, 9.4, 1.9, -5.7}; + // Duffie & Beckman 2013: Example 2.19.1 for (k = 0; k < 12; k++) { + + actual_vap_pressure = actualVaporPressure1(rel_humidity[k], air_temp_mean[k]); + + //--- Test without observed radiation: missing `rsds`; `H_gh` calculated + cc = cloud_cover1[k]; + rsds = SW_MISSING; + H_gt = solar_radiation( doys_Table1_6_1[k], 43. * deg_to_rad, // latitude @@ -747,23 +766,88 @@ namespace 60 * deg_to_rad, // slope 0., // aspect albedo[k], - cloud_cover[k], - rel_humidity[k], - air_temp_mean[k], + &cc, + actual_vap_pressure, + rsds, + desc_rsds, &H_oh, &H_ot, &H_gh ); EXPECT_NEAR(H_oh, H_Ex2_19_1[0][k], tol0) - << "Duffie & Beckman 2013: Example 2.19.1, H_oh: " + << "Duffie & Beckman 2013: Example 2.19.1 (missing rsds), H_oh: " << "month = " << k + 1 << "\n"; // Feb/March deviate by ±1.25; other months by less than ±1 - EXPECT_NEAR(H_gh, H_Ex2_19_1[1][k], 1.3 * tol0) - << "Duffie & Beckman 2013: Example 2.19.1, H_gh: " + EXPECT_NEAR(H_gh, H_Ex2_19_1[1][k], 1.25 * tol0) + << "Duffie & Beckman 2013: Example 2.19.1 (missing rsds), H_gh: " << "month = " << k + 1 << "\n"; - EXPECT_NEAR(H_gt, H_Ex2_19_1[2][k], 1.3 * tol0) - << "Duffie & Beckman 2013: Example 2.19.1, H_gt: " + EXPECT_NEAR(H_gt, H_Ex2_19_1[2][k], 1.25 * tol0) + << "Duffie & Beckman 2013: Example 2.19.1 (missing rsds), H_gt: " + << "month = " << k + 1 << "\n"; + + + //--- Test with previously calculated `H_gh` and missing cloud cover + cc = SW_MISSING; + rsds = H_gh; // calculated using `cloud_cover1[]` + + H_gt = solar_radiation( + doys_Table1_6_1[k], + 43. * deg_to_rad, // latitude + 226., // elevation + 60 * deg_to_rad, // slope + 0., // aspect + albedo[k], + &cc, + actual_vap_pressure, + rsds, + desc_rsds, + &H_oh, + &H_ot, + &H_gh + ); + + // Expect: observed `rsds` (for `desc_rsds = 0`) is equal to `H_gh` + EXPECT_DOUBLE_EQ(rsds, H_gh); + // Expect: calculated cloud cover is equal to cloud cover previously + // used to determine "observed" `rsds` + EXPECT_DOUBLE_EQ(cc, cloud_cover1[k]); + + + //--- Test with observed radiation `rsds` and missing cloud cover + cc = SW_MISSING; + rsds = H_Ex2_19_1[1][k]; + + H_gt = solar_radiation( + doys_Table1_6_1[k], + 43. * deg_to_rad, // latitude + 226., // elevation + 60 * deg_to_rad, // slope + 0., // aspect + albedo[k], + &cc, + actual_vap_pressure, + rsds, + desc_rsds, + &H_oh, + &H_ot, + &H_gh + ); + + EXPECT_NEAR(H_oh, H_Ex2_19_1[0][k], tol0) + << "Duffie & Beckman 2013: Example 2.19.1 (observed rsds), H_oh: " + << "month = " << k + 1 << "\n"; + EXPECT_NEAR(H_gh, H_Ex2_19_1[1][k], tol0) + << "Duffie & Beckman 2013: Example 2.19.1 (observed rsds), H_gh: " + << "month = " << k + 1 << "\n"; + // Nov deviates by -2.8; Oct-Jan by ±1.4; other months by less than ±1 + EXPECT_NEAR(H_gt, H_Ex2_19_1[2][k], 3 * tol0) + << "Duffie & Beckman 2013: Example 2.19.1 (observed rsds), H_gt: " + << "month = " << k + 1 << "\n"; + + // Cloud cover estimated from observed `rsds` and calculated `H_gh` + EXPECT_NEAR(cc, cloud_cover2[k], tol1) + << "Duffie & Beckman 2013: Example 2.19.1 (observed rsds), cloud cover: " << "month = " << k + 1 << "\n"; } @@ -811,9 +895,12 @@ namespace TEST(SW2PETTest, petfunc) { int i; - unsigned int doy = 2; + unsigned int + doy = 2, + desc_rsds = 0; double check_pet, + rsds = SW_MISSING, H_gt, H_oh, H_ot, H_gh, lat = 39. * deg_to_rad, elev = 1000., @@ -824,7 +911,8 @@ namespace temp = 25., RH = 61., windsp = 1.3, - cloudcov = 71.; + cloudcov = 71., + actual_vap_pressure; // TEST `petfunc()` for varying average daily air temperature `avgtemp` [C] @@ -839,10 +927,13 @@ namespace for (i = 0; i < 10; i++) { + actual_vap_pressure = actualVaporPressure1(RH, avgtemps[i]); + H_gt = solar_radiation( doy, lat, elev, slope0, aspect, reflec, - cloudcov, RH, avgtemps[i], + &cloudcov, actual_vap_pressure, + rsds, desc_rsds, &H_oh, &H_ot, &H_gh ); @@ -859,12 +950,15 @@ namespace // Expected PET expected_pet_lats[] = {0.1550, 0.4360, 0.3597, 0.1216, 0.0421}; + double e_a = actualVaporPressure1(RH, temp); + for (i = 0; i < 5; i++) { H_gt = solar_radiation( doy, lats[i] * deg_to_rad, elev, slope0, aspect, reflec, - cloudcov, RH, temp, + &cloudcov, e_a, + rsds, desc_rsds, &H_oh, &H_ot, &H_gh ); @@ -889,7 +983,8 @@ namespace H_gt = solar_radiation( doy, lat, elevs[i], slope0, aspect, reflec, - cloudcov, RH, temp, + &cloudcov, e_a, + rsds, desc_rsds, &H_oh, &H_ot, &H_gh ); @@ -911,7 +1006,8 @@ namespace H_gt = solar_radiation( doy, lat, elev, slopes[i] * deg_to_rad, aspect, reflec, - cloudcov, RH, temp, + &cloudcov, e_a, + rsds, desc_rsds, &H_oh, &H_ot, &H_gh ); @@ -938,7 +1034,8 @@ namespace H_gt = solar_radiation( doy, lat, elev, sloped, aspects[i] * deg_to_rad, reflec, - cloudcov, RH, temp, + &cloudcov, e_a, + rsds, desc_rsds, &H_oh, &H_ot, &H_gh ); @@ -962,7 +1059,8 @@ namespace H_gt = solar_radiation( doy, lat, elev, sloped, aspect, reflecs[i], - cloudcov, RH, temp, + &cloudcov, e_a, + rsds, desc_rsds, &H_oh, &H_ot, &H_gh ); @@ -981,10 +1079,13 @@ namespace for (i = 0; i < 5; i++) { + actual_vap_pressure = actualVaporPressure1(RHs[i], temp); + H_gt = solar_radiation( doy, lat, elev, slope0, aspect, reflec, - cloudcov, RHs[i], temp, + &cloudcov, actual_vap_pressure, + rsds, desc_rsds, &H_oh, &H_ot, &H_gh ); @@ -1004,7 +1105,8 @@ namespace H_gt = solar_radiation( doy, lat, elev, slope0, aspect, reflec, - cloudcov, RH, temp, + &cloudcov, e_a, + rsds, desc_rsds, &H_oh, &H_ot, &H_gh ); @@ -1029,7 +1131,8 @@ namespace H_gt = solar_radiation( doy, lat, elev, slope0, aspect, reflec, - cloudcovs[i], RH, temp, + &cloudcovs[i], e_a, + rsds, desc_rsds, &H_oh, &H_ot, &H_gh ); @@ -1057,9 +1160,12 @@ namespace { int doy, k1, k2, k3, k4, k5; + unsigned int desc_rsds = 0; + double pet, temp, RH, windspeed, cloudcover, fH_gt, + rsds = SW_MISSING, H_gt, H_oh, H_ot, H_gh, elev = 0., lat = 40., @@ -1109,7 +1215,8 @@ namespace H_gt = fH_gt * solar_radiation( doy, lat, elev, slope, aspect, reflec, - cloudcover, RH, temp, + &cloudcover, RH, temp, + rsds, desc_rsds, &H_oh, &H_ot, &H_gh ); diff --git a/test/test_SW_Flow_lib_temp.cc b/tests/gtests/test_SW_Flow_lib_temp.cc similarity index 97% rename from test/test_SW_Flow_lib_temp.cc rename to tests/gtests/test_SW_Flow_lib_temp.cc index 4f60f2a21..519dc6ca8 100644 --- a/test/test_SW_Flow_lib_temp.cc +++ b/tests/gtests/test_SW_Flow_lib_temp.cc @@ -17,30 +17,28 @@ #include // for 'typeid' -#include "../generic.h" -#include "../myMemory.h" -#include "../filefuncs.h" -#include "../rands.h" -#include "../Times.h" -#include "../SW_Defines.h" -#include "../SW_Times.h" -#include "../SW_Files.h" -#include "../SW_Carbon.h" -#include "../SW_Site.h" -#include "../SW_VegProd.h" -#include "../SW_VegEstab.h" -#include "../SW_Model.h" -#include "../SW_SoilWater.h" -#include "../SW_Weather.h" -#include "../SW_Markov.h" -#include "../SW_Sky.h" -#include "../pcg/pcg_basic.h" - -#include "../SW_Flow_lib.h" -#include "../SW_Flow_lib.c" - - -#include "sw_testhelpers.h" +#include "include/generic.h" +#include "include/myMemory.h" +#include "include/filefuncs.h" +#include "include/rands.h" +#include "include/Times.h" +#include "include/SW_Defines.h" +#include "include/SW_Times.h" +#include "include/SW_Files.h" +#include "include/SW_Carbon.h" +#include "include/SW_Site.h" +#include "include/SW_VegProd.h" +#include "include/SW_VegEstab.h" +#include "include/SW_Model.h" +#include "include/SW_SoilWater.h" +#include "include/SW_Weather.h" +#include "include/SW_Markov.h" +#include "include/SW_Sky.h" +#include "external/pcg/pcg_basic.h" + +#include "include/SW_Flow_lib.h" + +#include "tests/gtests/sw_testhelpers.h" pcg32_random_t flowTemp_rng; @@ -194,7 +192,7 @@ namespace { fc2, wp2, deltaX, theMaxDepth2, nRgr, &ptr_stError ), - "@ generic.c LogError" + "SOIL_TEMP FUNCTION ERROR: soil temperature max depth" ); // Reset to previous global state @@ -711,7 +709,7 @@ namespace { &ptr_stError, max_air_temp, min_air_temp, H_gt, min_temp, max_temp, &surface_max, &surface_min ), - "@ generic.c LogError" + "SOILWAT2 ERROR soil temperature module was not initialized" ); //Reset to global state diff --git a/test/test_SW_Markov.cc b/tests/gtests/test_SW_Markov.cc similarity index 87% rename from test/test_SW_Markov.cc rename to tests/gtests/test_SW_Markov.cc index 1f1a07cb3..ccf82d900 100644 --- a/test/test_SW_Markov.cc +++ b/tests/gtests/test_SW_Markov.cc @@ -15,25 +15,25 @@ #include #include -#include "../generic.h" -#include "../myMemory.h" -#include "../filefuncs.h" -#include "../rands.h" -#include "../Times.h" -#include "../SW_Defines.h" -#include "../SW_Times.h" -#include "../SW_Files.h" -#include "../SW_Carbon.h" -#include "../SW_Site.h" -#include "../SW_VegProd.h" -#include "../SW_VegEstab.h" -#include "../SW_Model.h" -#include "../SW_SoilWater.h" -#include "../SW_Weather.h" -#include "../SW_Markov.h" -#include "../SW_Sky.h" - -#include "sw_testhelpers.h" +#include "include/generic.h" +#include "include/myMemory.h" +#include "include/filefuncs.h" +#include "include/rands.h" +#include "include/Times.h" +#include "include/SW_Defines.h" +#include "include/SW_Times.h" +#include "include/SW_Files.h" +#include "include/SW_Carbon.h" +#include "include/SW_Site.h" +#include "include/SW_VegProd.h" +#include "include/SW_VegEstab.h" +#include "include/SW_Model.h" +#include "include/SW_SoilWater.h" +#include "include/SW_Weather.h" +#include "include/SW_Markov.h" +#include "include/SW_Sky.h" + +#include "tests/gtests/sw_testhelpers.h" @@ -73,7 +73,8 @@ namespace { ppt, *ppt0 = new double[n]; // Turn on Markov weather generator - SW_Weather.use_weathergenerator = swTRUE; + SW_Weather.generateWeatherMethod = 2; + //--- Generate some weather values with fixed seed ------ @@ -181,7 +182,7 @@ namespace { // Case: (wT_covar ^ 2 / wTmax_var) > wTmin_var --> LOGFATAL EXPECT_DEATH_IF_SUPPORTED( (test_mvnorm)(&tmax, &tmin, 0., 0., 1., 1., 2.), - "@ generic.c LogError" + "Bad covariance matrix" ); // Reset to previous global state diff --git a/tests/gtests/test_SW_Site.cc b/tests/gtests/test_SW_Site.cc new file mode 100644 index 000000000..0c47c17ee --- /dev/null +++ b/tests/gtests/test_SW_Site.cc @@ -0,0 +1,642 @@ +#include "gtest/gtest.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // for 'typeid' + +#include "include/generic.h" +#include "include/myMemory.h" +#include "include/filefuncs.h" +#include "include/rands.h" +#include "include/Times.h" +#include "include/SW_Defines.h" +#include "include/SW_Times.h" +#include "include/SW_Files.h" +#include "include/SW_Carbon.h" +#include "include/SW_Site.h" // externs `_TranspRgnBounds` +#include "include/SW_VegProd.h" +#include "include/SW_VegEstab.h" +#include "include/SW_Model.h" +#include "include/SW_SoilWater.h" +#include "include/SW_Weather.h" +#include "include/SW_Markov.h" +#include "include/SW_Sky.h" + +#include "tests/gtests/sw_testhelpers.h" + + + +namespace { + // List SWRC: PTFs + const char *ns_ptfca2C1974[] = { + "Campbell1974", + "Cosby1984AndOthers", "Cosby1984" + }; + const char *ns_ptfa2vG1980[] = { + "vanGenuchten1980", + // all PTFs + "Rosetta3" + }; + const char *ns_ptfc2vG1980[] = { + "vanGenuchten1980" + // PTFs implemented in SOILWAT2 + }; + const char *ns_ptfa2FXW[] = { + "FXW" + // all PTFs + "neuroFX2021" + }; + const char *ns_ptfc2FXW[] = { + "FXW" + // PTFs implemented in SOILWAT2 + }; + + // Test pedotransfer functions + TEST(SiteTest, PTFs) { + // inputs + RealD + swrcp[SWRC_PARAM_NMAX], + sand = 0.33, + clay = 0.33, + gravel = 0.1, + bdensity = 1.4; + unsigned int swrc_type, k; + + + //--- Matching PTF-SWRC pairs + // (k starts at 1 because 0 holds the SWRC) + + swrc_type = encode_str2swrc((char *) ns_ptfca2C1974[0]); + for (k = 1; k < length(ns_ptfca2C1974); k++) { + SWRC_PTF_estimate_parameters( + encode_str2ptf((char *) ns_ptfca2C1974[k]), + swrcp, + sand, + clay, + gravel, + bdensity + ); + EXPECT_TRUE((bool) SWRC_check_parameters(swrc_type, swrcp)); + } + + swrc_type = encode_str2swrc((char *) ns_ptfc2vG1980[0]); + for (k = 1; k < length(ns_ptfc2vG1980); k++) { + SWRC_PTF_estimate_parameters( + encode_str2ptf((char *) ns_ptfc2vG1980[k]), + swrcp, + sand, + clay, + gravel, + bdensity + ); + EXPECT_TRUE((bool) SWRC_check_parameters(swrc_type, swrcp)); + } + + swrc_type = encode_str2swrc((char *) ns_ptfc2FXW[0]); + for (k = 1; k < length(ns_ptfc2FXW); k++) { + SWRC_PTF_estimate_parameters( + encode_str2ptf((char *) ns_ptfc2FXW[k]), + swrcp, + sand, + clay, + gravel, + bdensity + ); + EXPECT_TRUE((bool) SWRC_check_parameters(swrc_type, swrcp)); + } + } + + + // Test fatal failures of PTF estimation + TEST(SiteDeathTest, PTFs) { + + RealD + swrcp[SWRC_PARAM_NMAX], + sand = 0.33, + clay = 0.33, + gravel = 0.1, + bdensity = 1.4; + unsigned int ptf_type; + + + //--- Test unimplemented PTF + ptf_type = N_PTFs + 1; + + EXPECT_DEATH_IF_SUPPORTED( + SWRC_PTF_estimate_parameters( + ptf_type, + swrcp, + sand, + clay, + gravel, + bdensity + ), + "PTF is not implemented in SOILWAT2" + ); + } + + + // Test PTF-SWRC pairings + TEST(SiteTest, PTF2SWRC) { + unsigned int k; // `length()` returns "unsigned long" + + for (k = 1; k < length(ns_ptfca2C1974); k++) { + EXPECT_TRUE( + (bool) check_SWRC_vs_PTF( + (char *) ns_ptfca2C1974[0], + (char *) ns_ptfca2C1974[k] + ) + ); + + EXPECT_FALSE( + (bool) check_SWRC_vs_PTF( + (char *) ns_ptfa2vG1980[0], + (char *) ns_ptfca2C1974[k] + ) + ); + + EXPECT_FALSE( + (bool) check_SWRC_vs_PTF( + (char *) ns_ptfa2FXW[0], + (char *) ns_ptfca2C1974[k] + ) + ); + } + + for (k = 1; k < length(ns_ptfa2vG1980); k++) { + EXPECT_FALSE( + (bool) check_SWRC_vs_PTF( + (char *) ns_ptfa2vG1980[0], + (char *) ns_ptfa2vG1980[k] + ) + ); + + EXPECT_FALSE( + (bool) check_SWRC_vs_PTF( + (char *) ns_ptfca2C1974[0], + (char *) ns_ptfa2vG1980[k] + ) + ); + + EXPECT_FALSE( + (bool) check_SWRC_vs_PTF( + (char *) ns_ptfa2FXW[0], + (char *) ns_ptfa2vG1980[k] + ) + ); + } + + + for (k = 1; k < length(ns_ptfa2FXW); k++) { + EXPECT_FALSE( + (bool) check_SWRC_vs_PTF( + (char *) ns_ptfa2FXW[0], + (char *) ns_ptfa2FXW[k] + ) + ); + + EXPECT_FALSE( + (bool) check_SWRC_vs_PTF( + (char *) ns_ptfca2C1974[0], + (char *) ns_ptfa2FXW[k] + ) + ); + + EXPECT_FALSE( + (bool) check_SWRC_vs_PTF( + (char *) ns_ptfa2vG1980[0], + (char *) ns_ptfa2FXW[k] + ) + ); + } + + } + + + // Test fatal failures of SWRC parameter checks + TEST(SiteDeathTest, SWRCpChecks) { + + // inputs + RealD swrcp[SWRC_PARAM_NMAX]; + unsigned int swrc_type; + + + //--- Test unimplemented SWRC + swrc_type = N_SWRCs + 1; + + EXPECT_DEATH_IF_SUPPORTED( + SWRC_check_parameters(swrc_type, swrcp), + "is not implemented" + ); + } + + + // Test nonfatal failures of SWRC parameter checks + TEST(SiteTest, SWRCpChecks) { + + // inputs + RealD + swrcp[SWRC_PARAM_NMAX], + tmp; + unsigned int swrc_type; + + + //--- SWRC: Campbell1974 + swrc_type = encode_str2swrc((char *) "Campbell1974"); + memset(swrcp, 0., SWRC_PARAM_NMAX * sizeof(swrcp[0])); + swrcp[0] = 24.2159; + swrcp[1] = 0.4436; + swrcp[2] = 10.3860; + swrcp[3] = 14.14351; + EXPECT_TRUE((bool) SWRC_check_parameters(swrc_type, swrcp)); + + // Param1 = psi_sat (> 0) + tmp = swrcp[0]; + swrcp[0] = -1.; + EXPECT_FALSE((bool) SWRC_check_parameters(swrc_type, swrcp)); + swrcp[0] = tmp; + + // Param2 = theta_sat (0-1) + tmp = swrcp[1]; + swrcp[1] = -1.; + EXPECT_FALSE((bool) SWRC_check_parameters(swrc_type, swrcp)); + swrcp[1] = 1.5; + EXPECT_FALSE((bool) SWRC_check_parameters(swrc_type, swrcp)); + swrcp[1] = tmp; + + // Param3 = beta (!= 0) + tmp = swrcp[2]; + swrcp[2] = 0.; + EXPECT_FALSE((bool) SWRC_check_parameters(swrc_type, swrcp)); + swrcp[2] = tmp; + + + + //--- Fail SWRC: vanGenuchten1980 + swrc_type = encode_str2swrc((char *) "vanGenuchten1980"); + memset(swrcp, 0., SWRC_PARAM_NMAX * sizeof(swrcp[0])); + swrcp[0] = 0.1246; + swrcp[1] = 0.4445; + swrcp[2] = 0.0112; + swrcp[3] = 1.2673; + swrcp[4] = 7.7851; + EXPECT_TRUE((bool) SWRC_check_parameters(swrc_type, swrcp)); + + + // Param1 = theta_res (0-1) + tmp = swrcp[0]; + swrcp[0] = -1.; + EXPECT_FALSE((bool) SWRC_check_parameters(swrc_type, swrcp)); + swrcp[0] = 1.5; + EXPECT_FALSE((bool) SWRC_check_parameters(swrc_type, swrcp)); + swrcp[0] = tmp; + + // Param2 = theta_sat (0-1 & > theta_res) + tmp = swrcp[1]; + swrcp[1] = -1.; + EXPECT_FALSE((bool) SWRC_check_parameters(swrc_type, swrcp)); + swrcp[1] = 1.5; + EXPECT_FALSE((bool) SWRC_check_parameters(swrc_type, swrcp)); + swrcp[1] = 0.5 * swrcp[0]; + EXPECT_FALSE((bool) SWRC_check_parameters(swrc_type, swrcp)); + swrcp[1] = tmp; + + // Param3 = alpha (> 0) + tmp = swrcp[2]; + swrcp[2] = 0.; + EXPECT_FALSE((bool) SWRC_check_parameters(swrc_type, swrcp)); + swrcp[2] = tmp; + + // Param4 = n (> 1) + tmp = swrcp[3]; + swrcp[3] = 1.; + EXPECT_FALSE((bool) SWRC_check_parameters(swrc_type, swrcp)); + swrcp[3] = tmp; + + + + + + //--- Fail SWRC: FXW + swrc_type = encode_str2swrc((char *) "FXW"); + memset(swrcp, 0., SWRC_PARAM_NMAX * sizeof(swrcp[0])); + swrcp[0] = 0.437461; + swrcp[1] = 0.050757; + swrcp[2] = 1.247689; + swrcp[3] = 0.308681; + swrcp[4] = 22.985379; + swrcp[5] = 2.697338; + EXPECT_TRUE((bool) SWRC_check_parameters(swrc_type, swrcp)); + + + // Param1 = theta_sat (0-1) + tmp = swrcp[0]; + swrcp[0] = -1.; + EXPECT_FALSE((bool) SWRC_check_parameters(swrc_type, swrcp)); + swrcp[0] = 1.5; + EXPECT_FALSE((bool) SWRC_check_parameters(swrc_type, swrcp)); + swrcp[0] = tmp; + + // Param2 = alpha (> 0) + tmp = swrcp[1]; + swrcp[1] = 0.; + EXPECT_FALSE((bool) SWRC_check_parameters(swrc_type, swrcp)); + swrcp[1] = tmp; + + // Param3 = n (> 1) + tmp = swrcp[2]; + swrcp[2] = 1.; + EXPECT_FALSE((bool) SWRC_check_parameters(swrc_type, swrcp)); + swrcp[2] = tmp; + + // Param4 = m (> 0) + tmp = swrcp[3]; + swrcp[3] = 0.; + EXPECT_FALSE((bool) SWRC_check_parameters(swrc_type, swrcp)); + swrcp[3] = tmp; + + // Param5 = Ksat (> 0) + tmp = swrcp[4]; + swrcp[4] = 0.; + EXPECT_FALSE((bool) SWRC_check_parameters(swrc_type, swrcp)); + swrcp[4] = tmp; + + // Param6 = L (> 0) + tmp = swrcp[5]; + swrcp[5] = 0.; + EXPECT_FALSE((bool) SWRC_check_parameters(swrc_type, swrcp)); + swrcp[5] = tmp; + } + + + // Test 'PTF_RawlsBrakensiek1985' + TEST(SiteTest, PTFRawlsBrakensiek1985) { + //declare mock INPUTS + double + theta_min, + clay = 0.1, + sand = 0.6, + porosity = 0.4; + int k1, k2, k3; + + //--- EXPECT SW_MISSING if soil texture is out of range + // within range: sand [0.05, 0.7], clay [0.05, 0.6], porosity [0.1, 1[ + PTF_RawlsBrakensiek1985(&theta_min, 0., clay, porosity); + EXPECT_DOUBLE_EQ(theta_min, SW_MISSING); + + PTF_RawlsBrakensiek1985(&theta_min, 0.75, clay, porosity); + EXPECT_DOUBLE_EQ(theta_min, SW_MISSING); + + PTF_RawlsBrakensiek1985(&theta_min, sand, 0., porosity); + EXPECT_DOUBLE_EQ(theta_min, SW_MISSING); + + PTF_RawlsBrakensiek1985(&theta_min, sand, 0.65, porosity); + EXPECT_DOUBLE_EQ(theta_min, SW_MISSING); + + PTF_RawlsBrakensiek1985(&theta_min, sand, clay, 0.); + EXPECT_DOUBLE_EQ(theta_min, SW_MISSING); + + PTF_RawlsBrakensiek1985(&theta_min, sand, clay, 1.); + EXPECT_DOUBLE_EQ(theta_min, SW_MISSING); + + + // Check that `theta_min` is reasonable over ranges of soil properties + for (k1 = 0; k1 <= 5; k1++) { + sand = 0.05 + (double) k1 / 5. * (0.7 - 0.05); + + for (k2 = 0; k2 <= 5; k2++) { + clay = 0.05 + (double) k2 / 5. * (0.6 - 0.05); + + for (k3 = 0; k3 <= 5; k3++) { + porosity = 0.1 + (double) k3 / 5. * (0.99 - 0.1); + + PTF_RawlsBrakensiek1985(&theta_min, sand, clay, porosity); + EXPECT_GE(theta_min, 0.); + EXPECT_LT(theta_min, porosity); + } + } + } + + // Expect theta_min = 0 if sand = 0.4, clay = 0.5, and porosity = 0.1 + PTF_RawlsBrakensiek1985(&theta_min, 0.4, 0.5, 0.1); + EXPECT_DOUBLE_EQ(theta_min, 0); + } + + + // Test that `SW_SIT_init_run` fails on bad soil inputs + TEST(SiteDeathTest, SoilParameters) { + LyrIndex n1 = 0, n2 = 1, k = 2; + RealD help; + + // Check error for bad bare-soil evaporation coefficient (should be [0-1]) + help = SW_Site.lyr[n1]->evap_coeff; + SW_Site.lyr[n1]->evap_coeff = -0.5; + EXPECT_DEATH_IF_SUPPORTED( + SW_SIT_init_run(), + "'bare-soil evaporation coefficient' has an invalid value" + ); + SW_Site.lyr[n1]->evap_coeff = help; + + // Check error for bad transpiration coefficient (should be [0-1]) + SW_Site.lyr[n2]->transp_coeff[k] = 1.5; + EXPECT_DEATH_IF_SUPPORTED( + SW_SIT_init_run(), + "'transpiration coefficient' has an invalid value" + ); + + // Reset to previous global states + Reset_SOILWAT2_after_UnitTest(); + } + + + // Test that soil transpiration regions are derived well + TEST(SWSiteTest, SoilTranspirationRegions) { + /* Notes: + - SW_Site.n_layers is base1 + - soil layer information in _TranspRgnBounds is base0 + */ + + LyrIndex + i, id, + nRegions, + prev_TranspRgnBounds[MAX_TRANSP_REGIONS] = {0}; + RealD + soildepth; + + for (i = 0; i < MAX_TRANSP_REGIONS; ++i) { + prev_TranspRgnBounds[i] = _TranspRgnBounds[i]; + } + + + // Check that "default" values do not change region bounds + nRegions = 3; + RealD regionLowerBounds1[] = {20., 40., 100.}; + derive_soilRegions(nRegions, regionLowerBounds1); + + for (i = 0; i < nRegions; ++i) { + // Quickly calculate soil depth for current region as output information + soildepth = 0.; + for (id = 0; id <= _TranspRgnBounds[i]; ++id) { + soildepth += SW_Site.lyr[id]->width; + } + + EXPECT_EQ(prev_TranspRgnBounds[i], _TranspRgnBounds[i]) << + "for transpiration region = " << i + 1 << + " at a soil depth of " << soildepth << " cm"; + } + + + // Check that setting one region for all soil layers works + nRegions = 1; + RealD regionLowerBounds2[] = {100.}; + derive_soilRegions(nRegions, regionLowerBounds2); + + for (i = 0; i < nRegions; ++i) { + EXPECT_EQ(SW_Site.n_layers - 1, _TranspRgnBounds[i]) << + "for a single transpiration region across all soil layers"; + } + + + // Check that setting one region for one soil layer works + nRegions = 1; + RealD regionLowerBounds3[] = {SW_Site.lyr[0]->width}; + derive_soilRegions(nRegions, regionLowerBounds3); + + for (i = 0; i < nRegions; ++i) { + EXPECT_EQ(0, _TranspRgnBounds[i]) << + "for a single transpiration region for the shallowest soil layer"; + } + + + // Check that setting the maximal number of regions works + nRegions = MAX_TRANSP_REGIONS; + RealD *regionLowerBounds4 = new RealD[nRegions]; + // Example: one region each for the topmost soil layers + soildepth = 0.; + for (i = 0; i < nRegions; ++i) { + soildepth += SW_Site.lyr[i]->width; + regionLowerBounds4[i] = soildepth; + } + derive_soilRegions(nRegions, regionLowerBounds4); + + for (i = 0; i < nRegions; ++i) { + EXPECT_EQ(i, _TranspRgnBounds[i]) << + "for transpiration region for the " << i + 1 << "-th soil layer"; + } + + delete[] regionLowerBounds4; + + // Reset to previous global states + Reset_SOILWAT2_after_UnitTest(); + } + + + // Test bulk and matric soil density functionality + TEST(SWSiteTest, SoilDensity) { + double + soildensity = 1.4, + fcoarse = 0.1; + + + // Check that matric density is zero if coarse fragments is 100% + EXPECT_DOUBLE_EQ( + calculate_soilMatricDensity(soildensity, 1.), + 0. + ); + + + // Check that bulk and matric soil density are equal if no coarse fragments + EXPECT_DOUBLE_EQ( + calculate_soilBulkDensity(soildensity, 0.), + calculate_soilMatricDensity(soildensity, 0.) + ); + + + // Check that bulk and matric density calculations are inverse to each other + EXPECT_DOUBLE_EQ( + calculate_soilBulkDensity( + calculate_soilMatricDensity(soildensity, fcoarse), + fcoarse + ), + soildensity + ); + + EXPECT_DOUBLE_EQ( + calculate_soilMatricDensity( + calculate_soilBulkDensity(soildensity, fcoarse), + fcoarse + ), + soildensity + ); + + + // Check that bulk density is larger than matric density if coarse fragments + EXPECT_GT( + calculate_soilBulkDensity(soildensity, fcoarse), + soildensity + ); + + + // Inputs represent matric density + SW_Site.type_soilDensityInput = SW_MATRIC; + SW_Site.lyr[0]->fractionVolBulk_gravel = fcoarse; + SW_SIT_init_run(); + + EXPECT_GT( + SW_Site.lyr[0]->soilBulk_density, + SW_Site.lyr[0]->soilMatric_density + ); + + + // Inputs represent bulk density + SW_Site.type_soilDensityInput = SW_BULK; + SW_Site.lyr[0]->fractionVolBulk_gravel = fcoarse; + SW_SIT_init_run(); + + EXPECT_GT( + SW_Site.lyr[0]->soilBulk_density, + SW_Site.lyr[0]->soilMatric_density + ); + + + // Reset to previous global states + Reset_SOILWAT2_after_UnitTest(); + } + + + // Test that bulk and matric soil density fail + TEST(SWSiteDeathTest, SoilDensity) { + + // Check error if bulk density too low for coarse fragments + EXPECT_DEATH_IF_SUPPORTED( + calculate_soilMatricDensity(1.65, 0.7), + "is lower than expected" + ); + + + // Check error if type_soilDensityInput not implemented + SW_Site.type_soilDensityInput = SW_MISSING; + + EXPECT_DEATH_IF_SUPPORTED( + SW_SIT_init_run(), + "Soil density type not recognized" + ); + + + // Reset to previous global states + Reset_SOILWAT2_after_UnitTest(); + } +} // namespace diff --git a/tests/gtests/test_SW_SoilWater.cc b/tests/gtests/test_SW_SoilWater.cc new file mode 100644 index 000000000..7abc79f75 --- /dev/null +++ b/tests/gtests/test_SW_SoilWater.cc @@ -0,0 +1,374 @@ +#include "gtest/gtest.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "include/generic.h" +#include "include/filefuncs.h" +#include "include/myMemory.h" +#include "include/SW_Defines.h" +#include "include/SW_Files.h" +#include "include/SW_Model.h" +#include "include/SW_Site.h" +#include "include/SW_SoilWater.h" +#include "include/SW_VegProd.h" +#include "include/SW_Site.h" +#include "include/SW_Flow_lib.h" +#include "tests/gtests/sw_testhelpers.h" + + + +namespace{ + // Test the 'SW_SoilWater' function 'SW_SWC_adjust_snow' + TEST(SWSoilWaterTest, SWCadjustSnow){ + // setup mock variables + SW_Site.TminAccu2 = 0; + SW_Model.doy = 1; + SW_Site.RmeltMax = 1; + SW_Site.RmeltMin = 0; + SW_Site.lambdasnow = .1; + SW_Site.TmaxCrit = 1; + + RealD temp_min = 0, temp_max = 10, ppt = 1, rain = 1.5, snow = 1.5, + snowmelt = 1.2; + + SW_SWC_adjust_snow(temp_min, temp_max, ppt, &rain, &snow, &snowmelt); + // when average temperature >= SW_Site.TminAccu2, we expect rain == ppt + EXPECT_EQ(rain, 1); + // when average temperature >= SW_Site.TminAccu2, we expect snow == 0 + EXPECT_EQ(snow, 0); + // when temp_snow <= SW_Site.TmaxCrit, we expect snowmelt == 0 + EXPECT_EQ(snowmelt, 0); + // Reset to previous global states + Reset_SOILWAT2_after_UnitTest(); + + SW_Site.TminAccu2 = 6; + + SW_SWC_adjust_snow(temp_min, temp_max, ppt, &rain, &snow, &snowmelt); + // when average temperature < SW_Site.TminAccu2, we expect rain == 0 + EXPECT_EQ(rain, 0); + // when average temperature < SW_Site.TminAccu2, we expect snow == ppt + EXPECT_EQ(snow, 1); + // when temp_snow > SW_Site.TmaxCrit, we expect snowmelt == fmax(0, *snowpack - *snowmelt ) + EXPECT_EQ(snowmelt, 0); + // Reset to previous global states + Reset_SOILWAT2_after_UnitTest(); + + temp_max = 22; + + SW_SWC_adjust_snow(temp_min, temp_max, ppt, &rain, &snow, &snowmelt); + // when average temperature >= SW_Site.TminAccu2, we expect rain == ppt + EXPECT_EQ(rain, 1); + // when average temperature >= SW_Site.TminAccu2, we expect snow == 0 + EXPECT_EQ(snow, 0); + // when temp_snow > SW_Site.TmaxCrit, we expect snowmelt == 0 + EXPECT_EQ(snowmelt, 0); + } + + + // Test the 'SW_SoilWater' functions 'SWRC_SWCtoSWP' and `SWRC_SWPtoSWC` + TEST(SWSoilWaterTest, TranslateBetweenSWCandSWP) { + // set up mock variables + unsigned int swrc_type, ptf_type, k; + const int em = LOGFATAL; + RealD + phi, + swcBulk, swc_sat, swc_fc, swc_wp, + swp, + swrcp[SWRC_PARAM_NMAX], + sand = 0.33, + clay = 0.33, + gravel = 0.2, + bdensity = 1.4, + width = 10., + // SWP values in [0, Inf[ but FXW maxes out at 6178.19079 bar + swpsb[12] = { + 0., 0.001, 0.01, 0.026, 0.027, 0.33, 15., 30., 100., 300., 1000., 6178. + }, + // SWP values in [fc, Inf[ but FXW maxes out at 6178.19079 bar + swpsi[7] = {0.33, 15., 30., 100., 300., 1000., 6178.}; + + std::ostringstream msg; + + + + // Loop over SWRCs + for (swrc_type = 0; swrc_type < N_SWRCs; swrc_type++) { + memset(swrcp, 0., SWRC_PARAM_NMAX * sizeof(swrcp[0])); + + // Find a suitable PTF to generate `SWRCp` + for ( + ptf_type = 0; + ptf_type < N_PTFs && !check_SWRC_vs_PTF( + (char *) swrc2str[swrc_type], + (char *) ptf2str[ptf_type] + ); + ptf_type++ + ) {} + + + // Obtain SWRCp + if (ptf_type < N_PTFs) { + // PTF implemented in C: estimate parameters + SWRC_PTF_estimate_parameters( + ptf_type, + swrcp, + sand, + clay, + gravel, + bdensity + ); + + } else { + // PTF not implemented in C: provide hard coded values + if ( + Str_CompareI( + (char *) swrc2str[swrc_type], + (char *) "vanGenuchten1980" + ) == 0 + ) { + swrcp[0] = 0.11214750; + swrcp[1] = 0.4213539; + swrcp[2] = 0.007735474; + swrcp[3] = 1.344678; + swrcp[4] = 7.78506; + + } else if ( + Str_CompareI( + (char *) swrc2str[swrc_type], + (char *) "FXW" + ) == 0 + ) { + swrcp[0] = 0.437461; + swrcp[1] = 0.050757; + swrcp[2] = 1.247689; + swrcp[3] = 0.308681; + swrcp[4] = 22.985379; + swrcp[5] = 2.697338; + + } else { + FAIL() << "No SWRC parameters available for " << swrc2str[swrc_type]; + } + } + + + //------ Tests SWC -> SWP + msg.str(""); + msg << "SWRC/PTF = " << swrc_type << "/" << ptf_type; + + // preferably we would print names instead of type codes, but + // this leads to "global-buffer-overflow" + // 0 bytes to the right of global variable 'ptf2str' + //msg << "SWRC/PTF = " << swrc2str[swrc_type] << "/" << ptf2str[ptf_type]; + + swc_sat = SWRC_SWPtoSWC(0., swrc_type, swrcp, gravel, width, em); + swc_fc = SWRC_SWPtoSWC(1. / 3., swrc_type, swrcp, gravel, width, em); + swc_wp = SWRC_SWPtoSWC(15., swrc_type, swrcp, gravel, width, em); + + + // if swc = saturation, then we expect phi in [0, fc] + // for instance, Campbell1974 goes to (theta_sat, swrcp[0]) instead of 0 + swp = SWRC_SWCtoSWP(swc_sat, swrc_type, swrcp, gravel, width, em); + EXPECT_GE(swp, 0.) << msg.str(); + EXPECT_LT(swp, 1. / 3.) << msg.str(); + + + // if swc > field capacity, then we expect phi < 0.33 bar + swcBulk = (swc_sat + swc_fc) / 2.; + EXPECT_LT( + SWRC_SWCtoSWP(swcBulk, swrc_type, swrcp, gravel, width, em), + 1. / 3. + ) << msg.str(); + + // if swc = field capacity, then we expect phi == 0.33 bar + EXPECT_NEAR( + SWRC_SWCtoSWP(swc_fc, swrc_type, swrcp, gravel, width, em), + 1. / 3., + tol9 + ) << msg.str(); + + // if field capacity > swc > wilting point, then + // we expect 15 bar > phi > 0.33 bar + swcBulk = (swc_wp + swc_fc) / 2.; + phi = SWRC_SWCtoSWP(swcBulk, swrc_type, swrcp, gravel, width, em); + EXPECT_GT(phi, 1. / 3.) << msg.str(); + EXPECT_LT(phi, 15.) << msg.str(); + + // if swc = wilting point, then we expect phi == 15 bar + EXPECT_NEAR( + SWRC_SWCtoSWP(swc_wp, swrc_type, swrcp, gravel, width, em), + 15., + tol9 + ) << msg.str(); + + // if swc < wilting point, then we expect phi > 15 bar + swcBulk = SWRC_SWPtoSWC(2. * 15., swrc_type, swrcp, gravel, width, em); + EXPECT_GT( + SWRC_SWCtoSWP(swcBulk, swrc_type, swrcp, gravel, width, em), + 15. + ) << msg.str(); + + + + //------ Tests SWP -> SWC + // when fractionGravel is 1, we expect theta == 0 + EXPECT_EQ( + SWRC_SWPtoSWC(15., swrc_type, swrcp, 1., width, em), + 0. + ) << msg.str(); + + // when width is 0, we expect theta == 0 + EXPECT_EQ( + SWRC_SWPtoSWC(15., swrc_type, swrcp, gravel, 0., em), + 0. + ) << msg.str(); + + // check bounds of swc + for (k = 0; k < length(swpsb); k++) { + swcBulk = SWRC_SWPtoSWC(swpsb[k], swrc_type, swrcp, gravel, width, em); + EXPECT_GE(swcBulk, 0.) << + msg.str() << " at SWP = " << swpsb[k] << " bar"; + EXPECT_LE(swcBulk, width * (1. - gravel)) << + msg.str() << " at SWP = " << swpsb[k] << " bar"; + } + + + //------ Tests that both SWP <-> SWC are inverse of each other + // for phi at 0 (saturation) and phi in [fc, infinity] + // but not necessarily if phi in ]0, fc[; + // for instance, Campbell1974 is not inverse in ]0, swrcp[0][ + for (k = 0; k < length(swpsi); k++) { + swcBulk = SWRC_SWPtoSWC(swpsi[k], swrc_type, swrcp, gravel, width, em); + swp = SWRC_SWCtoSWP(swcBulk, swrc_type, swrcp, gravel, width, em); + + EXPECT_NEAR(swp, swpsi[k], tol9) << + msg.str() << " at SWP = " << swpsi[k] << " bar"; + EXPECT_NEAR( + SWRC_SWPtoSWC(swp, swrc_type, swrcp, gravel, width, em), + swcBulk, + tol9 + ) << msg.str() << " at SWC = " << swcBulk << " cm"; + } + } + } + + + // Death Tests of 'SW_SoilWater' function 'SWRC_SWCtoSWP' + TEST(SoilWaterDeathTest, SWCtoSWP) { + // set up mock variables + RealD + swrcp[SWRC_PARAM_NMAX], + gravel = 0.1, + width = 10.; + + unsigned int swrc_type; + + + //--- we expect (non-)fatal errors in a few situations + // (fatality depends on the error mode) + + //--- 1) Unimplemented SWRC + swrc_type = N_SWRCs + 1; + EXPECT_DEATH_IF_SUPPORTED( + SWRC_SWCtoSWP(1., swrc_type, swrcp, gravel, width, LOGFATAL), + "is not implemented" + ); + EXPECT_DOUBLE_EQ( + SWRC_SWCtoSWP(1., swrc_type, swrcp, gravel, width, LOGWARN), + SW_MISSING + ); + + + // --- 2) swc < 0: water content cannot be negative + for (swrc_type = 0; swrc_type < N_SWRCs; swrc_type++) { + EXPECT_DEATH_IF_SUPPORTED( + SWRC_SWCtoSWP(-1., swrc_type, swrcp, gravel, width, LOGFATAL), + "invalid SWC" + ); + EXPECT_DOUBLE_EQ( + SWRC_SWCtoSWP(-1., swrc_type, swrcp, gravel, width, LOGWARN), + SW_MISSING + ); + + EXPECT_DEATH_IF_SUPPORTED( + SWRC_SWCtoSWP(1., swrc_type, swrcp, 1., width, LOGFATAL), + "invalid SWC" + ); + EXPECT_DOUBLE_EQ( + SWRC_SWCtoSWP(1., swrc_type, swrcp, 1., width, LOGWARN), + SW_MISSING + ); + + EXPECT_DEATH_IF_SUPPORTED( + SWRC_SWCtoSWP(1., swrc_type, swrcp, gravel, 0., LOGFATAL), + "invalid SWC" + ); + EXPECT_DOUBLE_EQ( + SWRC_SWCtoSWP(1., swrc_type, swrcp, gravel, 0., LOGWARN), + SW_MISSING + ); + } + + // --- *) if (theta - theta_res) < 0 (specific to vanGenuchten1980) + // note: this case is normally prevented due to SWC checks + swrc_type = encode_str2swrc((char *) "vanGenuchten1980"); + memset(swrcp, 0., SWRC_PARAM_NMAX * sizeof(swrcp[0])); + swrcp[0] = 0.1246; + swrcp[1] = 0.4445; + swrcp[2] = 0.0112; + swrcp[3] = 1.2673; + swrcp[4] = 7.78506; + + EXPECT_DEATH_IF_SUPPORTED( + SWRC_SWCtoSWP(0.99 * swrcp[0], swrc_type, swrcp, gravel, width, LOGFATAL), + "invalid value" + ); + EXPECT_DOUBLE_EQ( + SWRC_SWCtoSWP(0.99 * swrcp[0], swrc_type, swrcp, gravel, width, LOGWARN), + SW_MISSING + ); + } + + + // Death Tests of 'SW_SoilWater' function 'SWRC_SWPtoSWC' + TEST(SoilWaterDeathTest, SWPtoSWC) { + // set up mock variables + RealD + swrcp[SWRC_PARAM_NMAX], + gravel = 0.1, + width = 10.; + + unsigned int swrc_type; + + + //--- we expect (non-)fatal errors in two situations + // (fatality depends on the error mode) + + //--- 1) Unimplemented SWRC + swrc_type = N_SWRCs + 1; + EXPECT_DEATH_IF_SUPPORTED( + SWRC_SWPtoSWC(15., swrc_type, swrcp, gravel, width, LOGFATAL), + "is not implemented" + ); + EXPECT_DOUBLE_EQ( + SWRC_SWPtoSWC(15., swrc_type, swrcp, gravel, width, LOGWARN), + SW_MISSING + ); + + // --- 2) swp < 0: water content cannot be negative (any SWRC) + for (swrc_type = 0; swrc_type < N_SWRCs; swrc_type++) { + EXPECT_DEATH_IF_SUPPORTED( + SWRC_SWPtoSWC(-1., swrc_type, swrcp, gravel, width, LOGFATAL), + "invalid SWP" + ); + EXPECT_DOUBLE_EQ( + SWRC_SWPtoSWC(-1., swrc_type, swrcp, gravel, width, LOGWARN), + SW_MISSING + ); + } + } +} diff --git a/test/test_SW_VegEstab.cc b/tests/gtests/test_SW_VegEstab.cc similarity index 74% rename from test/test_SW_VegEstab.cc rename to tests/gtests/test_SW_VegEstab.cc index 43b405cd2..869dbba72 100644 --- a/test/test_SW_VegEstab.cc +++ b/tests/gtests/test_SW_VegEstab.cc @@ -1,10 +1,10 @@ #include "gtest/gtest.h" -#include "../generic.h" // for `Bool`, `swTRUE`, `swFALSE` -#include "../SW_Control.h" // for `SW_CTL_main()` -#include "../SW_VegEstab.h" // for `SW_VegEstab`, `SW_VES_read2()` +#include "include/generic.h" // for `Bool`, `swTRUE`, `swFALSE` +#include "include/SW_Control.h" // for `SW_CTL_main()` +#include "include/SW_VegEstab.h" // for `SW_VegEstab`, `SW_VES_read2()` -#include "sw_testhelpers.h" // for `Reset_SOILWAT2_after_UnitTest()` +#include "tests/gtests/sw_testhelpers.h" // for `Reset_SOILWAT2_after_UnitTest()` diff --git a/tests/gtests/test_SW_VegProd.cc b/tests/gtests/test_SW_VegProd.cc new file mode 100644 index 000000000..c10f25630 --- /dev/null +++ b/tests/gtests/test_SW_VegProd.cc @@ -0,0 +1,1235 @@ +#include "gtest/gtest.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "include/generic.h" +#include "include/myMemory.h" +#include "include/filefuncs.h" +#include "include/rands.h" +#include "include/Times.h" +#include "include/SW_Defines.h" +#include "include/SW_Times.h" +#include "include/SW_Files.h" +#include "include/SW_Carbon.h" +#include "include/SW_Site.h" +#include "include/SW_VegProd.h" +#include "include/SW_VegEstab.h" +#include "include/SW_Model.h" +#include "include/SW_SoilWater.h" +#include "include/SW_Weather.h" +#include "include/SW_Markov.h" +#include "include/SW_Sky.h" + +#include "tests/gtests/sw_testhelpers.h" + + + + +static void assert_decreasing_SWPcrit(void); +static void assert_decreasing_SWPcrit(void) +{ + int rank, vegtype; + + for (rank = 0; rank < NVEGTYPES - 1; rank++) + { + vegtype = SW_VegProd.rank_SWPcrits[rank]; + + /* + swprintf("Rank=%d is vegtype=%d with SWPcrit=%f\n", + rank, vegtype, + SW_VegProd.critSoilWater[vegtype]); + */ + + // Check that SWPcrit of `vegtype` is larger or equal to + // SWPcrit of the vegetation type with the next larger rank + ASSERT_GE( + SW_VegProd.critSoilWater[vegtype], + SW_VegProd.critSoilWater[SW_VegProd.rank_SWPcrits[rank + 1]]); + } +} + + +// Vegetation cover: see `estimatePotNatVegComposition()` +// RelAbundanceL0 and inputValues indices +int succIndex = 0; +int forbIndex = 1; +int C3Index = 2; +int C4Index = 3; +int grassAnn = 4; +int shrubIndex = 5; +int treeIndex = 6; +int bareGround = 7; + +// RelAbundanceL1 indices +int treeIndexL1 = 0; +int shrubIndexL1 = 1; +int forbIndexL1 = 2; +int grassesIndexL1 = 3; +int bareGroundL1 = 4; + + +static void copyL0(double outL0[], double inL0[]) { + for (int index = 0; index < 8; index++) { + outL0[index] = inL0[index]; + } +} + +static void calcVegCoverL1FromL0(double L1[], double L0[]) { + L1[treeIndexL1] = L0[treeIndex]; + L1[shrubIndexL1] = L0[shrubIndex]; + L1[forbIndexL1] = L0[forbIndex] + L0[succIndex]; + L1[grassesIndexL1] = L0[C3Index] + L0[C4Index] + L0[grassAnn]; + L1[bareGroundL1] = L0[bareGround]; +} + +static void calcGrassCoverFromL0(double grass[], double L0[]) { + double grass_sum = L0[C3Index] + L0[C4Index] + L0[grassAnn]; + + if (GT(grass_sum, 0.)) { + grass[0] = L0[C3Index] / grass_sum; + grass[1] = L0[C4Index] / grass_sum; + grass[2] = L0[grassAnn] / grass_sum; + } else { + grass[0] = 0.; + grass[1] = 0.; + grass[2] = 0.; + } +} + + +namespace { + SW_VEGPROD *v = &SW_VegProd; + int k; + + // Test the SW_VEGPROD constructor 'SW_VPD_construct' + TEST(VegTest, Constructor) { + SW_VPD_construct(); + SW_VPD_init_run(); + + ForEachVegType(k) { + EXPECT_DOUBLE_EQ(1., v->veg[k].co2_multipliers[BIO_INDEX][0]); + EXPECT_DOUBLE_EQ(1., v->veg[k].co2_multipliers[BIO_INDEX][MAX_NYEAR - 1]); + + EXPECT_DOUBLE_EQ(1., v->veg[k].co2_multipliers[WUE_INDEX][0]); + EXPECT_DOUBLE_EQ(1., v->veg[k].co2_multipliers[WUE_INDEX][MAX_NYEAR - 1]); + } + + // Reset to previous global state + Reset_SOILWAT2_after_UnitTest(); + } + + + // Test the application of the biomass CO2-effect + TEST(VegTest, BiomassCO2effect) { + int i; + double x; + double biom1[12], biom2[12]; + + for (i = 0; i < 12; i++) { + biom1[i] = i + 1.; + } + + // One example + x = v->veg[SW_GRASS].co2_multipliers[BIO_INDEX][SW_Model.startyr + SW_Model.addtl_yr]; + apply_biomassCO2effect(biom2, biom1, x); + + for (i = 0; i < 12; i++) { + EXPECT_DOUBLE_EQ(biom2[i], biom1[i] * x); + } + + // Reset to previous global state + Reset_SOILWAT2_after_UnitTest(); + } + + + // Test summing values across vegetation types + TEST(VegTest, Summing) { + double x0[NVEGTYPES] = {0.}; + double x1[NVEGTYPES] = {0.25, 0.25, 0.25, 0.25}; + + EXPECT_DOUBLE_EQ(sum_across_vegtypes(x0), 0.); + EXPECT_DOUBLE_EQ(sum_across_vegtypes(x1), 1.); + } + + + // Check `get_critical_rank` + TEST(VegTest, rank) { + int k; + // Check `get_critical_rank` for normal inputs, e.g., -2.0, -2.0, -3.5, -3.9 + get_critical_rank(); + assert_decreasing_SWPcrit(); + + + // Check `get_critical_rank` for constant values + ForEachVegType(k) + { + SW_VegProd.critSoilWater[k] = 0.; + } + + get_critical_rank(); + assert_decreasing_SWPcrit(); + + + // Check `get_critical_rank` for increasing values + ForEachVegType(k) + { + SW_VegProd.critSoilWater[k] = k; + } + + get_critical_rank(); + assert_decreasing_SWPcrit(); + + + // Check `get_critical_rank` for decreasing values + ForEachVegType(k) + { + SW_VegProd.critSoilWater[k] = NVEGTYPES - k; + } + + get_critical_rank(); + assert_decreasing_SWPcrit(); + + + // Reset to previous global state + Reset_SOILWAT2_after_UnitTest(); + } + + TEST(EstimateVegetationTest, NotFullVegetation) { + + /* ================================================================ + This block of tests deals with input values to + `estimatePotNatVegComposition()` that do not add up to 1 + + NOTE: Some tests use EXPECT_NEAR to cover for the unnecessary precision + in results + ================================================================ */ + + + SW_CLIMATE_YEARLY climateOutput; + SW_CLIMATE_CLIM climateAverages; + SW_VEGPROD vegProd; + + double inputValues[8]; + double shrubLimit = .2; + + // Array holding only grass values + double grassOutput[3]; // 3 = Number of grass variables + + // Array holding all values from the estimation + double RelAbundanceL0[8]; // 8 = Number of types + + // Array holding all values from estimation minus grasses + double RelAbundanceL1[5]; // 5 = Number of types minus grasses + + double SumGrassesFraction = SW_MISSING; + double C4Variables[3]; + + int startYear = 1980; + int endYear = 2010; + int veg_method = 1; + double latitude = 90.0; + + Bool fillEmptyWithBareGround = swTRUE; + Bool warnExtrapolation = swTRUE; + Bool inNorthHem = swTRUE; + Bool fixBareGround = swTRUE; + + int nTypes = 8; + int index; + + + double RelAbundanceL0Expected[8]; + double RelAbundanceL1Expected[5]; + double grassOutputExpected[3]; + + + // Reset "SW_Weather.allHist" + SW_WTH_read(); + finalizeAllWeather(&SW_Weather); + + // Allocate arrays needed for `calcSiteClimate()` and `averageClimateAcrossYears()` + allocateClimateStructs(31, &climateOutput, &climateAverages); + + // Calculate climate of the site and add results to "climateOutput" + calcSiteClimate(SW_Weather.allHist, 31, 1980, inNorthHem, &climateOutput); + + // Average values from "climateOutput" and put them in "climateAverages" + averageClimateAcrossYears(&climateOutput, 31, &climateAverages); + + // Set C4 results, standard deviations are not needed for estimating vegetation + C4Variables[0] = climateAverages.minTemp7thMon_C; + C4Variables[1] = climateAverages.ddAbove65F_degday; + C4Variables[2] = climateAverages.frostFree_days; + + + /* =============================================================== + Test when all input values are "SW_MISSING" + =============================================================== */ + inputValues[succIndex] = SW_MISSING; + inputValues[forbIndex] = SW_MISSING; + inputValues[C3Index] = SW_MISSING; + inputValues[C4Index] = SW_MISSING; + inputValues[grassAnn] = SW_MISSING; + inputValues[shrubIndex] = SW_MISSING; + inputValues[treeIndex] = SW_MISSING; + inputValues[bareGround] = SW_MISSING; + + /* Expect identical output to rSOILWAT2 (e.g., v5.3.1) + * NOTE: Command uses deprecated estimate_PotNatVeg_composition (rSOILWAT >= v.6.0.0) + ```{r} + clim1 <- calc_SiteClimate(weatherList = + rSOILWAT2::get_WeatherHistory(rSOILWAT2::sw_exampleData), + do_C4vars = TRUE) + + rSOILWAT2:::estimate_PotNatVeg_composition_old( + MAP_mm = 10 * clim1[["MAP_cm"]], MAT_C = clim1[["MAT_C"]], + mean_monthly_ppt_mm = 10 * clim1[["meanMonthlyPPTcm"]], + mean_monthly_Temp_C = clim1[["meanMonthlyTempC"]], + dailyC4vars = clim1[["dailyC4vars"]], + fix_issue218 = TRUE + ) + ``` + */ + + // Set or calculate expected outputs + copyL0(RelAbundanceL0Expected, inputValues); + RelAbundanceL0Expected[succIndex] = 0.0; + RelAbundanceL0Expected[forbIndex] = 0.2608391; + RelAbundanceL0Expected[C3Index] = 0.4307061; + RelAbundanceL0Expected[C4Index] = 0.0; + RelAbundanceL0Expected[grassAnn] = 0.0; + RelAbundanceL0Expected[shrubIndex] = 0.3084547; + RelAbundanceL0Expected[treeIndex] = 0.0; + RelAbundanceL0Expected[bareGround] = 0.0; + + calcVegCoverL1FromL0(RelAbundanceL1Expected, RelAbundanceL0Expected); + calcGrassCoverFromL0(grassOutputExpected, RelAbundanceL0Expected); + + + // Estimate vegetation + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, + fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); + + + // Loop through RelAbundanceL0 and test results + for(index = 0; index < nTypes; index++) { + EXPECT_NEAR(RelAbundanceL0[index], RelAbundanceL0Expected[index], tol6); + } + + // Loop through RelAbundanceL1 and test results + for(index = 0; index < 5; index++) { + EXPECT_NEAR(RelAbundanceL1[index], RelAbundanceL1Expected[index], tol6); + } + + // Loop through grassOutput and test results + for(index = 0; index < 3; index++) { + EXPECT_NEAR(grassOutput[index], grassOutputExpected[index], tol6); + } + + + /* =============================================================== + Test with some of input values not "SW_MISSING" + =============================================================== */ + + // estimate cover of forbs and C4 grasses; fix all other + inputValues[succIndex] = .376; + inputValues[forbIndex] = SW_MISSING; + inputValues[C3Index] = .096; + inputValues[C4Index] = SW_MISSING; + inputValues[grassAnn] = 0.; + inputValues[shrubIndex] = .1098; + inputValues[treeIndex] = .0372; + inputValues[bareGround] = 0.; + + /* Expect identical output to rSOILWAT2 (e.g., v5.3.1) + * NOTE: Command uses deprecated estimate_PotNatVeg_composition (rSOILWAT >= v.6.0.0) + ```{r} + clim1 <- calc_SiteClimate(weatherList = + rSOILWAT2::get_WeatherHistory(rSOILWAT2::sw_exampleData), + do_C4vars = TRUE) + + rSOILWAT2:::estimate_PotNatVeg_composition_old( + MAP_mm = 10 * clim1[["MAP_cm"]], MAT_C = clim1[["MAT_C"]], + mean_monthly_ppt_mm = 10 * clim1[["meanMonthlyPPTcm"]], + mean_monthly_Temp_C = clim1[["meanMonthlyTempC"]], + Succulents_Fraction = .376, fix_succulents = TRUE, + C3_Fraction = .096, fix_C3grasses = TRUE, + Shrubs_Fraction = .1098, fix_shrubs = TRUE, + Trees_Fraction = .0372, fix_trees = TRUE, + dailyC4vars = clim1[["dailyC4vars"]], + fix_issue218 = TRUE + ) + ``` + */ + + // Set or calculate expected outputs + copyL0(RelAbundanceL0Expected, inputValues); + RelAbundanceL0Expected[forbIndex] = .3810; + RelAbundanceL0Expected[C4Index] = 0.; + + calcVegCoverL1FromL0(RelAbundanceL1Expected, RelAbundanceL0Expected); + calcGrassCoverFromL0(grassOutputExpected, RelAbundanceL0Expected); + + + // Estimate vegetation + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, + fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); + + // Loop through RelAbundanceL0 and test results + for(index = 0; index < 8; index++) { + EXPECT_DOUBLE_EQ(RelAbundanceL0[index], RelAbundanceL0Expected[index]); + } + + // Loop through RelAbundanceL1 and test results + for(index = 0; index < 5; index++) { + EXPECT_DOUBLE_EQ(RelAbundanceL1[index], RelAbundanceL1Expected[index]); + } + + // Loop through grassOutput and test results + for(index = 0; index < 3; index++) { + EXPECT_NEAR(grassOutput[index], grassOutputExpected[index], tol6); + } + + + /* =============================================================== + Test with all input values not "SW_MISSING" + =============================================================== */ + + inputValues[succIndex] = .1098; + inputValues[forbIndex] = .1098; + inputValues[C3Index] = .1098; + inputValues[C4Index] = .1098; + inputValues[grassAnn] = .1098; + inputValues[shrubIndex] = .1098; + inputValues[treeIndex] = .1098; + inputValues[bareGround] = .1098; + + /* Expect identical output to rSOILWAT2 (e.g., v5.3.1) + * NOTE: Command uses deprecated estimate_PotNatVeg_composition (rSOILWAT >= v.6.0.0) + ```{r} + clim1 <- calc_SiteClimate(weatherList = + rSOILWAT2::get_WeatherHistory(rSOILWAT2::sw_exampleData), + do_C4vars = TRUE) + + rSOILWAT2:::estimate_PotNatVeg_composition_old( + MAP_mm = 10 * clim1[["MAP_cm"]], MAT_C = clim1[["MAT_C"]], + mean_monthly_ppt_mm = 10 * clim1[["meanMonthlyPPTcm"]], + mean_monthly_Temp_C = clim1[["meanMonthlyTempC"]], + Succulents_Fraction = .1098, fix_succulents = TRUE, + C3_Fraction = .1098, fix_C3grasses = TRUE, + Shrubs_Fraction = .1098, fix_shrubs = TRUE, + Trees_Fraction = .1098, fix_trees = TRUE, + Annuals_Fraction = .1098, fix_annuals = TRUE, + C4_Fraction = .1098, fix_C4grasses = TRUE, + Forbs_Fraction = .1098, fix_forbs = TRUE, + BareGround_Fraction = .1098, fix_BareGround = TRUE, + dailyC4vars = clim1[["dailyC4vars"]], + fix_issue218 = TRUE + ) + ``` + */ + + // Set or calculate expected outputs + copyL0(RelAbundanceL0Expected, inputValues); + + // RelAbundanceL0Expected[bareGround] is not .1098 because + // fillEmptyWithBareGround = swTRUE + RelAbundanceL0Expected[bareGround] = 0.2314; + + calcVegCoverL1FromL0(RelAbundanceL1Expected, RelAbundanceL0Expected); + calcGrassCoverFromL0(grassOutputExpected, RelAbundanceL0Expected); + + + // Estimate vegetation + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, + fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); + + // Loop through RelAbundanceL0 and test results. + for(index = 0; index < nTypes; index++) { + EXPECT_DOUBLE_EQ(RelAbundanceL0[index], RelAbundanceL0Expected[index]); + } + + // Loop through RelAbundanceL1 and test results + for(index = 0; index < 5; index++) { + EXPECT_DOUBLE_EQ(RelAbundanceL1[index], RelAbundanceL1Expected[index]); + } + + // Loop through grassOutput and test results + for(index = 0; index < 3; index++) { + EXPECT_NEAR(grassOutput[index], grassOutputExpected[index], tol6); + } + + + + /* =============================================================== + Test `estimateVegetationFromClimate()` when "veg_method" is 1 + using default values of the function: + [SW_MISSING, SW_MISSING, SW_MISSING, SW_MISSING, 0.0, SW_MISSING, 0.0, 0.0] + =============================================================== */ + + /* Expect identical output to rSOILWAT2 (e.g., v5.3.1) + * NOTE: Command uses deprecated estimate_PotNatVeg_composition (rSOILWAT >= v.6.0.0) + ```{r} + clim1 <- calc_SiteClimate(weatherList = + rSOILWAT2::get_WeatherHistory(rSOILWAT2::sw_exampleData), + do_C4vars = TRUE) + + rSOILWAT2:::estimate_PotNatVeg_composition_old( + MAP_mm = 10 * clim1[["MAP_cm"]], MAT_C = clim1[["MAT_C"]], + mean_monthly_ppt_mm = 10 * clim1[["meanMonthlyPPTcm"]], + mean_monthly_Temp_C = clim1[["meanMonthlyTempC"]], + dailyC4vars = clim1[["dailyC4vars"]], + fix_issue218 = TRUE + ) + ``` + */ + + RelAbundanceL1Expected[treeIndexL1] = 0.; + RelAbundanceL1Expected[shrubIndexL1] = .3084547; + RelAbundanceL1Expected[forbIndexL1] = .2608391; // Constains forbs + succulents (L0) + RelAbundanceL1Expected[grassesIndexL1] = .4307061; + RelAbundanceL1Expected[bareGroundL1] = 0.; + + + estimateVegetationFromClimate(&vegProd, startYear, endYear, veg_method, latitude); + + // Loop through RelAbundanceL1 and test results + for(index = 0; index < 4; index++) { + EXPECT_NEAR(vegProd.veg[index].cov.fCover, RelAbundanceL1Expected[index], tol6); + } + + EXPECT_NEAR(vegProd.bare_cov.fCover, RelAbundanceL1Expected[bareGroundL1], tol6); + + + + /* =============================================================== + Tests for southern hemisphere: + + 1) Same input values as previous test except for trees and bare ground which + are both .0549 + + 2) Default input values: + [SW_MISSING, SW_MISSING, SW_MISSING, SW_MISSING, 0.0, SW_MISSING, 0.0, 0.0] + yielding different values in southern hemisphere compared to northern hemisphere + =============================================================== */ + + // Recalculate climate of the site in southern hemisphere and add results to "climateOutput" + inNorthHem = swFALSE; + calcSiteClimate(SW_Weather.allHist, 31, 1980, inNorthHem, &climateOutput); + + + inputValues[treeIndex] = .0549; + inputValues[bareGround] = .0549; + + /* Expect identical output to rSOILWAT2 (e.g., v5.3.1) + * NOTE: Command uses deprecated estimate_PotNatVeg_composition (rSOILWAT >= v.6.0.0) + ```{r} + clim1 <- calc_SiteClimate(weatherList = + rSOILWAT2::get_WeatherHistory(rSOILWAT2::sw_exampleData), + do_C4vars = TRUE, latitude = -90) + + rSOILWAT2:::estimate_PotNatVeg_composition_old( + MAP_mm = 10 * clim1[["MAP_cm"]], MAT_C = clim1[["MAT_C"]], + mean_monthly_ppt_mm = 10 * clim1[["meanMonthlyPPTcm"]], + mean_monthly_Temp_C = clim1[["meanMonthlyTempC"]], + Succulents_Fraction = .1098, fix_succulents = TRUE, + Forbs_Fraction = .1098, fix_forbs = TRUE, + C3_Fraction = .1098, fix_C3grasses = TRUE, + C4_Fraction = .1098, fix_C4grasses = TRUE, + Annuals_Fraction = .1098, fix_annuals = TRUE, + Shrubs_Fraction = .1098, fix_shrubs = TRUE, + Trees_Fraction = 0.0549, fix_trees = TRUE, + BareGround_Fraction = .0549, fix_BareGround = TRUE, + isNorth = FALSE, dailyC4vars = clim1[["dailyC4vars"]], + fix_issue218 = TRUE + ) + ``` + */ + + // Set or calculate expected outputs + copyL0(RelAbundanceL0Expected, inputValues); + RelAbundanceL0Expected[bareGround] = .2863; + + calcVegCoverL1FromL0(RelAbundanceL1Expected, RelAbundanceL0Expected); + calcGrassCoverFromL0(grassOutputExpected, RelAbundanceL0Expected); + + + // Estimate vegetation + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, + fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); + + // Loop through RelAbundanceL0 and test results. + for(index = 0; index < 8; index++) { + EXPECT_DOUBLE_EQ(RelAbundanceL0[index], RelAbundanceL0Expected[index]); + } + + // Loop through RelAbundanceL1 and test results + for(index = 0; index < 5; index++) { + EXPECT_DOUBLE_EQ(RelAbundanceL1[index], RelAbundanceL1Expected[index]); + } + + // Loop through grassOutput and test results + for(index = 0; index < 3; index++) { + EXPECT_NEAR(grassOutput[index], grassOutputExpected[index], tol6); + } + + + + /* =============================================================== + Test "C4Variables" not being defined (faked by setting july min (index zero) to SW_MISSING) + Use southern hemisphere for clear difference in values (C4 is/isn't defined) + Use default values: + [SW_MISSING, SW_MISSING, SW_MISSING, SW_MISSING, 0.0, SW_MISSING, 0.0, 0.0] + =============================================================== */ + + inputValues[succIndex] = SW_MISSING; + inputValues[forbIndex] = SW_MISSING; + inputValues[C3Index] = SW_MISSING; + inputValues[C4Index] = SW_MISSING; + inputValues[grassAnn] = 0.; + inputValues[shrubIndex] = SW_MISSING; + inputValues[treeIndex] = 0.; + inputValues[bareGround] = 0.; + + C4Variables[0] = SW_MISSING; + + /* Expect identical output to rSOILWAT2 (e.g., v5.3.1) + * NOTE: Command uses deprecated estimate_PotNatVeg_composition (rSOILWAT >= v.6.0.0) + ```{r} + clim1 <- calc_SiteClimate(weatherList = + rSOILWAT2::get_WeatherHistory(rSOILWAT2::sw_exampleData), + do_C4vars = TRUE, latitude = -90) + + rSOILWAT2:::estimate_PotNatVeg_composition_old( + MAP_mm = 10 * clim1[["MAP_cm"]], MAT_C = clim1[["MAT_C"]], + mean_monthly_ppt_mm = 10 * clim1[["meanMonthlyPPTcm"]], + mean_monthly_Temp_C = clim1[["meanMonthlyTempC"]], + isNorth = FALSE, + fix_issue218 = FALSE + ) + ``` + */ + + // Set or calculate expected outputs + copyL0(RelAbundanceL0Expected, inputValues); + RelAbundanceL0Expected[succIndex] = 0.; + RelAbundanceL0Expected[forbIndex] = .22804606; + RelAbundanceL0Expected[C3Index] = .52575060; + RelAbundanceL0Expected[C4Index] = .15766932; + RelAbundanceL0Expected[grassAnn] = 0.; + RelAbundanceL0Expected[shrubIndex] = .08853402; + RelAbundanceL0Expected[treeIndex] = 0.; + RelAbundanceL0Expected[bareGround] = 0.; + + calcVegCoverL1FromL0(RelAbundanceL1Expected, RelAbundanceL0Expected); + calcGrassCoverFromL0(grassOutputExpected, RelAbundanceL0Expected); + + + // Estimate vegetation + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, + fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); + + + // Loop through RelAbundanceL0 and test results. + for(index = 0; index < nTypes; index++) { + EXPECT_NEAR(RelAbundanceL0[index], RelAbundanceL0Expected[index], tol6); + } + + // Loop through RelAbundanceL1 and test results + for(index = 0; index < 5; index++) { + EXPECT_NEAR(RelAbundanceL1[index], RelAbundanceL1Expected[index], tol6); + } + + // Loop through grassOutput and test results + for(index = 0; index < 3; index++) { + EXPECT_NEAR(grassOutput[index], grassOutputExpected[index], tol6); + } + + + // Deallocate structs + deallocateClimateStructs(&climateOutput, &climateAverages); + } + + TEST(EstimateVegetationTest, FullVegetation) { + + /* ================================================================ + This block of tests deals with input values to + `estimatePotNatVegComposition()` that add up to 1 + + NOTE: Some tests use EXPECT_NEAR to cover for the unnecessary precision + in results + ================================================================ */ + + SW_CLIMATE_YEARLY climateOutput; + SW_CLIMATE_CLIM climateAverages; + + int index; + int nTypes = 8; + + double inputValues[8]; + double shrubLimit = .2; + + // Array holding only grass values + double grassOutput[3]; // 3 = Number of grass variables + + // Array holding all values from the estimation + double RelAbundanceL0[8]; // 8 = Number of types + + // Array holding all values from estimation minus grasses + double RelAbundanceL1[5]; // 5 = Number of types minus grasses + + double SumGrassesFraction = SW_MISSING; + double C4Variables[3]; + double RelAbundanceL0Expected[8]; + double RelAbundanceL1Expected[5]; + double grassOutputExpected[3]; + + Bool fillEmptyWithBareGround = swTRUE; + Bool inNorthHem = swTRUE; + Bool warnExtrapolation = swTRUE; + Bool fixBareGround = swTRUE; + + + // Reset "SW_Weather.allHist" + SW_WTH_read(); + finalizeAllWeather(&SW_Weather); + + // Allocate arrays needed for `calcSiteClimate()` and `averageClimateAcrossYears()` + allocateClimateStructs(31, &climateOutput, &climateAverages); + + // Calculate climate of the site and add results to "climateOutput" + calcSiteClimate(SW_Weather.allHist, 31, 1980, inNorthHem, &climateOutput); + + // Average values from "climateOutput" and put them in "climateAverages" + averageClimateAcrossYears(&climateOutput, 31, &climateAverages); + + // Set C4 results, standard deviations are not needed for estimating vegetation + C4Variables[0] = climateAverages.minTemp7thMon_C; + C4Variables[1] = climateAverages.ddAbove65F_degday; + C4Variables[2] = climateAverages.frostFree_days; + + + /* =============================================================== + Test when fixed inputs sum to 1 & all inputs are fixed + Expect that outputs == inputs + =============================================================== */ + inputValues[succIndex] = .0567; + inputValues[forbIndex] = .2317; + inputValues[C3Index] = .0392; + inputValues[C4Index] = .0981; + inputValues[grassAnn] = .3218; + inputValues[shrubIndex] = .0827; + inputValues[treeIndex] = .1293; + inputValues[bareGround] = .0405; + + // Set or calculate expected outputs + copyL0(RelAbundanceL0Expected, inputValues); + + calcVegCoverL1FromL0(RelAbundanceL1Expected, RelAbundanceL0Expected); + calcGrassCoverFromL0(grassOutputExpected, RelAbundanceL0Expected); + + + // Estimate vegetation + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, + fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); + + + // Loop through RelAbundanceL0 and test results. + for(index = 0; index < nTypes; index++) { + // All values in "RelAbundanceL0" should be exactly the same as "inputValues" + EXPECT_DOUBLE_EQ(RelAbundanceL0[index], inputValues[index]); + EXPECT_NEAR(RelAbundanceL0[index], RelAbundanceL0Expected[index], tol3); + } + + // Loop through RelAbundanceL1 and test results + for(index = 0; index < 5; index++) { + EXPECT_DOUBLE_EQ(RelAbundanceL1[index], RelAbundanceL1Expected[index]); + } + + // Loop through grassOutput and test results + for(index = 0; index < 3; index++) { + EXPECT_DOUBLE_EQ(grassOutput[index], grassOutputExpected[index]); + } + + + + /* =============================================================== + Test when fixed inputs sum to 1 & some inputs are not fixed + =============================================================== */ + inputValues[succIndex] = .5; + inputValues[forbIndex] = SW_MISSING; + inputValues[C3Index] = .5; + inputValues[C4Index] = SW_MISSING; + inputValues[grassAnn] = 0.; + inputValues[shrubIndex] = SW_MISSING; + inputValues[treeIndex] = 0.; + inputValues[bareGround] = 0.; + + /* Expect identical output to rSOILWAT2 (e.g., v5.3.1) + * NOTE: Command uses deprecated estimate_PotNatVeg_composition (rSOILWAT >= v.6.0.0) + ```{r} + clim1 <- calc_SiteClimate(weatherList = + rSOILWAT2::get_WeatherHistory(rSOILWAT2::sw_exampleData), + do_C4vars = TRUE) + + rSOILWAT2:::estimate_PotNatVeg_composition_old( + MAP_mm = 10 * clim1[["MAP_cm"]], MAT_C = clim1[["MAT_C"]], + mean_monthly_ppt_mm = 10 * clim1[["meanMonthlyPPTcm"]], + mean_monthly_Temp_C = clim1[["meanMonthlyTempC"]], + Succulents_Fraction = .5, fix_succulents = TRUE, + C3_Fraction = .5, fix_C3grasses = TRUE, + dailyC4vars = clim1[["dailyC4vars"]], + fix_issue218 = TRUE + ) + ``` + */ + + // Set or calculate expected outputs + copyL0(RelAbundanceL0Expected, inputValues); + RelAbundanceL0Expected[forbIndex] = 0.; + RelAbundanceL0Expected[C4Index] = 0.; + RelAbundanceL0Expected[shrubIndex] = 0.; + + calcVegCoverL1FromL0(RelAbundanceL1Expected, RelAbundanceL0Expected); + calcGrassCoverFromL0(grassOutputExpected, RelAbundanceL0Expected); + + + // Estimate vegetation + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, + fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); + + + // Loop through RelAbundanceL0 and test results. + for(index = 0; index < nTypes; index++) { + EXPECT_DOUBLE_EQ(RelAbundanceL0[index], RelAbundanceL0Expected[index]); + } + + // Loop through RelAbundanceL1 and test results + for(index = 0; index < 5; index++) { + EXPECT_DOUBLE_EQ(RelAbundanceL1[index], RelAbundanceL1Expected[index]); + } + + // Loop through grassOutput and test results + for(index = 0; index < 3; index++) { + EXPECT_DOUBLE_EQ(grassOutput[index], grassOutputExpected[index]); + } + + + /* =============================================================== + Test with `fillEmptyWithBareGround` set to false, same input values + as previous test except for bare ground, which is now .2314 + =============================================================== */ + fillEmptyWithBareGround = swFALSE; + + inputValues[succIndex] = .1098; + inputValues[forbIndex] = .1098; + inputValues[C3Index] = .1098; + inputValues[C4Index] = .1098; + inputValues[grassAnn] = .1098; + inputValues[shrubIndex] = .1098; + inputValues[treeIndex] = .1098; + inputValues[bareGround] = .2314; + + /* Expect identical output to rSOILWAT2 (e.g., v5.3.1) + * NOTE: Command uses deprecated estimate_PotNatVeg_composition (rSOILWAT >= v.6.0.0) + ```{r} + clim1 <- calc_SiteClimate(weatherList = + rSOILWAT2::get_WeatherHistory(rSOILWAT2::sw_exampleData), + do_C4vars = TRUE) + + rSOILWAT2:::estimate_PotNatVeg_composition_old( + MAP_mm = 10 * clim1[["MAP_cm"]], MAT_C = clim1[["MAT_C"]], + mean_monthly_ppt_mm = 10 * clim1[["meanMonthlyPPTcm"]], + mean_monthly_Temp_C = clim1[["meanMonthlyTempC"]], + Succulents_Fraction = .1098, fix_succulents = TRUE, + C3_Fraction = .1098, fix_C3grasses = TRUE, + Shrubs_Fraction = .1098, fix_shrubs = TRUE, + Trees_Fraction = .1098, fix_trees = TRUE, + Annuals_Fraction = .1098, fix_annuals = TRUE, + C4_Fraction = .1098, fix_C4grasses = TRUE, + Forbs_Fraction = .1098, fix_forbs = TRUE, + BareGround_Fraction = 0.2314, fix_BareGround = TRUE, + fill_empty_with_BareGround = TRUE, + dailyC4vars = clim1[["dailyC4vars"]], + fix_issue218 = TRUE + ) + ``` + */ + + // Set or calculate expected outputs + copyL0(RelAbundanceL0Expected, inputValues); + + calcVegCoverL1FromL0(RelAbundanceL1Expected, RelAbundanceL0Expected); + calcGrassCoverFromL0(grassOutputExpected, RelAbundanceL0Expected); + + + // Estimate vegetation + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, + fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); + + // Loop through RelAbundanceL0 and test results. + for(index = 0; index < nTypes; index++) { + EXPECT_DOUBLE_EQ(RelAbundanceL0[index], RelAbundanceL0Expected[index]); + } + + // Loop through RelAbundanceL1 and test results + for(index = 0; index < 5; index++) { + EXPECT_DOUBLE_EQ(RelAbundanceL1[index], RelAbundanceL1Expected[index]); + } + + // Loop through grassOutput and test results + for(index = 0; index < 3; index++) { + EXPECT_DOUBLE_EQ(grassOutput[index], grassOutputExpected[index]); + } + + + /* =============================================================== + Test with `SumGrassesFraction` being fixed, all input of previous tests + are halved to .0549 + =============================================================== */ + + SumGrassesFraction = .7255; + + inputValues[succIndex] = .0549; + inputValues[forbIndex] = .0549; + inputValues[C3Index] = SW_MISSING; + inputValues[C4Index] = SW_MISSING; + inputValues[grassAnn] = 0.; + inputValues[shrubIndex] = .0549; + inputValues[treeIndex] = .0549; + inputValues[bareGround] = .0549; + + /* Expect identical output to rSOILWAT2 (e.g., v5.3.1) + * NOTE: Command uses deprecated estimate_PotNatVeg_composition (rSOILWAT >= v.6.0.0) + ```{r} + clim1 <- calc_SiteClimate(weatherList = + rSOILWAT2::get_WeatherHistory(rSOILWAT2::sw_exampleData), + do_C4vars = TRUE) + + rSOILWAT2:::estimate_PotNatVeg_composition_old( + MAP_mm = 10 * clim1[["MAP_cm"]], MAT_C = clim1[["MAT_C"]], + mean_monthly_ppt_mm = 10 * clim1[["meanMonthlyPPTcm"]], + mean_monthly_Temp_C = clim1[["meanMonthlyTempC"]], + Succulents_Fraction = .0549, fix_succulents = TRUE, + Forbs_Fraction = .0549, fix_forbs = TRUE, + Shrubs_Fraction = .0549, fix_shrubs = TRUE, + Trees_Fraction = .0549, fix_trees = TRUE, + SumGrasses_Fraction = .7255, fix_sumgrasses = TRUE, + BareGround_Fraction = .0549, fix_BareGround = TRUE, + dailyC4vars = clim1[["dailyC4vars"]], + fix_issue218 = TRUE + ) + ``` + */ + + // Set or calculate expected outputs + copyL0(RelAbundanceL0Expected, inputValues); + RelAbundanceL0Expected[C3Index] = .7255; + RelAbundanceL0Expected[C4Index] = 0.; + + calcVegCoverL1FromL0(RelAbundanceL1Expected, RelAbundanceL0Expected); + calcGrassCoverFromL0(grassOutputExpected, RelAbundanceL0Expected); + + + // Estimate vegetation + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, + fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); + + // Loop through RelAbundanceL0 and test results. + for(index = 0; index < nTypes; index++) { + EXPECT_DOUBLE_EQ(RelAbundanceL0[index], RelAbundanceL0Expected[index]); + } + + // Loop through RelAbundanceL1 and test results + for(index = 0; index < 5; index++) { + EXPECT_DOUBLE_EQ(RelAbundanceL1[index], RelAbundanceL1Expected[index]); + } + + // Loop through grassOutput and test results + for(index = 0; index < 3; index++) { + EXPECT_DOUBLE_EQ(grassOutput[index], grassOutputExpected[index]); + } + + // Expect that sum of grass cover is equal to requested `SumGrassesFraction` + EXPECT_NEAR(RelAbundanceL1[grassesIndexL1], SumGrassesFraction, tol6); + + + + /* =============================================================== + Test where one input value is fixed at 1 and 5/7 are fixed to 0, + with the rest being SW_MISSING (C3 and C4 values), and `SumGrassesFraction` + is set to 0 + =============================================================== */ + + SumGrassesFraction = 0.; + + inputValues[succIndex] = 0.; + inputValues[forbIndex] = 0.; + inputValues[C3Index] = SW_MISSING; + inputValues[C4Index] = SW_MISSING; + inputValues[grassAnn] = 0.; + inputValues[shrubIndex] = 1.; + inputValues[treeIndex] = 0.; + inputValues[bareGround] = 0.; + + /* Expect identical output to rSOILWAT2 (e.g., v5.3.1) + * NOTE: Command uses deprecated estimate_PotNatVeg_composition (rSOILWAT >= v.6.0.0) + ```{r} + clim1 <- calc_SiteClimate(weatherList = + rSOILWAT2::get_WeatherHistory(rSOILWAT2::sw_exampleData), + do_C4vars = TRUE) + + rSOILWAT2:::estimate_PotNatVeg_composition_old( + MAP_mm = 10 * clim1[["MAP_cm"]], MAT_C = clim1[["MAT_C"]], + mean_monthly_ppt_mm = 10 * clim1[["meanMonthlyPPTcm"]], + mean_monthly_Temp_C = clim1[["meanMonthlyTempC"]], + Succulents_Fraction = 0, fix_succulents = TRUE, + Forbs_Fraction = 0, fix_forbs = TRUE, + Shrubs_Fraction = 1, fix_shrubs = TRUE, + Trees_Fraction = 0, fix_trees = TRUE, + SumGrasses_Fraction = 0, fix_sumgrasses = TRUE, + BareGround_Fraction = 0, fix_BareGround = TRUE, + dailyC4vars = clim1[["dailyC4vars"]], + fix_issue218 = TRUE, fix_issue219 = TRUE + ) + ``` + */ + + // Set or calculate expected outputs + copyL0(RelAbundanceL0Expected, inputValues); + RelAbundanceL0Expected[C3Index] = 0.; + RelAbundanceL0Expected[C4Index] = 0.; + + calcVegCoverL1FromL0(RelAbundanceL1Expected, RelAbundanceL0Expected); + calcGrassCoverFromL0(grassOutputExpected, RelAbundanceL0Expected); + + + // Estimate vegetation + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, + fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); + + // Loop through RelAbundanceL0 and test results. + for(index = 0; index < nTypes; index++) { + EXPECT_DOUBLE_EQ(RelAbundanceL0[index], RelAbundanceL0Expected[index]); + } + + // Loop through RelAbundanceL1 and test results + for(index = 0; index < 5; index++) { + EXPECT_DOUBLE_EQ(RelAbundanceL1[index], RelAbundanceL1Expected[index]); + } + + // Loop through grassOutput and test results + for(index = 0; index < 3; index++) { + EXPECT_DOUBLE_EQ(grassOutput[index], grassOutputExpected[index]); + } + + // Expect that sum of grass cover is equal to requested `SumGrassesFraction` + EXPECT_NEAR(RelAbundanceL1[grassesIndexL1], SumGrassesFraction, tol6); + + + + /* =============================================================== + Test when input sum is 1, including `SumGrassesFraction`, and + grass needs to be estimated + =============================================================== */ + + SumGrassesFraction = .5; + + inputValues[succIndex] = 0.; + inputValues[forbIndex] = 0.; + inputValues[C3Index] = SW_MISSING; + inputValues[C4Index] = SW_MISSING; + inputValues[grassAnn] = 0.; + inputValues[shrubIndex] = 0.; + inputValues[treeIndex] = 0.; + inputValues[bareGround] = .5; + + /* Expect identical output to rSOILWAT2 (e.g., v5.3.1) + * NOTE: Command uses deprecated estimate_PotNatVeg_composition (rSOILWAT >= v.6.0.0) + ```{r} + clim1 <- calc_SiteClimate(weatherList = + rSOILWAT2::get_WeatherHistory(rSOILWAT2::sw_exampleData), + do_C4vars = TRUE) + + rSOILWAT2:::estimate_PotNatVeg_composition_old( + MAP_mm = 10 * clim1[["MAP_cm"]], MAT_C = clim1[["MAT_C"]], + mean_monthly_ppt_mm = 10 * clim1[["meanMonthlyPPTcm"]], + mean_monthly_Temp_C = clim1[["meanMonthlyTempC"]], + Succulents_Fraction = 0.0, fix_succulents = TRUE, + Forbs_Fraction = 0.0, fix_forbs = TRUE, + Shrubs_Fraction = 0.0, fix_shrubs = TRUE, + Trees_Fraction = 0.0, fix_trees = TRUE, + SumGrasses_Fraction = .5, fix_sumgrasses = TRUE, + BareGround_Fraction = .5, fix_BareGround = TRUE, + dailyC4vars = clim1[["dailyC4vars"]], + fix_issue218 = TRUE, fix_issue219 = TRUE + ) + ``` + */ + + + // Set or calculate expected outputs + copyL0(RelAbundanceL0Expected, inputValues); + RelAbundanceL0Expected[C3Index] = 0.5; + RelAbundanceL0Expected[C4Index] = 0.; + + calcVegCoverL1FromL0(RelAbundanceL1Expected, RelAbundanceL0Expected); + calcGrassCoverFromL0(grassOutputExpected, RelAbundanceL0Expected); + + + // Estimate vegetation + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, + fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); + + + // Loop through RelAbundanceL0 and test results. + for(index = 0; index < nTypes; index++) { + EXPECT_DOUBLE_EQ(RelAbundanceL0[index], RelAbundanceL0Expected[index]); + } + + // Loop through RelAbundanceL1 and test results + for(index = 0; index < 5; index++) { + EXPECT_DOUBLE_EQ(RelAbundanceL1[index], RelAbundanceL1Expected[index]); + } + + // Loop through grassOutput and test results + for(index = 0; index < 3; index++) { + EXPECT_DOUBLE_EQ(grassOutput[index], grassOutputExpected[index]); + } + + // Expect that sum of grass cover is equal to requested `SumGrassesFraction` + EXPECT_NEAR(RelAbundanceL1[grassesIndexL1], SumGrassesFraction, tol6); + + + + // Deallocate structs + deallocateClimateStructs(&climateOutput, &climateAverages); + + } + + TEST(VegEstimationDeathTest, VegInputGreaterThanOne) { + + /* ================================================================ + Tests a death case of `estimatePotNatVegComposition()` + when input vegetation values sum to over 1 + ================================================================ */ + + SW_CLIMATE_CLIM climateAverages; + SW_CLIMATE_YEARLY climateOutput; + + double SumGrassesFraction = SW_MISSING; + double C4Variables[3]; + + Bool fillEmptyWithBareGround = swTRUE; + Bool inNorthHem = swTRUE; + Bool warnExtrapolation = swTRUE; + Bool fixBareGround = swTRUE; + + double inputValues[8] = {.0567, .5, .0392, .0981, + .3218, .0827, .1293, .0405}; + double shrubLimit = .2; + + // Array holding only grass values + double grassOutput[3]; // 3 = Number of grass variables + + // Array holding all values from the estimation + double RelAbundanceL0[8]; // 8 = Number of types + + // Array holding all values from estimation minus grasses + double RelAbundanceL1[5]; // 5 = Number of types minus grasses + + // Reset "SW_Weather.allHist" + SW_WTH_read(); + finalizeAllWeather(&SW_Weather); + + // Allocate arrays needed for `calcSiteClimate()` and `averageClimateAcrossYears()` + allocateClimateStructs(31, &climateOutput, &climateAverages); + + // Calculate climate of the site and add results to "climateOutput" + calcSiteClimate(SW_Weather.allHist, 31, 1980, inNorthHem, &climateOutput); + + // Average values from "climateOutput" and put them in "climateAverages" + averageClimateAcrossYears(&climateOutput, 31, &climateAverages); + + /* =============================================================== + Test for fail when input sum is greater than one with the values: + [.0567, .5, .0392, .0981, .3218, .0827, .1293, .0405] + =============================================================== */ + + EXPECT_DEATH_IF_SUPPORTED( + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, + fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1);, + "User defined relative abundance values sum to more than 1 = full land cover" + ); + + /* =============================================================== + Test for fail when SumGrassesFraction makes the input sum greater than one + [.0567, .25, .SW_MISSING, SW_MISSING, .0912, .0465, .1293, .0405], input sum = .6142 + SumGrassesFraction = .5, total input sum: 1.023. + Total input sum is 1.1211 instead of 1.1142, because annual grass + is already defined, so that value is subtracted from SumGrassesFraction and + added to the initial input sum + =============================================================== */ + + SumGrassesFraction = .5; + + inputValues[succIndex] = .0567; + inputValues[forbIndex] = .25; + inputValues[C3Index] = SW_MISSING; + inputValues[C4Index] = SW_MISSING; + inputValues[grassAnn] = .0912; + inputValues[shrubIndex] = .0465; + inputValues[treeIndex] = .1293; + inputValues[bareGround] = .0405; + + EXPECT_DEATH_IF_SUPPORTED( + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, + fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1);, + "User defined relative abundance values sum to more than 1 = full land cover" + ); + + // Free allocated data + deallocateClimateStructs(&climateOutput, &climateAverages); + + } + +} // namespace diff --git a/tests/gtests/test_SW_Weather.cc b/tests/gtests/test_SW_Weather.cc new file mode 100644 index 000000000..9590b720f --- /dev/null +++ b/tests/gtests/test_SW_Weather.cc @@ -0,0 +1,1175 @@ +#include "gtest/gtest.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "include/SW_Weather.h" +#include "tests/gtests/sw_testhelpers.h" +#include "include/SW_Markov.h" +#include "include/SW_Model.h" +#include "include/SW_Flow_lib_PET.h" +#include "include/SW_Sky.h" + +namespace { + + TEST(ReadAllWeatherTest, DefaultValues) { + + // Testing to fill allHist from `SW_Weather` + SW_SKY_read(); + + readAllWeather( + SW_Weather.allHist, + 1980, + SW_Weather.n_years, + SW_Weather.use_weathergenerator_only, + SW_Weather.name_prefix, + SW_Weather.use_cloudCoverMonthly, + SW_Weather.use_humidityMonthly, + SW_Weather.use_windSpeedMonthly, + SW_Weather.n_input_forcings, + SW_Weather.dailyInputIndices, + SW_Weather.dailyInputFlags, + SW_Sky.cloudcov, + SW_Sky.windspeed, + SW_Sky.r_humidity + ); + + // Test first day of first year in `allHist` to make sure correct + // temperature max/min/avg and precipitation values + EXPECT_NEAR(SW_Weather.allHist[0]->temp_max[0], -0.520000, tol6); + EXPECT_NEAR(SW_Weather.allHist[0]->temp_avg[0], -8.095000, tol6); + EXPECT_NEAR(SW_Weather.allHist[0]->temp_min[0], -15.670000, tol6); + EXPECT_NEAR(SW_Weather.allHist[0]->ppt[0], .220000, tol6); + + // Reset SOILWAT2 + Reset_SOILWAT2_after_UnitTest(); + } + + TEST(ReadAllWeatherTest, NoMemoryLeakIfDecreasedNumberOfYears) { + + // Default number of years is 31 + EXPECT_EQ(SW_Weather.n_years, 31); + + // Decrease number of years + SW_Model.startyr = 1981; + SW_Model.endyr = 1982; + + // Real expectation is that there is no memory leak for `allHist` + SW_WTH_read(); + + EXPECT_EQ(SW_Weather.n_years, 2); + + + Reset_SOILWAT2_after_UnitTest(); + } + + TEST(ReadAllWeatherTest, SomeMissingValuesDays) { + + SW_Weather.generateWeatherMethod = 2; + + // Change directory to get input files with some missing data + strcpy(SW_Weather.name_prefix, "Input/data_weather_missing/weath"); + + SW_MKV_setup(); + + SW_WTH_read(); + SW_WTH_finalize_all_weather(); + + + // Expect that missing input values (from 1980) are filled by the weather generator + EXPECT_FALSE(missing(SW_Weather.allHist[0]->temp_max[0])); + EXPECT_FALSE(missing(SW_Weather.allHist[0]->temp_max[1])); + EXPECT_FALSE(missing(SW_Weather.allHist[0]->temp_min[0])); + EXPECT_FALSE(missing(SW_Weather.allHist[0]->temp_min[2])); + EXPECT_FALSE(missing(SW_Weather.allHist[0]->ppt[0])); + EXPECT_FALSE(missing(SW_Weather.allHist[0]->ppt[3])); + Reset_SOILWAT2_after_UnitTest(); + + } + + TEST(ReadAllWeatherTest, SomeMissingValuesYears) { + + int year, day; + SW_Weather.generateWeatherMethod = 2; + + // Change directory to get input files with some missing data + strcpy(SW_Weather.name_prefix, "Input/data_weather_missing/weath"); + + SW_MKV_setup(); + + SW_Model.startyr = 1981; + SW_Model.endyr = 1982; + + SW_WTH_read(); + SW_WTH_finalize_all_weather(); + + + // Check everyday's value and test if it's `MISSING` + for(year = 0; year < 2; year++) { + for(day = 0; day < 365; day++) { + EXPECT_TRUE(!missing(SW_Weather.allHist[year]->temp_max[day])); + } + } + + Reset_SOILWAT2_after_UnitTest(); + + } + + TEST(ReadAllWeatherTest, WeatherGeneratorOnly) { + + int year, day; + + SW_Weather.generateWeatherMethod = 2; + SW_Weather.use_weathergenerator_only = swTRUE; + + SW_MKV_setup(); + + // Change directory to get input files with some missing data + strcpy(SW_Weather.name_prefix, "Input/data_weather_nonexisting/weath"); + + SW_WTH_read(); + SW_WTH_finalize_all_weather(); + + // Check everyday's value and test if it's `MISSING` + for(year = 0; year < 31; year++) { + for(day = 0; day < 365; day++) { + EXPECT_TRUE(!missing(SW_Weather.allHist[year]->temp_max[day])); + } + } + + Reset_SOILWAT2_after_UnitTest(); + + } + + TEST(ReadAllWeatherDeathTest, TooManyMissingForLOCF) { + + // Change to directory without input files + strcpy(SW_Weather.name_prefix, "Input/data_weather_nonexisting/weath"); + + // Set LOCF (temp) + 0 (PPT) method + SW_Weather.generateWeatherMethod = 1; + + SW_Model.startyr = 1981; + SW_Model.endyr = 1981; + + SW_WTH_read(); + + // Error: too many missing values and weather generator turned off + EXPECT_DEATH_IF_SUPPORTED( + SW_WTH_finalize_all_weather(), + "more than 3 days missing in year 1981 and weather generator turned off" + ); + + Reset_SOILWAT2_after_UnitTest(); + + } + + TEST(ClimateVariableTest, ClimateFromDefaultWeather) { + + // This test relies on allHist from `SW_WEATHER` being already filled + SW_CLIMATE_YEARLY climateOutput; + SW_CLIMATE_CLIM climateAverages; + + Bool inNorthHem = swTRUE; + + // Allocate memory + // 31 = number of years used in test + allocateClimateStructs(31, &climateOutput, &climateAverages); + + + // ------ Check climate variables for default weather ------ + // 1980 is first year out of 31 years of default weather + + // --- Annual time-series of climate variables ------ + // Here, check values for 1980 + + /* Expect identical output to rSOILWAT2 (e.g., v5.3.1) + * NOTE: Command uses deprecated calc_SiteClimate (rSOILWAT >= v.6.0.0) + ```{r} + rSOILWAT2:::calc_SiteClimate_old( + weatherList = rSOILWAT2::get_WeatherHistory( + rSOILWAT2::sw_exampleData + )[1], + do_C4vars = TRUE, + do_Cheatgrass_ClimVars = TRUE + ) + ``` + */ + + calcSiteClimate(SW_Weather.allHist, 31, 1980, inNorthHem, &climateOutput); + + EXPECT_NEAR(climateOutput.meanTempMon_C[Jan][0], -8.432581, tol6); + EXPECT_NEAR(climateOutput.maxTempMon_C[Jan][0], -2.562581, tol6); + EXPECT_NEAR(climateOutput.minTempMon_C[Jan][0], -14.302581, tol6); + EXPECT_NEAR(climateOutput.PPTMon_cm[Jan][0], 15.1400001, tol6); + EXPECT_NEAR(climateOutput.PPT_cm[0], 59.27, tol1); + EXPECT_NEAR(climateOutput.meanTemp_C[0], 4.524863, tol1); + + // Climate variables used for C4 grass cover + // (stdev of one value is undefined) + EXPECT_DOUBLE_EQ(climateOutput.minTemp7thMon_C[0], 2.81); + EXPECT_NEAR(climateOutput.frostFree_days[0], 92, tol6); + EXPECT_NEAR(climateOutput.ddAbove65F_degday[0], 13.546000, tol6); + + + // Climate variables used for cheatgrass cover + // (stdev of one value is undefined) + EXPECT_NEAR(climateOutput.PPT7thMon_mm[0], 18.299999, tol6); + EXPECT_NEAR(climateOutput.meanTempDriestQtr_C[0], 0.936387, tol6); + EXPECT_NEAR(climateOutput.minTemp2ndMon_C[0], -12.822068, tol6); + + + /* --- Long-term variables (aggregated across years) ------ + * Expect identical output to rSOILWAT2 (e.g., v5.3.1) + * NOTE: Command uses deprecated calc_SiteClimate (rSOILWAT >= v.6.0.0) + ```{r} + rSOILWAT2:::calc_SiteClimate_old( + weatherList = rSOILWAT2::get_WeatherHistory( + rSOILWAT2::sw_exampleData + ), + do_C4vars = TRUE, + do_Cheatgrass_ClimVars = TRUE + ) + ``` + */ + + averageClimateAcrossYears(&climateOutput, 31, &climateAverages); + + EXPECT_NEAR(climateAverages.meanTempMon_C[Jan], -9.325551, tol6); + EXPECT_NEAR(climateAverages.maxTempMon_C[Jan], -2.714381, tol6); + EXPECT_NEAR(climateAverages.minTempMon_C[Jan], -15.936722, tol6); + EXPECT_NEAR(climateAverages.PPTMon_cm[Jan], 6.867419, tol6); + + EXPECT_NEAR(climateAverages.PPT_cm, 62.817419, tol6); + // Note: rSOILWAT2 v5.3.1 returns incorrect meanTemp_C = 4.153896 + // which is long-term daily average (but not long-term annual average) + EXPECT_NEAR(climateAverages.meanTemp_C, 4.154009, tol6); + + // Climate variables used for C4 grass cover + EXPECT_NEAR(climateAverages.minTemp7thMon_C, 3.078387, tol6); + EXPECT_NEAR(climateAverages.frostFree_days, 90.612903, tol6); + EXPECT_NEAR(climateAverages.ddAbove65F_degday, 21.168032, tol6); + + EXPECT_NEAR(climateAverages.sdC4[0], 1.785535, tol6); + EXPECT_NEAR(climateAverages.sdC4[1], 14.091788, tol6); + EXPECT_NEAR(climateAverages.sdC4[2], 19.953560, tol6); + + // Climate variables used for cheatgrass cover + EXPECT_NEAR(climateAverages.PPT7thMon_mm, 35.729032, tol6); + EXPECT_NEAR(climateAverages.meanTempDriestQtr_C, 11.524859, tol6); + EXPECT_NEAR(climateAverages.minTemp2ndMon_C, -13.904599, tol6); + + EXPECT_NEAR(climateAverages.sdCheatgrass[0], 21.598367, tol6); + EXPECT_NEAR(climateAverages.sdCheatgrass[1], 7.171922, tol6); + EXPECT_NEAR(climateAverages.sdCheatgrass[2], 2.618434, tol6); + + + // ------ Reset and deallocate + deallocateClimateStructs(&climateOutput, &climateAverages); + + Reset_SOILWAT2_after_UnitTest(); + } + + + + TEST(ClimateVariableTest, ClimateFromOneYearWeather) { + + // This test relies on allHist from `SW_WEATHER` being already filled + SW_CLIMATE_YEARLY climateOutput; + SW_CLIMATE_CLIM climateAverages; + + Bool inNorthHem = swTRUE; + + // Allocate memory + // 1 = number of years used in test + allocateClimateStructs(1, &climateOutput, &climateAverages); + + // ------ Check climate variables for one year of default weather ------ + + /* Expect identical output to rSOILWAT2 (e.g., v5.3.1) + * NOTE: Command uses deprecated calc_SiteClimate (rSOILWAT >= v.6.0.0) + ```{r} + rSOILWAT2:::calc_SiteClimate_old( + weatherList = rSOILWAT2::get_WeatherHistory( + rSOILWAT2::sw_exampleData + )[1], + do_C4vars = TRUE, + do_Cheatgrass_ClimVars = TRUE + ) + ``` + */ + + calcSiteClimate(SW_Weather.allHist, 1, 1980, inNorthHem, &climateOutput); + averageClimateAcrossYears(&climateOutput, 1, &climateAverages); + + // Expect that aggregated values across one year are identical + // to values of that one year + EXPECT_DOUBLE_EQ( + climateAverages.meanTempMon_C[Jan], + climateOutput.meanTempMon_C[Jan][0] + ); + EXPECT_DOUBLE_EQ( + climateAverages.maxTempMon_C[Jan], + climateOutput.maxTempMon_C[Jan][0] + ); + EXPECT_DOUBLE_EQ( + climateAverages.minTempMon_C[Jan], + climateOutput.minTempMon_C[Jan][0] + ); + EXPECT_DOUBLE_EQ( + climateAverages.PPTMon_cm[Jan], + climateOutput.PPTMon_cm[Jan][0] + ); + EXPECT_DOUBLE_EQ( + climateAverages.PPT_cm, + climateOutput.PPT_cm[0] + ); + EXPECT_DOUBLE_EQ( + climateAverages.meanTemp_C, + climateOutput.meanTemp_C[0] + ); + + // Climate variables used for C4 grass cover + EXPECT_DOUBLE_EQ( + climateAverages.minTemp7thMon_C, + climateOutput.minTemp7thMon_C[0] + ); + EXPECT_DOUBLE_EQ( + climateAverages.frostFree_days, + climateOutput.frostFree_days[0] + ); + EXPECT_DOUBLE_EQ( + climateAverages.ddAbove65F_degday, + climateOutput.ddAbove65F_degday[0] + ); + + // Climate variables used for cheatgrass cover + EXPECT_DOUBLE_EQ( + climateAverages.PPT7thMon_mm, + climateOutput.PPT7thMon_mm[0] + ); + EXPECT_DOUBLE_EQ( + climateAverages.meanTempDriestQtr_C, + climateOutput.meanTempDriestQtr_C[0] + ); + EXPECT_DOUBLE_EQ( + climateAverages.minTemp2ndMon_C, + climateOutput.minTemp2ndMon_C[0] + ); + + + EXPECT_NEAR(climateAverages.meanTempMon_C[Jan], -8.432581, tol6); + EXPECT_NEAR(climateAverages.maxTempMon_C[Jan], -2.562581, tol6); + EXPECT_NEAR(climateAverages.minTempMon_C[Jan], -14.302581, tol6); + EXPECT_NEAR(climateAverages.PPTMon_cm[Jan], 15.1400001, tol6); + EXPECT_NEAR(climateAverages.PPT_cm, 59.27, tol1); + EXPECT_NEAR(climateAverages.meanTemp_C, 4.524863, tol1); + + // Climate variables used for C4 grass cover + // (stdev of one value is undefined) + EXPECT_DOUBLE_EQ(climateAverages.minTemp7thMon_C, 2.81); + EXPECT_NEAR(climateAverages.frostFree_days, 92, tol6); + EXPECT_NEAR(climateAverages.ddAbove65F_degday, 13.546000, tol6); + EXPECT_TRUE(isnan(climateAverages.sdC4[0])); + EXPECT_TRUE(isnan(climateAverages.sdC4[1])); + EXPECT_TRUE(isnan(climateAverages.sdC4[2])); + + // Climate variables used for cheatgrass cover + // (stdev of one value is undefined) + EXPECT_NEAR(climateAverages.PPT7thMon_mm, 18.299999, tol6); + EXPECT_NEAR(climateAverages.meanTempDriestQtr_C, 0.936387, tol6); + EXPECT_NEAR(climateAverages.minTemp2ndMon_C, -12.822068, tol6); + EXPECT_TRUE(isnan(climateAverages.sdCheatgrass[0])); + EXPECT_TRUE(isnan(climateAverages.sdCheatgrass[1])); + EXPECT_TRUE(isnan(climateAverages.sdCheatgrass[2])); + + + // ------ Reset and deallocate + deallocateClimateStructs(&climateOutput, &climateAverages); + + Reset_SOILWAT2_after_UnitTest(); + + } + + TEST(ClimateVariableTest, ClimateFromDefaultWeatherSouth) { + + /* ================================================================== + Values for these SOILWAT2 tests and in rSOILWAT2 v6.0.0 for + southern hemisphere are different than versions preceeding v6.0.0. + + rSOILWAT2 previously calculated climate variable values for southern + hemisphere differently than SOILWAT2 now does. To briefly explain, + rSOILWAT2 would have instances within the north/south adjustment where + July 1st - 3rd would be ignored resulting in different values from + missing data for a specific year. SOILWAT2 modified the north/south + algorithm and the southern year now properly starts on July 1st + resulting in different data values for a year. + ================================================================= */ + + // This test relies on allHist from `SW_WEATHER` being already filled + SW_CLIMATE_YEARLY climateOutput; + SW_CLIMATE_CLIM climateAverages; + + // "South" and not "North" to reduce confusion when calling `calcSiteClimate()` + Bool inSouthHem = swFALSE; + + // Allocate memory + // 31 = number of years used in test + allocateClimateStructs(31, &climateOutput, &climateAverages); + + + // ------ Check climate variables for default weather ------ + // 1980 is first year out of 31 years of default weather + + // --- Annual time-series of climate variables ------ + // Here, check values for 1980 + + /* Expect similar output to rSOILWAT2 before v6.0.0 (e.g., v5.3.1) + * NOTE: Command uses deprecated calc_SiteClimate (rSOILWAT >= v.6.0.0) + ```{r} + rSOILWAT2:::calc_SiteClimate_old( + weatherList = rSOILWAT2::get_WeatherHistory( + rSOILWAT2::sw_exampleData + )[1], + do_C4vars = TRUE, + do_Cheatgrass_ClimVars = TRUE, + latitude = -10 + ) + ``` + */ + + calcSiteClimate(SW_Weather.allHist, 31, 1980, inSouthHem, &climateOutput); + + EXPECT_NEAR(climateOutput.meanTempMon_C[Jan][0], -8.432581, tol6); + EXPECT_NEAR(climateOutput.maxTempMon_C[Jan][0], -2.562581, tol6); + EXPECT_NEAR(climateOutput.minTempMon_C[Jan][0], -14.302581, tol6); + EXPECT_NEAR(climateOutput.PPTMon_cm[Jan][0], 15.1400001, tol6); + EXPECT_NEAR(climateOutput.PPT_cm[0], 59.27, tol6); + EXPECT_NEAR(climateOutput.meanTemp_C[0], 4.524863, tol6); + + // Climate variables used for C4 grass cover + // (stdev of one value is undefined) + EXPECT_NEAR(climateOutput.minTemp7thMon_C[1], -16.98, tol6); + EXPECT_NEAR(climateOutput.frostFree_days[1], 78, tol6); + EXPECT_NEAR(climateOutput.ddAbove65F_degday[1], 16.458001, tol6); + + + // Climate variables used for cheatgrass cover + // (stdev of one value is undefined) + EXPECT_DOUBLE_EQ(climateOutput.PPT7thMon_mm[1], 22.2); + EXPECT_NEAR(climateOutput.meanTempDriestQtr_C[0], 0.936387, tol6); + EXPECT_NEAR(climateOutput.minTemp2ndMon_C[1], 5.1445161, tol6); + + + /* --- Long-term variables (aggregated across years) ------ + * Expect similar output to rSOILWAT2 before v6.0.0 (e.g., v5.3.1), identical otherwise + * NOTE: Command uses deprecated calc_SiteClimate (rSOILWAT >= v.6.0.0) + ```{r} + rSOILWAT2:::calc_SiteClimate_old( + weatherList = rSOILWAT2::get_WeatherHistory( + rSOILWAT2::sw_exampleData + ), + do_C4vars = TRUE, + do_Cheatgrass_ClimVars = TRUE, + latitude = -10 + ) + ``` + */ + + averageClimateAcrossYears(&climateOutput, 31, &climateAverages); + + EXPECT_NEAR(climateAverages.meanTempMon_C[Jan], -9.325551, tol6); + EXPECT_NEAR(climateAverages.maxTempMon_C[Jan], -2.714381, tol6); + EXPECT_NEAR(climateAverages.minTempMon_C[Jan], -15.936722, tol6); + EXPECT_NEAR(climateAverages.PPTMon_cm[Jan], 6.867419, tol6); + + EXPECT_NEAR(climateAverages.PPT_cm, 62.817419, tol6); + // Note: rSOILWAT2 v5.3.1 returns incorrect meanTemp_C = 4.153896 + // which is long-term daily average (but not long-term annual average) + EXPECT_NEAR(climateAverages.meanTemp_C, 4.154009, tol6); + + // Climate variables used for C4 grass cover + EXPECT_NEAR(climateAverages.minTemp7thMon_C, -27.199333, tol6); + EXPECT_NEAR(climateAverages.frostFree_days, 72.599999, tol6); + EXPECT_NEAR(climateAverages.ddAbove65F_degday, 21.357533, tol6); + + EXPECT_NEAR(climateAverages.sdC4[0], 5.325365, tol6); + EXPECT_NEAR(climateAverages.sdC4[1], 9.586628, tol6); + EXPECT_NEAR(climateAverages.sdC4[2], 19.550419, tol6); + + // Climate variables used for cheatgrass cover + EXPECT_NEAR(climateAverages.PPT7thMon_mm, 65.916666, tol6); + EXPECT_NEAR(climateAverages.meanTempDriestQtr_C, 11.401228, tol6); + EXPECT_NEAR(climateAverages.minTemp2ndMon_C, 6.545577, tol6); + + EXPECT_NEAR(climateAverages.sdCheatgrass[0], 35.285408, tol6); + EXPECT_NEAR(climateAverages.sdCheatgrass[1], 7.260851, tol6); + EXPECT_NEAR(climateAverages.sdCheatgrass[2], 1.639639, tol6); + + + // ------ Reset and deallocate + deallocateClimateStructs(&climateOutput, &climateAverages); + + Reset_SOILWAT2_after_UnitTest(); + } + + + TEST(ClimateVariableTest, ClimateFromConstantWeather) { + + SW_CLIMATE_YEARLY climateOutput; + SW_CLIMATE_CLIM climateAverages; + SW_WEATHER_HIST **allHist; + + Bool inNorthHem = swTRUE; + + // Allocate memory + allocateClimateStructs(2, &climateOutput, &climateAverages); + + allHist = (SW_WEATHER_HIST **)malloc(sizeof(SW_WEATHER_HIST *) * 2); + + for (int year = 0; year < 2; year++) { + allHist[year] = (SW_WEATHER_HIST *)malloc(sizeof(SW_WEATHER_HIST)); + } + + + // ------ Check climate variables for constant weather = 1 ------ + // Years 1980 (leap) + 1981 (nonleap) + + // Expect that min/avg/max of weather variables are 1 + // Expect that sums of weather variables correspond to number of days + // Expect that the average annual number of days is 365.5 (1 leap + 1 nonleap) + // Expect that the average number of days in February is 28.5 (1 leap + 1 nonleap) + + // Set all weather values to 1 + for(int year = 0; year < 2; year++) { + for(int day = 0; day < 366; day++) { + allHist[year]->temp_max[day] = 1.; + allHist[year]->temp_min[day] = 1.; + allHist[year]->temp_avg[day] = 1.; + allHist[year]->ppt[day] = 1.; + } + } + + // --- Annual time-series of climate variables ------ + calcSiteClimate(allHist, 2, 1980, inNorthHem, &climateOutput); + + EXPECT_DOUBLE_EQ(climateOutput.meanTempMon_C[Jan][0], 1.); + EXPECT_DOUBLE_EQ(climateOutput.maxTempMon_C[Jan][0], 1.); + EXPECT_DOUBLE_EQ(climateOutput.minTempMon_C[Jan][0], 1.); + EXPECT_DOUBLE_EQ(climateOutput.PPTMon_cm[Jan][0], 31.); + EXPECT_DOUBLE_EQ(climateOutput.PPTMon_cm[Feb][0], 29.); + EXPECT_DOUBLE_EQ(climateOutput.PPTMon_cm[Feb][1], 28.); + EXPECT_DOUBLE_EQ(climateOutput.PPT_cm[0], 366.); + EXPECT_DOUBLE_EQ(climateOutput.PPT_cm[1], 365.); + EXPECT_DOUBLE_EQ(climateOutput.meanTemp_C[0], 1.); + + // Climate variables used for C4 grass cover + // (stdev of one value is undefined) + EXPECT_DOUBLE_EQ(climateOutput.minTemp7thMon_C[0], 1.); + EXPECT_DOUBLE_EQ(climateOutput.frostFree_days[0], 366.); + EXPECT_DOUBLE_EQ(climateOutput.frostFree_days[1], 365.); + EXPECT_DOUBLE_EQ(climateOutput.ddAbove65F_degday[0], 0.); + + + // Climate variables used for cheatgrass cover + // (stdev of one value is undefined) + EXPECT_DOUBLE_EQ(climateOutput.PPT7thMon_mm[0], 310.); + EXPECT_DOUBLE_EQ(climateOutput.meanTempDriestQtr_C[0], 1.); + EXPECT_DOUBLE_EQ(climateOutput.minTemp2ndMon_C[0], 1.); + + + // --- Long-term variables (aggregated across years) ------ + averageClimateAcrossYears(&climateOutput, 2, &climateAverages); + + EXPECT_DOUBLE_EQ(climateAverages.meanTempMon_C[Jan], 1.); + EXPECT_DOUBLE_EQ(climateAverages.maxTempMon_C[Jan], 1.); + EXPECT_DOUBLE_EQ(climateAverages.minTempMon_C[Jan], 1.); + EXPECT_DOUBLE_EQ(climateAverages.PPTMon_cm[Jan], 31.); + EXPECT_DOUBLE_EQ(climateAverages.PPTMon_cm[Feb], 28.5); + EXPECT_DOUBLE_EQ(climateAverages.PPTMon_cm[Dec], 31); + EXPECT_DOUBLE_EQ(climateAverages.PPT_cm, 365.5); + EXPECT_DOUBLE_EQ(climateAverages.meanTemp_C, 1.); + + // Climate variables used for C4 grass cover + EXPECT_DOUBLE_EQ(climateAverages.minTemp7thMon_C, 1.); + EXPECT_DOUBLE_EQ(climateAverages.frostFree_days, 365.5); + EXPECT_DOUBLE_EQ(climateAverages.ddAbove65F_degday, 0.); + EXPECT_DOUBLE_EQ(climateAverages.sdC4[0], 0.); + EXPECT_NEAR(climateAverages.sdC4[1], .7071067, tol6); // sd(366, 365) + EXPECT_DOUBLE_EQ(climateAverages.sdC4[2], 0.); + + // Climate variables used for cheatgrass cover + EXPECT_DOUBLE_EQ(climateAverages.PPT7thMon_mm, 310.); + EXPECT_DOUBLE_EQ(climateAverages.meanTempDriestQtr_C, 1.); + EXPECT_DOUBLE_EQ(climateAverages.minTemp2ndMon_C, 1.); + EXPECT_DOUBLE_EQ(climateAverages.sdCheatgrass[0], 0.); + EXPECT_DOUBLE_EQ(climateAverages.sdCheatgrass[1], 0.); + EXPECT_DOUBLE_EQ(climateAverages.sdCheatgrass[2], 0.); + + + // ------ Reset and deallocate + deallocateClimateStructs(&climateOutput, &climateAverages); + + for (int year = 0; year < 2; year++) { + free(allHist[year]); + } + free(allHist); + + } + + + TEST(ClimateVariableTest, AverageTemperatureOfDriestQuarterTest) { + + double monthlyPPT[MAX_MONTHS] = {.5, .5, .1, .4, .9, 1.0, 1.2, 6.5, 7.5, 1.2, 4., .6}; + double monthlyTemp[MAX_MONTHS] = {-3.2, -.4, 1.2, 3.5, 7.5, 4.5, 6.5, 8.2, 2.0, 3., .1, -.3}; + double result[2]; // 2 = max number of years in test + + int month, year; + + double **PPTMon_cm; + PPTMon_cm = new double*[MAX_MONTHS]; + + double **meanTempMon_C = new double*[MAX_MONTHS]; + + Bool inNorthHem = swTRUE; + + for(month = 0; month < MAX_MONTHS; month++) { + PPTMon_cm[month] = new double[2]; + meanTempMon_C[month] = new double[2]; + for(year = 0; year < 2; year++) { + + PPTMon_cm[month][year] = monthlyPPT[month]; + meanTempMon_C[month][year] = monthlyTemp[month]; + } + } + + + // ------ Test for one year ------ + findDriestQtr(1, inNorthHem, result, meanTempMon_C, PPTMon_cm); + + // Value 1.433333... is the average temperature of the driest quarter of the year + // In this case, the driest quarter is February-April + EXPECT_NEAR(result[0], 1.4333333333333333, tol9); + + + // ------ Test for two years ------ + findDriestQtr(2, inNorthHem, result, meanTempMon_C, PPTMon_cm); + + EXPECT_NEAR(result[0], 1.4333333333333333, tol9); + EXPECT_NEAR(result[1], 1.4333333333333333, tol9); + + + + // ------ Test when there are multiple driest quarters ------ + for (month = 0; month < MAX_MONTHS; month++) { + for(year = 0; year < 2; year++) { + PPTMon_cm[month][year] = 1.; + } + } + + findDriestQtr(1, inNorthHem, result, meanTempMon_C, PPTMon_cm); + + // Expect that the driest quarter that occurs first + // among all driest quarters is used + // Here, the first driest quarter is centered on Jan with Dec-Feb + // with average temp of -1.3 C + EXPECT_NEAR(result[0], -1.3, tol9); + + + // ------ Clean up + for(int month = 0; month < MAX_MONTHS; month++) { + delete[] PPTMon_cm[month]; + delete[] meanTempMon_C[month]; + } + + delete[] PPTMon_cm; + delete[] meanTempMon_C; + + } + + TEST(WeatherReadTest, Initialization) { + + SW_WTH_read(); + + EXPECT_FLOAT_EQ(SW_Weather.allHist[0]->temp_max[0], -.52); + + // Reset SOIWLAT2 + Reset_SOILWAT2_after_UnitTest(); + } + + TEST(DailyInsteadOfMonthlyInputTest, MonthlyInputPrioritization) { + /* + This section covers the correct prioritization of monthly input values + instead of daily read-in values + */ + + // Initialize any variables + int yearIndex = 0, midJanDay = 14; + SW_WEATHER *w = &SW_Weather; + + /* Test if monthly values are not being used */ + SW_WTH_setup(); + + // Read in all weather + SW_WTH_read(); + + // Test the middle of January in year 1980 and see if it's not equal to SW_Sky.r_humidity[0], + // SW_Sky.cloudcov[0], and SW_Sky.windspeed[0] + // Note: Daily interpolated values in the middle of a month are equal to the + // original monthly values from which they were interpolated + EXPECT_NEAR(w->allHist[yearIndex]->r_humidity_daily[midJanDay], SW_Sky.r_humidity[0], tol6); + EXPECT_NEAR(w->allHist[yearIndex]->cloudcov_daily[midJanDay], SW_Sky.cloudcov[0], tol6); + EXPECT_NEAR(w->allHist[yearIndex]->windspeed_daily[midJanDay], SW_Sky.windspeed[0], tol6); + + // Reset SOILWAT2 so that `finalizeAllWeather()` is called + Reset_SOILWAT2_after_UnitTest(); + } + + TEST(DailyWeatherInputTest, DailyGridMet) { + + /* + This section tests humidity-related values that can be averaged, + max/min relative humidity, and the calculation of actual vapor pressure, + based on the average relative humidity value, is calculated reasonably. + + * This section uses the test directory "*_gridmet". + */ + + SW_WEATHER *w = &SW_Weather; + double result, expectedResult; + int yearIndex = 0, year = 1980, midJanDay = 14; + + /* Test correct priority is being given to input values from DAYMET */ + SW_WTH_setup(); + + // Switch directory to gridmet input folder + strcpy(w->name_prefix, "Input/data_weather_gridmet/weath"); + + // Turn off monthly flags + w->use_cloudCoverMonthly = swFALSE; + w->use_windSpeedMonthly = swFALSE; + w->use_humidityMonthly = swFALSE; + + // Manually edit index/flag arrays in SW_WEATHER to make test as + // realistic as possible + // Note: Indices are based on the directory: + // Input/data_weather_gridmet/weath.1980 + w->dailyInputIndices[WIND_SPEED] = 3; + w->dailyInputIndices[REL_HUMID_MAX] = 4; + w->dailyInputIndices[REL_HUMID_MIN] = 5; + w->dailyInputIndices[SHORT_WR] = 6; + w->dailyInputFlags[WIND_SPEED] = swTRUE; + w->dailyInputFlags[REL_HUMID_MAX] = swTRUE; + w->dailyInputFlags[REL_HUMID_MIN] = swTRUE; + w->dailyInputFlags[SHORT_WR] = swTRUE; + w->n_input_forcings = 7; + w->desc_rsds = 1; // gridMET rsds is flux density over 24 hours + + // Reset daily weather values + _clear_hist_weather(w->allHist[0]); + + // Using the new inputs folder, read in year = 1980 + _read_weather_hist( + year, + w->allHist[0], + w->name_prefix, + w->n_input_forcings, + w->dailyInputIndices, + w->dailyInputFlags + ); + + result = w->allHist[yearIndex]->r_humidity_daily[0]; + + // Get expected average from Input/data_weather_gridmet/weath.1980 day 1 + // hursmax_pct and hursmin_pct + expectedResult = (74.17 + 31.42) / 2.; + + // Test if the average of relative humidity has been calculated + // instead of being based on huss for the first day of 1980 + EXPECT_NEAR(result, expectedResult, tol3); + + // Expect that daily relative humidity is derived from hursmax_pct and hursmin_pct + // (and is not interpolated from mean monthly values) + EXPECT_NEAR( + w->allHist[yearIndex]->r_humidity_daily[midJanDay], + (88.35 + 34.35) / 2., + tol6 + ); + + EXPECT_NE( + w->allHist[yearIndex]->r_humidity_daily[midJanDay], + SW_Sky.r_humidity[0] + ); + + result = w->allHist[yearIndex]->actualVaporPressure[0]; + + // Get expected result from Input/data_weather_gridmet/weath.1980 day 1 + // hursmax_pct, hursmin_pct, Tmax_C, and Tmin_C + expectedResult = actualVaporPressure2(74.17, 31.42, -3.18, -12.32); + + // Based on the max/min of relative humidity, test if actual vapor pressure + // was calculated reasonably + EXPECT_NEAR(result, expectedResult, tol6); + + + // We have observed radiation and missing cloud cover + EXPECT_FALSE(missing(w->allHist[yearIndex]->shortWaveRad[0])); + EXPECT_TRUE(missing(w->allHist[yearIndex]->cloudcov_daily[0])); + + + // Make sure calculations and set input values are within reasonable range + checkAllWeather(w); + + + // Reset SOILWAT2 for next test + Reset_SOILWAT2_after_UnitTest(); + } + + TEST(DailyWeatherInputTest, DailyDayMet) { + + /* + This section covers the assurance that if a daily value is provided + (actual vapor pressure in this case), aside from temperature and precipitation, + it is correctly set. Along with reasonable calculation of a separate daily value + that is dependent on said daily variable (relative humidity in this case). + + * This scenario only happens with humidity-related variables. + * This section uses the test directory "*_daymet". + */ + + SW_WEATHER *w = &SW_Weather; + double result, expectedResult, tempSlope; + int yearIndex = 0, year = 1980, midJanDay = 14; + + /* Test correct priority is being given to input values from DAYMET */ + SW_WTH_setup(); + + // Switch directory to daymet input folder + strcpy(w->name_prefix, "Input/data_weather_daymet/weath"); + + // Turn off monthly flags + w->use_cloudCoverMonthly = swFALSE; + w->use_windSpeedMonthly = swTRUE; + w->use_humidityMonthly = swFALSE; + + // Manually edit index/flag arrays in SW_WEATHER to make test as + // realistic as possible + // Note: Indices are based on the directory: + // Input/data_weather_daymet/weath.1980 + w->dailyInputIndices[ACTUAL_VP] = 3; + w->dailyInputIndices[SHORT_WR] = 4; + w->dailyInputFlags[ACTUAL_VP] = swTRUE; + w->dailyInputFlags[SHORT_WR] = swTRUE; + w->n_input_forcings = 5; + w->desc_rsds = 2; // DayMet rsds is flux density over daylight period + + // Reset daily weather values + _clear_hist_weather(w->allHist[0]); + + // Using the new inputs folder, read in year = 1980 + _read_weather_hist( + year, + w->allHist[0], + w->name_prefix, + w->n_input_forcings, + w->dailyInputIndices, + w->dailyInputFlags + ); + + result = w->allHist[yearIndex]->actualVaporPressure[0]; + + // Get expected result from Input/data_weather_daymet/weath.1980 day 1 + // vp_kPa + expectedResult = .3; + + // Test if actual vapor pressure value was set to the first days + // input value from Input/data_weather_daymet/weath.1980 day 1 + EXPECT_NEAR(result, expectedResult, tol6); + + result = w->allHist[yearIndex]->r_humidity_daily[0]; + + // Get expected result from Input/data_weather_daymet/weath.1980 day 1 + // Tmax_C = -.37, Tmin_C = -9.2, and vp_kPa = .3 + expectedResult = (-.37 - 9.2) / 2.; + expectedResult = svp(expectedResult, &tempSlope); + expectedResult = .3 / expectedResult; + + // Based on actual vapor pressure, test if relative humidity + // was calculated reasonably + EXPECT_NEAR(result, expectedResult, tol6); + + // Expect that daily relative humidity is derived from vp_kPa, Tmax_C, and Tmin_C + // (and is not interpolated from mean monthly values) + expectedResult = (-.81 - 9.7) / 2.; + expectedResult = svp(expectedResult, &tempSlope); + expectedResult = .29 / expectedResult; + + EXPECT_NEAR( + w->allHist[yearIndex]->r_humidity_daily[midJanDay], + expectedResult, + tol6 + ); + + EXPECT_NE( + w->allHist[yearIndex]->r_humidity_daily[midJanDay], + SW_Sky.r_humidity[0] + ); + + // We have observed radiation and missing cloud cover + EXPECT_FALSE(missing(w->allHist[yearIndex]->shortWaveRad[0])); + EXPECT_TRUE(missing(w->allHist[yearIndex]->cloudcov_daily[0])); + + + // Make sure calculations and set input values are within reasonable range + checkAllWeather(w); + + // Reset SOILWAT2 for next test + Reset_SOILWAT2_after_UnitTest(); + } + + TEST(DailyWeatherInputTest, DailyMACA) { + + /* + This section assures that a variable aside from humidity-related ones, + wind speed in this case, is calculated reasonably based on its components. + + * This section uses the test directory "*_maca". + */ + + SW_WEATHER *w = &SW_Weather; + double result, expectedResult; + int yearIndex = 0, year = 1980, midJanDay = 14; + + /* Test correct priority is being given to input values from MACA */ + + SW_WTH_setup(); + + // Switch directory to daymet input folder + strcpy(w->name_prefix, "Input/data_weather_maca/weath"); + + // Turn off monthly flags + w->use_cloudCoverMonthly = swFALSE; + w->use_windSpeedMonthly = swFALSE; + w->use_humidityMonthly = swFALSE; + + // Manually edit index/flag arrays in SW_WEATHER to make test as + // realistic as possible + // Note: Indices are based on the directory: + // Input/data_weather_maca/weath.1980 + w->dailyInputIndices[WIND_EAST] = 3; + w->dailyInputIndices[WIND_NORTH] = 4; + w->dailyInputIndices[REL_HUMID_MAX] = 5; + w->dailyInputIndices[REL_HUMID_MIN] = 6; + w->dailyInputIndices[SHORT_WR] = 7; + w->dailyInputFlags[WIND_EAST] = swTRUE; + w->dailyInputFlags[WIND_NORTH] = swTRUE; + w->dailyInputFlags[REL_HUMID_MAX] = swTRUE; + w->dailyInputFlags[REL_HUMID_MIN] = swTRUE; + w->dailyInputFlags[SHORT_WR] = swTRUE; + w->n_input_forcings = 8; + w->desc_rsds = 1; // MACA rsds is flux density over 24 hours + + // Reset daily weather values + _clear_hist_weather(w->allHist[0]); + + // Using the new inputs folder, read in year = 1980 + _read_weather_hist( + year, + w->allHist[0], + w->name_prefix, + w->n_input_forcings, + w->dailyInputIndices, + w->dailyInputFlags + ); + + result = w->allHist[yearIndex]->windspeed_daily[0]; + + // Get expected result from Input/data_weather_maca/weath.1980 day 1 + // uas_mPERs = 3.31 and vas_mPERs = -.85 + expectedResult = sqrt(squared(3.31) + squared(-.85)); + + // Test if wind speed was calculated within reasonable range to + // the expected result + EXPECT_NEAR(result, expectedResult, tol6); + + // Expect that daily wind speed is derived from uas_mPERs and vas_mPERs + // (and is not interpolated from mean monthly values) + expectedResult = sqrt(squared(2.82) + squared(-.4)); + + EXPECT_NEAR( + w->allHist[yearIndex]->windspeed_daily[midJanDay], + expectedResult, + tol6 + ); + + EXPECT_NE( + w->allHist[yearIndex]->windspeed_daily[midJanDay], + SW_Sky.windspeed[0] + ); + + // Expect that daily relative humidity is derived from hursmax_pct and hursmin_pct + // (and is not interpolated from mean monthly values) + EXPECT_NEAR( + w->allHist[yearIndex]->r_humidity_daily[midJanDay], + (80.55 + 32.28) / 2., + tol6 + ); + + EXPECT_NE( + w->allHist[yearIndex]->r_humidity_daily[midJanDay], + SW_Sky.r_humidity[0] + ); + + // We have observed radiation and missing cloud cover + EXPECT_FALSE(missing(w->allHist[yearIndex]->shortWaveRad[0])); + EXPECT_TRUE(missing(w->allHist[yearIndex]->cloudcov_daily[0])); + + + // Make sure calculations and set input values are within reasonable range + checkAllWeather(w); + + // Reset SOILWAT2 for next test + Reset_SOILWAT2_after_UnitTest(); + } + + TEST(DailyInputLOCFTest, LOCFForDailyValues) { + + /* + Since SOILWAT2 now has the ability to deal with more than + maximum/minimum temperature and precipitation, part of the weather + generator can now perform LOCF on these values (generator method = 1). + + We want to make sure when the weather generator method is equal to 1, + LOCF is performed on these variables and not ignored + */ + int numDaysLOCFTolerance = 366, yearIndex = 0, day; + double cloudCovTestVal = .5, actVapPressTestVal = 4.23, windSpeedTestVal = 2.12; + SW_WEATHER *w = &SW_Weather; + + // Setup and read in weather + SW_WTH_setup(); + + // Turn off flags for monthly values along with daily flags + // so all daily variables aside from max/min temperature and precipiation + // are set to SW_MISSING + w->use_cloudCoverMonthly = swFALSE; + w->use_humidityMonthly = swFALSE; + w->use_windSpeedMonthly = swFALSE; + + SW_WTH_read(); + + // Setup values/flags for `generateMissingWeather()` to deal with + w->generateWeatherMethod = 1; + w->allHist[yearIndex]->cloudcov_daily[0] = cloudCovTestVal; + w->allHist[yearIndex]->actualVaporPressure[0] = actVapPressTestVal; + w->allHist[yearIndex]->windspeed_daily[0] = windSpeedTestVal; + + generateMissingWeather(w->allHist, + 1980, + 1, + w->generateWeatherMethod, + numDaysLOCFTolerance); + + // Test to see if the first year of cloud cover, actual vapor pressure and + // wind speed has been filled with cloudCovTestVal, actVapPressTestVal, + // and windSpeedTestVal, respectively + for(day = 0; day < MAX_DAYS; day++){ + EXPECT_EQ(w->allHist[yearIndex]->cloudcov_daily[day], cloudCovTestVal); + EXPECT_EQ(w->allHist[yearIndex]->actualVaporPressure[day], actVapPressTestVal); + EXPECT_EQ(w->allHist[yearIndex]->windspeed_daily[day], windSpeedTestVal); + } + + // Reset rSOILWAT2 + Reset_SOILWAT2_after_UnitTest(); + } + + TEST(DailyInsteadOfMonthlyInputDeathTest, ReasonableValuesAndFlags) { + /* + This section covers number of flags and the testing of reasonable results (`checkAllWeather()`). + + An incorrect number of "n_input_forcings" within SW_WEATHER relative to the number of input columns + read in should result in a crash. + + If an input or calculated value is out of range (e.g., range = [-100, 100] C for min/max temperature), + `checkAllWeather()` should result in a crash. + */ + + // Initialize any variables + TimeInt year = 1980; + double originVal; + SW_WEATHER *w = &SW_Weather; + + /* Not the same number of flags as columns */ + + SW_WTH_read(); + + // Set SW_WEATHER's n_input_forcings to a number that is + // not the columns being read in + w->n_input_forcings = 0; + + // Run death test + EXPECT_DEATH_IF_SUPPORTED( + _read_weather_hist( + year, + w->allHist[0], + w->name_prefix, + w->n_input_forcings, + w->dailyInputIndices, + w->dailyInputFlags + ), + "Incomplete record 1" + ); + + /* Check for value(s) that are not within reasonable range these + tests will make use of `checkAllWeather()` */ + + // Edit SW_WEATHER_HIST values from their original value + // Make temperature unreasonable (not within [-100, 100]) + + originVal = SW_Weather.allHist[0]->temp_max[0]; + + w->allHist[0]->temp_max[0] = -102.; + + EXPECT_DEATH_IF_SUPPORTED( + checkAllWeather(w), + "Daily input value for minimum temperature is greater than daily input value for maximum temperature" + ); + + // Make precipitation unresonable (< 0) + w->allHist[0]->temp_max[0] = originVal; + + originVal = SW_Weather.allHist[0]->ppt[0]; + + w->allHist[0]->ppt[0] = -1.; + + EXPECT_DEATH_IF_SUPPORTED( + checkAllWeather(w), + "Invalid daily precipitation value" + ); + + // Make relative humidity unreasonable (< 0%) + w->allHist[0]->ppt[0] = originVal; + + w->allHist[0]->r_humidity_daily[0] = -.1252; + + EXPECT_DEATH_IF_SUPPORTED( + checkAllWeather(w), + "relative humidity value did not fall in the range" + ); + + // Reset SOILWAT2 for next test + Reset_SOILWAT2_after_UnitTest(); + } +} diff --git a/test/test_Times.cc b/tests/gtests/test_Times.cc similarity index 60% rename from test/test_Times.cc rename to tests/gtests/test_Times.cc index 4c59bfc37..b1dadb537 100644 --- a/test/test_Times.cc +++ b/tests/gtests/test_Times.cc @@ -4,20 +4,21 @@ #include #include #include -#include "../generic.h" -#include "../SW_Sky.h" -#include "../filefuncs.h" -#include "../myMemory.h" -#include "../SW_Defines.h" -#include "../SW_Files.h" -#include "../SW_Model.h" -#include "../SW_Site.h" -#include "../SW_SoilWater.h" -#include "../SW_VegProd.h" -#include "../SW_Site.h" -#include "../SW_Flow_lib.h" -#include "../Times.h" -#include "sw_testhelpers.h" +#include "include/generic.h" +#include "include/SW_Sky.h" +#include "include/filefuncs.h" +#include "include/myMemory.h" +#include "include/SW_Defines.h" +#include "include/SW_Files.h" +#include "include/SW_Model.h" +#include "include/SW_Site.h" +#include "include/SW_SoilWater.h" +#include "include/SW_VegProd.h" +#include "include/SW_Site.h" +#include "include/SW_Flow_lib.h" +#include "include/Times.h" +#include "include/SW_Weather.h" +#include "tests/gtests/sw_testhelpers.h" namespace{ TEST(TimesTest, LeapYear) { @@ -65,6 +66,8 @@ namespace{ // point to the structure that contains cloud coverage monthly values SW_SKY SW_Sky; SW_SKY *xintpl = &SW_Sky; + SW_WEATHER_HIST xintpl_weather[1][1]; + Bool interpAsBase1 = swFALSE; unsigned int i, k, doy, lpadd, years[] = {1980, 1981}; // leap year, non-leap year @@ -81,25 +84,35 @@ namespace{ for (i = 0; i < length(xintpl -> cloudcov); i++) { xintpl -> cloudcov[i] = 10; } - xintpl -> cloudcov_daily[0] = 0; + xintpl_weather[0] -> cloudcov_daily[0] = SW_MISSING; - interpolate_monthlyValues(xintpl -> cloudcov, xintpl -> cloudcov_daily); + interpolate_monthlyValues(xintpl->cloudcov, interpAsBase1, + xintpl_weather[0] -> cloudcov_daily); - // value for daily index 0 is unchanged because we use here a base1 index - EXPECT_NEAR(xintpl -> cloudcov_daily[0], 0, tol9); + // value for daily index 0 is 10 to make sure base0 is working correctly + EXPECT_NEAR(xintpl_weather[0] -> cloudcov_daily[0], 10.0, tol9); // Expect all xintpld values to be the same (constant input) - for (doy = 1; doy <= Time_get_lastdoy_y(years[k]); doy++) { - EXPECT_NEAR(xintpl -> cloudcov_daily[doy], 10.0, tol9); + for (doy = 0; doy < Time_get_lastdoy_y(years[k]); doy++) { + EXPECT_NEAR(xintpl_weather[0] -> cloudcov_daily[doy], 10.0, tol9); } + // Zero the first value in "cloudcov_daily" for testing on base1 interpolation + xintpl_weather[0] -> cloudcov_daily[0] = 0.; + + interpolate_monthlyValues(xintpl->cloudcov, swTRUE, xintpl_weather[0] -> cloudcov_daily); + + // Value for daily index 0 is unchanged because we use here a base1 index + EXPECT_NEAR(xintpl_weather[0] -> cloudcov_daily[0], 0, tol9); + EXPECT_NEAR(xintpl_weather[0] -> cloudcov_daily[1], 10., tol9); // Test: all monthlyValues equal to 10 except December and March are 20 // (affected by leap/nonleap yrs) xintpl -> cloudcov[Mar] = 20; xintpl -> cloudcov[Dec] = 20; - interpolate_monthlyValues(xintpl -> cloudcov, xintpl -> cloudcov_daily); + interpolate_monthlyValues(xintpl -> cloudcov, interpAsBase1, + xintpl_weather[0] -> cloudcov_daily); /* for (doy = 1; doy <= Time_get_lastdoy_y(years[k]); doy++) { printf( @@ -110,61 +123,61 @@ namespace{ } */ - // value for daily index 0 is unchanged because we use here a base1 index - EXPECT_NEAR(xintpl -> cloudcov_daily[0], 0, tol9); + // value for daily index 0 is ~14.5161 to make sure base0 is working correctly + EXPECT_NEAR(xintpl_weather[0] -> cloudcov_daily[0], 14.516129032, tol9); // Expect mid-Nov to mid-Jan and mid-Feb to mid-Apr values to vary, // all other are the same // Expect Jan 1 to Jan 15 to vary - for (doy = 1; doy <= 15; doy++) { + for (doy = 0; doy < 15; doy++) { EXPECT_NEAR( - xintpl -> cloudcov_daily[doy], - valXd(10, 20, -1, doy2mday(doy), 31), + xintpl_weather[0] -> cloudcov_daily[doy], + valXd(10, 20, -1, doy2mday(doy + 1), 31), tol9 ); } // Expect Jan 15 to Feb 15 to have same values as the constant input - for (doy = 15; doy <= 46; doy++) { - EXPECT_NEAR(xintpl -> cloudcov_daily[doy], 10.0, tol9); + for (doy = 14; doy < 45; doy++) { + EXPECT_NEAR(xintpl_weather[0] -> cloudcov_daily[doy], 10.0, tol9); } // Expect Feb 16 to March 15 to vary (account for leap years) - for (doy = 46; doy <= 74 + lpadd; doy++) { - isMon1 = (Bool)(doy <= 59 + lpadd); + for (doy = 45; doy < 74 + lpadd; doy++) { + isMon1 = (Bool)(doy <= 58 + lpadd); EXPECT_NEAR( - xintpl -> cloudcov_daily[doy], + xintpl_weather[0] -> cloudcov_daily[doy], valXd( isMon1 ? 10 : 20, isMon1 ? 20 : 10, isMon1 ? 1 : -1, - doy2mday(doy), + doy2mday(doy + 1), 28 + lpadd ), tol9 - ) << "year = " << years[k] << " doy = " << doy << " mday = " << doy2mday(doy); + ) << "year = " << years[k] << " doy = " << doy << " mday = " << doy2mday(doy + 1); } // Expect Apr 15 to Nov 15 to have same values as the constant input - for (doy = 105 + lpadd; doy <= 319 + lpadd; doy++) { - EXPECT_NEAR(xintpl -> cloudcov_daily[doy], 10.0, tol9); + for (doy = 104 + lpadd; doy < 319 + lpadd; doy++) { + EXPECT_NEAR(xintpl_weather[0] -> cloudcov_daily[doy], 10.0, tol9); } // Expect Dec 1 to Dec 31 to vary - for (doy = 335 + lpadd; doy <= 365 + lpadd; doy++) { + for (doy = 335 + lpadd; doy < 365 + lpadd; doy++) { isMon1 = (Bool)(doy < 349 + lpadd); EXPECT_NEAR( - xintpl -> cloudcov_daily[doy], + xintpl_weather[0] -> cloudcov_daily[doy], valXd( 20, // Dec value 10, // Nov or Jan value isMon1 ? -1 : 1, - doy2mday(doy), + doy2mday(doy + 1), isMon1 ? 30 : 31 ), tol9 - ) << "year = " << years[k] << " doy = " << doy << " mday = " << doy2mday(doy); + ) << "year = " << years[k] << " doy = " << doy << " mday = " << doy2mday(doy + 1); } diff --git a/tests/gtests/test_WaterBalance.cc b/tests/gtests/test_WaterBalance.cc new file mode 100644 index 000000000..69869210f --- /dev/null +++ b/tests/gtests/test_WaterBalance.cc @@ -0,0 +1,427 @@ +#include "gtest/gtest.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "include/generic.h" +#include "include/myMemory.h" +#include "include/filefuncs.h" +#include "include/rands.h" +#include "include/Times.h" +#include "include/SW_Defines.h" +#include "include/SW_Times.h" +#include "include/SW_Files.h" +#include "include/SW_Carbon.h" +#include "include/SW_Site.h" +#include "include/SW_VegProd.h" +#include "include/SW_VegEstab.h" +#include "include/SW_Model.h" +#include "include/SW_SoilWater.h" +#include "include/SW_Weather.h" +#include "include/SW_Markov.h" +#include "include/SW_Sky.h" +#include "include/SW_Control.h" + +#include "tests/gtests/sw_testhelpers.h" + + + +namespace { + /* Test daily water balance and water cycling: + i) Call function 'SW_CTL_main' which calls 'SW_CTL_run_current_year' for each year + which calls 'SW_SWC_water_flow' for each day + ii) Summarize checks added to debugging code of 'SW_SWC_water_flow' (which is + compiled if flag 'SWDEBUG' is defined) + */ + TEST(WaterBalanceTest, Example1) { // default run == 'testing' example1 + int i; + + // Run the simulation + SW_CTL_main(); + + // Collect and output from daily checks + for (i = 0; i < N_WBCHECKS; i++) { + EXPECT_EQ(0, SW_Soilwat.wbError[i]) << + "Water balance error in test " << + i << ": " << (char*)SW_Soilwat.wbErrorNames[i]; + } + + // Reset to previous global state + Reset_SOILWAT2_after_UnitTest(); + } + + + TEST(WaterBalanceTest, WithSoilTemperature) { + int i; + + // Turn on soil temperature simulations + SW_Site.use_soil_temp = swTRUE; + + // Run the simulation + SW_CTL_main(); + + // Collect and output from daily checks + for (i = 0; i < N_WBCHECKS; i++) { + EXPECT_EQ(0, SW_Soilwat.wbError[i]) << + "Water balance error in test " << + i << ": " << (char*)SW_Soilwat.wbErrorNames[i]; + } + + // Reset to previous global state + Reset_SOILWAT2_after_UnitTest(); + } + + + TEST(WaterBalanceTest, WithPondedWaterRunonRunoff) { + int i; + + // Turn on impermeability of first soil layer, runon, and runoff + SW_Site.lyr[0]->impermeability = 0.95; + SW_Site.percentRunoff = 0.5; + SW_Site.percentRunon = 1.25; + + // Run the simulation + SW_CTL_main(); + + // Collect and output from daily checks + for (i = 0; i < N_WBCHECKS; i++) { + EXPECT_EQ(0, SW_Soilwat.wbError[i]) << + "Water balance error in test " << + i << ": " << (char*)SW_Soilwat.wbErrorNames[i]; + } + + // Reset to previous global state + Reset_SOILWAT2_after_UnitTest(); + } + + + + TEST(WaterBalanceTest, WithWeatherGeneratorOnly) { + int i; + + // Turn on Markov weather generator (and turn off use of historical weather) + SW_Weather.generateWeatherMethod = 2; + SW_Weather.use_weathergenerator_only = swTRUE; + + // Read Markov weather generator input files (they are not normally read) + SW_MKV_setup(); + + // Point to nonexisting weather data + strcpy(SW_Weather.name_prefix, "Input/data_weather_nonexisting/weath"); + + // Prepare weather data + SW_WTH_read(); + SW_WTH_finalize_all_weather(); + + // Run the simulation + SW_CTL_main(); + + // Collect and output from daily checks + for (i = 0; i < N_WBCHECKS; i++) { + EXPECT_EQ(0, SW_Soilwat.wbError[i]) << + "Water balance error in test " << + i << ": " << (char*)SW_Soilwat.wbErrorNames[i]; + } + + // Reset to previous global state + Reset_SOILWAT2_after_UnitTest(); + } + + + TEST(WaterBalanceTest, WithWeatherGeneratorForSomeMissingValues) { + int i; + + // Turn on Markov weather generator + SW_Weather.generateWeatherMethod = 2; + + // Point to partial weather data + strcpy(SW_Weather.name_prefix, "Input/data_weather_missing/weath"); + + // Read Markov weather generator input files (they are not normally read) + SW_MKV_setup(); + + // Prepare weather data + SW_WTH_read(); + SW_WTH_finalize_all_weather(); + + // Run the simulation + SW_CTL_main(); + + // Collect and output from daily checks + for (i = 0; i < N_WBCHECKS; i++) { + EXPECT_EQ(0, SW_Soilwat.wbError[i]) << + "Water balance error in test " << + i << ": " << (char*)SW_Soilwat.wbErrorNames[i]; + } + + // Reset to previous global state + Reset_SOILWAT2_after_UnitTest(); + } + + + TEST(WaterBalanceTest, WithHighGravelVolume) { + int i; + LyrIndex s; + + // Set high gravel volume in all soil layers + ForEachSoilLayer(s) + { + SW_Site.lyr[s]->fractionVolBulk_gravel = 0.99; + } + + // Re-calculate soils + SW_SIT_init_run(); + + // Run the simulation + SW_CTL_main(); + + // Collect and output from daily checks + for (i = 0; i < N_WBCHECKS; i++) { + EXPECT_EQ(0, SW_Soilwat.wbError[i]) << + "Water balance error in test " << + i << ": " << (char*)SW_Soilwat.wbErrorNames[i]; + } + + // Reset to previous global state + Reset_SOILWAT2_after_UnitTest(); + } + + + TEST(WaterBalanceTest, WithVegetationFromClimate1) { + int i; + + // Select method to estimate vegetation from long-term climate + SW_VegProd.veg_method = 1; + + // Re-calculate vegetation + SW_VPD_init_run(); + + // Run the simulation + SW_CTL_main(); + + // Collect and output from daily checks + for (i = 0; i < N_WBCHECKS; i++) { + EXPECT_EQ(0, SW_Soilwat.wbError[i]) << + "Water balance error in test " << + i << ": " << (char*)SW_Soilwat.wbErrorNames[i]; + } + + // Reset to previous global state + Reset_SOILWAT2_after_UnitTest(); + } + + TEST(WaterBalanceTest, WithSWRCvanGenuchten1980) { + int i; + + // Set SWRC and PTF (and SWRC parameter input filename) + strcpy(SW_Site.site_swrc_name, (char *) "vanGenuchten1980"); + SW_Site.site_swrc_type = encode_str2swrc(SW_Site.site_swrc_name); + strcpy(SW_Site.site_ptf_name, (char *) "Rosetta3"); + SW_Site.site_ptf_type = encode_str2ptf(SW_Site.site_ptf_name); + SW_Site.site_has_swrcp = swTRUE; + + Mem_Free(InFiles[eSWRCp]); + InFiles[eSWRCp] = Str_Dup("Input/swrc_params_vanGenuchten1980.in"); + + // Read SWRC parameter input file (which is not read by default) + SW_SWRC_read(); + + // Update soils + SW_SIT_init_run(); + + // Run the simulation + SW_CTL_main(); + + // Collect and output from daily checks + for (i = 0; i < N_WBCHECKS; i++) { + EXPECT_EQ(0, SW_Soilwat.wbError[i]) << + "Water balance error in test " << + i << ": " << (char*)SW_Soilwat.wbErrorNames[i]; + } + + // Reset to previous global state + Reset_SOILWAT2_after_UnitTest(); + } + + + + TEST(WaterBalanceTest, WithSWRCFXW) { + int i; + + // Set SWRC and PTF (and SWRC parameter input filename) + strcpy(SW_Site.site_swrc_name, (char *) "FXW"); + SW_Site.site_swrc_type = encode_str2swrc(SW_Site.site_swrc_name); + strcpy(SW_Site.site_ptf_name, (char *) "neuroFX2021"); + SW_Site.site_ptf_type = encode_str2ptf(SW_Site.site_ptf_name); + SW_Site.site_has_swrcp = swTRUE; + + Mem_Free(InFiles[eSWRCp]); + InFiles[eSWRCp] = Str_Dup("Input/swrc_params_FXW.in"); + + // Read SWRC parameter input file (which is not read by default) + SW_SWRC_read(); + + // Update soils + SW_SIT_init_run(); + + // Run the simulation + SW_CTL_main(); + + // Collect and output from daily checks + for (i = 0; i < N_WBCHECKS; i++) { + EXPECT_EQ(0, SW_Soilwat.wbError[i]) << + "Water balance error in test " << + i << ": " << (char*)SW_Soilwat.wbErrorNames[i]; + } + + // Reset to previous global state + Reset_SOILWAT2_after_UnitTest(); + } + + + TEST(WaterBalanceTest, WithDaymet) { + int i; + + // Point to Daymet weather data + strcpy(SW_Weather.name_prefix, "Input/data_weather_daymet/weath"); + + // Adjust simulation years: we have 2 years of Daymet inputs + SW_Model.startyr = 1980; + SW_Model.endyr = 1981; + + // Describe daily Daymet inputs + SW_Weather.use_cloudCoverMonthly = swFALSE; + SW_Weather.use_windSpeedMonthly = swTRUE; + SW_Weather.use_humidityMonthly = swFALSE; + + SW_Weather.dailyInputIndices[ACTUAL_VP] = 3; + SW_Weather.dailyInputIndices[SHORT_WR] = 4; + SW_Weather.dailyInputFlags[ACTUAL_VP] = swTRUE; + SW_Weather.dailyInputFlags[SHORT_WR] = swTRUE; + SW_Weather.n_input_forcings = 5; + SW_Weather.desc_rsds = 2; // Daymet rsds is flux density over daylight period + + // Prepare weather data + SW_WTH_read(); + SW_WTH_finalize_all_weather(); + + // Run the simulation + SW_CTL_main(); + + // Collect and output from daily checks + for (i = 0; i < N_WBCHECKS; i++) { + EXPECT_EQ(0, SW_Soilwat.wbError[i]) << + "Water balance error in test " << + i << ": " << (char*)SW_Soilwat.wbErrorNames[i]; + } + + // Reset to previous global state + Reset_SOILWAT2_after_UnitTest(); + } + + + TEST(WaterBalanceTest, WithGRIDMET) { + int i; + + // Point to gridMET weather data + strcpy(SW_Weather.name_prefix, "Input/data_weather_gridmet/weath"); + + // Adjust simulation years: we have 2 years of gridMET inputs + SW_Model.startyr = 1980; + SW_Model.endyr = 1981; + + // Describe daily gridMET inputs + SW_Weather.use_cloudCoverMonthly = swFALSE; + SW_Weather.use_windSpeedMonthly = swFALSE; + SW_Weather.use_humidityMonthly = swFALSE; + + SW_Weather.dailyInputIndices[WIND_SPEED] = 3; + SW_Weather.dailyInputIndices[REL_HUMID_MAX] = 4; + SW_Weather.dailyInputIndices[REL_HUMID_MIN] = 5; + SW_Weather.dailyInputIndices[SHORT_WR] = 6; + SW_Weather.dailyInputFlags[REL_HUMID_MAX] = swTRUE; + SW_Weather.dailyInputFlags[REL_HUMID_MIN] = swTRUE; + SW_Weather.dailyInputFlags[WIND_SPEED] = swTRUE; + SW_Weather.dailyInputFlags[SHORT_WR] = swTRUE; + SW_Weather.n_input_forcings = 7; + SW_Weather.desc_rsds = 1; // gridMET rsds is flux density over 24 hours + + // Prepare weather data + SW_WTH_read(); + SW_WTH_finalize_all_weather(); + + // Run the simulation + SW_CTL_main(); + + // Collect and output from daily checks + for (i = 0; i < N_WBCHECKS; i++) { + EXPECT_EQ(0, SW_Soilwat.wbError[i]) << + "Water balance error in test " << + i << ": " << (char*)SW_Soilwat.wbErrorNames[i]; + } + + // Reset to previous global state + Reset_SOILWAT2_after_UnitTest(); + } + + + TEST(WaterBalanceTest, WithMACA) { + int i; + + // Point to MACA weather data + strcpy(SW_Weather.name_prefix, "Input/data_weather_maca/weath"); + + // Adjust simulation years: we have 2 years of MACA inputs + SW_Model.startyr = 1980; + SW_Model.endyr = 1981; + + // Describe daily MACA inputs + SW_Weather.use_cloudCoverMonthly = swFALSE; + SW_Weather.use_windSpeedMonthly = swFALSE; + SW_Weather.use_humidityMonthly = swFALSE; + + SW_Weather.dailyInputIndices[WIND_EAST] = 3; + SW_Weather.dailyInputIndices[WIND_NORTH] = 4; + SW_Weather.dailyInputIndices[REL_HUMID_MAX] = 5; + SW_Weather.dailyInputIndices[REL_HUMID_MIN] = 6; + SW_Weather.dailyInputIndices[SHORT_WR] = 7; + SW_Weather.dailyInputFlags[WIND_EAST] = swTRUE; + SW_Weather.dailyInputFlags[WIND_NORTH] = swTRUE; + SW_Weather.dailyInputFlags[REL_HUMID_MAX] = swTRUE; + SW_Weather.dailyInputFlags[REL_HUMID_MIN] = swTRUE; + SW_Weather.dailyInputFlags[SHORT_WR] = swTRUE; + SW_Weather.n_input_forcings = 8; + SW_Weather.desc_rsds = 1; // MACA rsds is flux density over 24 hours + + // Prepare weather data + SW_WTH_read(); + SW_WTH_finalize_all_weather(); + + // Run the simulation + SW_CTL_main(); + + // Collect and output from daily checks + for (i = 0; i < N_WBCHECKS; i++) { + EXPECT_EQ(0, SW_Soilwat.wbError[i]) << + "Water balance error in test " << + i << ": " << (char*)SW_Soilwat.wbErrorNames[i]; + } + + // Reset to previous global state + Reset_SOILWAT2_after_UnitTest(); + } + + +} // namespace diff --git a/tests/gtests/test_generic.cc b/tests/gtests/test_generic.cc new file mode 100644 index 000000000..6e39b598b --- /dev/null +++ b/tests/gtests/test_generic.cc @@ -0,0 +1,112 @@ +#include "gtest/gtest.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "include/generic.h" +#include "include/SW_Defines.h" + + +namespace { + const unsigned int N = 9; + unsigned int k; + double + x[N] = {-4., -3., -2., -1., 0., 1., 2., 3., 4.}, + // m calculated in R with `for (k in seq_along(x)) print(mean(x[1:k]))` + m[N] = {-4, -3.5, -3, -2.5, -2, -1.5, -1, -0.5, 0}, + // sd calculated in R with `for (k in seq_along(x)) print(sd(x[1:k]))` + sd[N] = {SW_MISSING, 0.7071068, 1., 1.290994, 1.581139, 1.870829, 2.160247, + 2.44949, 2.738613}; + float tol = 1e-6; + + TEST(RunningAggregatorsTest, RunningMean) { + double m_at_k = 0.; + + for (k = 0; k < N; k++) + { + m_at_k = get_running_mean(k + 1, m_at_k, x[k]); + EXPECT_DOUBLE_EQ(m_at_k, m[k]); + } + } + + TEST(RunningAggregatorsTest, RunningSD) { + double ss, sd_at_k; + + for (k = 0; k < N; k++) + { + if (k == 0) + { + ss = get_running_sqr(0., m[k], x[k]); + + } else { + ss += get_running_sqr(m[k - 1], m[k], x[k]); + + sd_at_k = final_running_sd(k + 1, ss); // k is base0 + EXPECT_NEAR(sd_at_k, sd[k], tol); + } + } + } + + TEST(StandardDeviationTest, UnexpectedAndExpectedCases) { + double value[1] = {5.}; + double values[5] = {5.4, 3.4, 7.6, 5.6, 1.8}; + double oneValMissing[5] = {5.4, SW_MISSING, 7.6, 5.6, 1.8}; + + double standardDev = standardDeviation(value, 1); + + // Testing that one value for a standard deviation is `NAN` + EXPECT_TRUE(isnan(standardDev)); + + standardDev = standardDeviation(value, 0); + + // Testing that no value put into standard deviation is `NAN` + EXPECT_DOUBLE_EQ(standardDev, 0.); + + standardDev = standardDeviation(values, 5); + + // Testing the standard deviation function on a normal set of data + EXPECT_NEAR(standardDev, 2.22441, tol); + + standardDev = standardDeviation(oneValMissing, 5); + + // Testing the standard deviation function on a normal set of data with + // one value set to SW_MISSING + EXPECT_NEAR(standardDev, 2.413848, tol); + } + + TEST(MeanTest, UnexpectedAndExpectedCases) { + + double result; + double values[5] = {1.8, 2.2, 10., 13.5, 3.2}; + double oneValMissing[5] = {4.3, 2.6, SW_MISSING, 17.1, 32.4}; + + result = mean(values, 0); + + // Testing that a set of size zero returns `NAN` for a mean + EXPECT_TRUE(isnan(result)); + + result = mean(values, 5); + + // Testing the mean function on a normal set of data + EXPECT_FLOAT_EQ(result, 6.14); + + result = mean(oneValMissing, 5); + + // Testing the mean function on a normal set of data + EXPECT_FLOAT_EQ(result, 14.1); + + } + +} // namespace diff --git a/test/test_rands.cc b/tests/gtests/test_rands.cc similarity index 95% rename from test/test_rands.cc rename to tests/gtests/test_rands.cc index 6d5c69846..15a4f3bfd 100644 --- a/test/test_rands.cc +++ b/tests/gtests/test_rands.cc @@ -14,11 +14,11 @@ #include #include #include -#include "../generic.h" -#include "../myMemory.h" -#include "../filefuncs.h" -#include "../rands.h" -#include "../pcg/pcg_basic.h" +#include "include/generic.h" +#include "include/myMemory.h" +#include "include/filefuncs.h" +#include "include/rands.h" +#include "external/pcg/pcg_basic.h" namespace { diff --git a/tools/check_SOILWAT2.sh b/tools/check_SOILWAT2.sh index c61ccc7b1..5968b10d3 100755 --- a/tools/check_SOILWAT2.sh +++ b/tools/check_SOILWAT2.sh @@ -34,18 +34,18 @@ declare -a port_compilers=( "none" - "mp-gcc49" "mp-gcc5" "mp-gcc7" "mp-gcc9" "mp-gcc10" "mp-gcc11" - "mp-clang-3.3" "mp-clang-5.0" "mp-clang-9.0" "mp-clang-10" "mp-clang-11" "mp-clang-12" "mp-clang-13" + "mp-gcc10" "mp-gcc11" "mp-gcc12" + "mp-clang-10" "mp-clang-11" "mp-clang-12" "mp-clang-13" "mp-clang-14" "mp-clang-15" ) declare -a ccs=( "clang" - "gcc" "gcc" "gcc" "gcc" "gcc" "gcc" - "clang" "clang" "clang" "clang" "clang" "clang" "clang" + "gcc" "gcc" "gcc" + "clang" "clang" "clang" "clang" "clang" "clang" ) declare -a cxxs=( "clang++" - "g++" "g++" "g++" "g++" "g++" "g++" - "clang++" "clang++" "clang++" "clang++" "clang++" "clang++" "clang" + "g++" "g++" "g++" + "clang++" "clang++" "clang++" "clang++" "clang++" "clang++" ) @@ -54,11 +54,11 @@ ncomp=${#ccs[@]} #--- Function to check testing output check_testing_output () { - if diff -q -x "\.DS_Store" -x "\.gitignore" testing/Output/ testing/Output_ref/ >/dev/null 2>&1; then + if diff -q -x "\.DS_Store" -x "\.gitignore" tests/example/Output/ tests/example/Output_ref/ >/dev/null 2>&1; then echo $'\n'"Example output is reproduced by binary!"$'\n'$'\n' else echo $'\n'"Error: Example output is not reproduced by binary!" - diff -qs -x "\.DS_Store" -x "\.gitignore" testing/Output/ testing/Output_ref/ + diff -qs -x "\.DS_Store" -x "\.gitignore" tests/example/Output/ tests/example/Output_ref/ echo $'\n'$'\n' fi } @@ -93,15 +93,15 @@ for ((k = 0; k < ncomp; k++)); do Run binary with ${port_compilers[k]} ...$'\n'\ -------------------------------------------------- - CC=${ccs[k]} CXX=${cxxs[k]} make clean bin bint_run + CC=${ccs[k]} CXX=${cxxs[k]} make clean bin_run if [ ${k} -eq 0 ]; then # Save default testing output as reference for future comparisons - cp -r testing/Output testing/Output_ref + cp -r tests/example/Output tests/example/Output_ref fi check_testing_output - CC=${ccs[k]} CXX=${cxxs[k]} make clean bin_debug_severe bint_run + CC=${ccs[k]} CXX=${cxxs[k]} make clean bin_debug_severe check_testing_output if command -v valgrind >/dev/null 2>&1; then @@ -114,8 +114,8 @@ for ((k = 0; k < ncomp; k++)); do Run tests with ${port_compilers[k]} ...$'\n'\ -------------------------------------------------- - CC=${ccs[k]} CXX=${cxxs[k]} make clean test test_run - CC=${ccs[k]} CXX=${cxxs[k]} make clean test_severe test_run + CC=${ccs[k]} CXX=${cxxs[k]} make clean test_run + CC=${ccs[k]} CXX=${cxxs[k]} make clean test_severe echo $'\n'$'\n'\ @@ -125,7 +125,7 @@ for ((k = 0; k < ncomp; k++)); do # CC=${ccs[k]} CXX=${cxxs[k]} ASAN_OPTIONS=detect_leaks=1 LSAN_OPTIONS=suppressions=.LSAN_suppr.txt make clean test_severe test_run # https://github.com/google/sanitizers/wiki/AddressSanitizer - CC=${ccs[k]} CXX=${cxxs[k]} ASAN_OPTIONS=detect_leaks=1:strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1 LSAN_OPTIONS=suppressions=.LSAN_suppr.txt make clean test_severe test_run + CC=${ccs[k]} CXX=${cxxs[k]} make clean test_leaks echo $'\n'$'\n'\ diff --git a/tools/many_test_runs.sh b/tools/many_test_runs.sh index d0c849f05..e56f7b343 100755 --- a/tools/many_test_runs.sh +++ b/tools/many_test_runs.sh @@ -4,16 +4,13 @@ # $N number of test runs; default 10 if empty or unset iters=${N:-10} -sw_test=${sw_test:-"sw_test"} echo $(date): will run ${iters} test replicates. -if [ ! -x sw_test ]; then - make test -fi +make test for i in $(seq 1 $iters); do - temp=$( ./sw_test | grep -i "Fail" ) + temp=$( make test_run | grep -i "Fail" ) if [ ! -z "${temp}" ]; then echo Replicate $i failed with: diff --git a/tools/plot__SW2_PET_Test__petfunc_by_temps.R b/tools/plot__SW2_PET_Test__petfunc_by_temps.R index 9ee7f4e88..7a27eecae 100644 --- a/tools/plot__SW2_PET_Test__petfunc_by_temps.R +++ b/tools/plot__SW2_PET_Test__petfunc_by_temps.R @@ -4,7 +4,7 @@ # Run SOILWAT2 unit tests with appropriate flag # ``` -# CPPFLAGS=-DSW2_PET_Test__petfunc_by_temps make test test_run +# CPPFLAGS=-DSW2_PET_Test__petfunc_by_temps make test_run # ``` # # Produce plots based on output generated above @@ -13,7 +13,7 @@ # ``` #------ -dir_out <- file.path("testing", "Output") +dir_out <- file.path("tests", "example", "Output") tag_filename <- "SW2_PET_Test__petfunc_by_temps" @@ -32,7 +32,7 @@ if (length(file_sw2_test_output) == 0) { } -if (!require("ggplot2", quietly = TRUE)) { +if (!requireNamespace("ggplot2", quietly = TRUE)) { stop("Package 'ggplot2' is required.") } @@ -75,26 +75,35 @@ if (do_plot) { width = 2 * n_panels[2] ) - tmp <- ggplot( + tmp <- ggplot2::ggplot( data_pet[ids_fH, ], - aes(Temperature_C, PET_mm, group = RH_pct, color = RH_pct) + ggplot2::aes(Temperature_C, PET_mm, group = RH_pct, color = RH_pct) ) + - xlim(-10, 40) + - labs(x = "Temperature (C)", y = "Annual PET (mm)", color = "RH (%)") + - geom_line(size = 0.75) + - scale_color_viridis_c(direction = -1) + - facet_grid( + ggplot2::xlim(-10, 40) + + ggplot2::labs(x = "Temperature (C)", y = "Annual PET (mm)", color = "RH (%)") + + ggplot2::geom_line(linewidth = 0.75) + + ggplot2::scale_color_viridis_c(direction = -1) + + ggplot2::facet_grid( windspeed_m_per_s ~ cloudcover_pct, labeller = all_labeller - ) + - egg::theme_article() + - theme( + ) + + if (requireNamespace("egg", quietly = TRUE)) { + tmp <- tmp + + egg::theme_article() + } else { + tmp <- tmp + + ggplot2::theme_classic() + } + + tmp <- tmp + + ggplot2::theme( legend.position = c( 0.4 / (n_panels[2] + 1), 1 - 0.4 / (n_panels[1] + 1) ), - legend.key.size = unit(0.015, units = "npc"), - legend.title = element_text(size = 10) + legend.key.size = ggplot2::unit(0.015, units = "npc"), + legend.title = ggplot2::element_text(size = 10) ) plot(tmp) @@ -113,26 +122,35 @@ if (do_plot) { width = 2 * n_panels[2] ) - tmp <- ggplot( + tmp <- ggplot2::ggplot( data_pet[ids_fH, ], - aes(Temperature_C, PET_mm, group = cloudcover_pct, color = cloudcover_pct) + ggplot2::aes(Temperature_C, PET_mm, group = cloudcover_pct, color = cloudcover_pct) ) + - xlim(-10, 40) + - labs(x = "Temperature (C)", y = "Annual PET (mm)", color = "Clouds (%)") + - geom_line(size = 0.75) + - scale_color_viridis_c(direction = -1) + - facet_grid( + ggplot2::xlim(-10, 40) + + ggplot2::labs(x = "Temperature (C)", y = "Annual PET (mm)", color = "Clouds (%)") + + ggplot2::geom_line(linewidth = 0.75) + + ggplot2::scale_color_viridis_c(direction = -1) + + ggplot2::facet_grid( RH_pct ~ windspeed_m_per_s, labeller = all_labeller - ) + - egg::theme_article() + - theme( + ) + + if (requireNamespace("egg", quietly = TRUE)) { + tmp <- tmp + + egg::theme_article() + } else { + tmp <- tmp + + ggplot2::theme_classic() + } + + tmp <- tmp + + ggplot2::theme( legend.position = c( 0.4 / (n_panels[2] + 1), 1 - 0.4 / (n_panels[1] + 1) ), - legend.key.size = unit(0.015, units = "npc"), - legend.title = element_text(size = 10) + legend.key.size = ggplot2::unit(0.015, units = "npc"), + legend.title = ggplot2::element_text(size = 10) ) plot(tmp) @@ -151,26 +169,35 @@ if (do_plot) { width = 2 * n_panels[2] ) - tmp <- ggplot( + tmp <- ggplot2::ggplot( data_pet[ids_fH, ], - aes(Temperature_C, PET_mm, group = windspeed_m_per_s, color = windspeed_m_per_s) + ggplot2::aes(Temperature_C, PET_mm, group = windspeed_m_per_s, color = windspeed_m_per_s) ) + - xlim(-10, 40) + - labs(x = "Temperature (C)", y = "Annual PET (mm)", color = "Wind speed (m/s)") + - geom_line(size = 0.75) + - scale_color_viridis_c(direction = -1) + - facet_grid( + ggplot2::xlim(-10, 40) + + ggplot2::labs(x = "Temperature (C)", y = "Annual PET (mm)", color = "Wind speed (m/s)") + + ggplot2::geom_line(linewidth = 0.75) + + ggplot2::scale_color_viridis_c(direction = -1) + + ggplot2::facet_grid( RH_pct ~ cloudcover_pct, labeller = all_labeller - ) + - egg::theme_article() + - theme( + ) + + if (requireNamespace("egg", quietly = TRUE)) { + tmp <- tmp + + egg::theme_article() + } else { + tmp <- tmp + + ggplot2::theme_classic() + } + + tmp <- tmp + + ggplot2::theme( legend.position = c( 0.4 / (n_panels[2] + 1), 1 - 0.4 / (n_panels[1] + 1) ), - legend.key.size = unit(0.015, units = "npc"), - legend.title = element_text(size = 10) + legend.key.size = ggplot2::unit(0.015, units = "npc"), + legend.title = ggplot2::element_text(size = 10) ) plot(tmp) @@ -192,26 +219,35 @@ if (do_plot) { width = 2 * n_panels[2] ) - tmp <- ggplot( + tmp <- ggplot2::ggplot( data_pet[ids_cc, ], - aes(Temperature_C, PET_mm, group = factor(fH_gt), color = factor(fH_gt)) + ggplot2::aes(Temperature_C, PET_mm, group = factor(fH_gt), color = factor(fH_gt)) ) + - xlim(-10, 40) + - labs(x = "Temperature (C)", y = "Annual PET (mm)", color = "fRSDS (%)") + - geom_line(size = 0.75) + - scale_color_viridis_d(direction = -1) + - facet_grid( + ggplot2::xlim(-10, 40) + + ggplot2::labs(x = "Temperature (C)", y = "Annual PET (mm)", color = "fRSDS (%)") + + ggplot2::geom_line(linewidth = 0.75) + + ggplot2::scale_color_viridis_d(direction = -1) + + ggplot2::facet_grid( RH_pct ~ windspeed_m_per_s, labeller = all_labeller - ) + - egg::theme_article() + - theme( + ) + + if (requireNamespace("egg", quietly = TRUE)) { + tmp <- tmp + + egg::theme_article() + } else { + tmp <- tmp + + ggplot2::theme_classic() + } + + tmp <- tmp + + ggplot2::theme( legend.position = c( 0.4 / (n_panels[2] + 1), 1 - 0.4 / (n_panels[1] + 1) ), - legend.key.size = unit(0.015, units = "npc"), - legend.title = element_text(size = 10) + legend.key.size = ggplot2::unit(0.015, units = "npc"), + legend.title = ggplot2::element_text(size = 10) ) plot(tmp) diff --git a/tools/plot__SW2_SoilTemperature.R b/tools/plot__SW2_SoilTemperature.R index 0e332fc4f..229e804d0 100644 --- a/tools/plot__SW2_SoilTemperature.R +++ b/tools/plot__SW2_SoilTemperature.R @@ -3,14 +3,14 @@ # ``` # Rscript tools/plot__SW2_SoilTemperature.R # ``` -# after running SOILWAT2 on the "testing" exampel, e.g., +# after running SOILWAT2 on "tests/example", e.g., # ``` -# make bin bint_run +# make bin_run # ``` # Note: do not commit output (figures) from this script -dir_testing <- "testing" +dir_testing <- file.path("tests", "example") dir_sw2_input <- file.path(dir_testing, "Input") dir_sw2_output <- file.path(dir_testing, "Output") @@ -27,17 +27,21 @@ x <- read.csv(file.path(dir_sw2_output, "sw2_daily_slyrs.csv")) tag_soil_temp <- "SOILTEMP_Lyr" tag_surface_temp <- "SOILTEMP_surfaceTemp_C" -if (!any(grepl(tag_surface_temp, colnames(x)))) { - tag_surface_temp <- "SOILTEMP_surfaceTemp" +if (any(grepl(tag_surface_temp, colnames(x)))) { + xsurf <- x +} else { + tag_surface_temp <- "TEMP_surfaceTemp" + xsurf <- xw } #--- Convert to long format # Surface soil temperature -tmp_vars1 <- grep(tag_surface_temp, colnames(x), value = TRUE) +tmp_vars1 <- grep(tag_surface_temp, colnames(xsurf), value = TRUE) +stopifnot(length(tmp_vars1) > 0) tmp1 <- as.data.frame(tidyr::pivot_longer( - x[, c("Year", "Day", tmp_vars1)], + xsurf[, c("Year", "Day", tmp_vars1)], cols = tidyr::all_of(tmp_vars1), names_to = "Fun", names_pattern = paste0(tag_surface_temp, "_(.*)_C"), diff --git a/tools/plot__SW2_SolarPosition_Test__hourangles_by_lat_and_doy.R b/tools/plot__SW2_SolarPosition_Test__hourangles_by_lat_and_doy.R index bab183138..7fe4bb28c 100644 --- a/tools/plot__SW2_SolarPosition_Test__hourangles_by_lat_and_doy.R +++ b/tools/plot__SW2_SolarPosition_Test__hourangles_by_lat_and_doy.R @@ -3,7 +3,7 @@ # Run SOILWAT2 unit tests with appropriate flag # ``` -# CPPFLAGS=-DSW2_SolarPosition_Test__hourangles_by_lat_and_doy make test test_run +# CPPFLAGS=-DSW2_SolarPosition_Test__hourangles_by_lat_and_doy make test_run # ``` # # Produce plots based on output generated above @@ -12,7 +12,7 @@ # ``` #------ -dir_out <- file.path("testing", "Output") +dir_out <- file.path("tests", "example", "Output") tag_filename <- "SW2_SolarPosition_Test__hourangles_by_lat_and_doy" diff --git a/tools/plot__SW2_SolarPosition_Test__hourangles_by_lats.R b/tools/plot__SW2_SolarPosition_Test__hourangles_by_lats.R index e2773970d..14453f74d 100644 --- a/tools/plot__SW2_SolarPosition_Test__hourangles_by_lats.R +++ b/tools/plot__SW2_SolarPosition_Test__hourangles_by_lats.R @@ -4,7 +4,7 @@ # Run SOILWAT2 unit tests with appropriate flag # ``` -# CPPFLAGS=-DSW2_SolarPosition_Test__hourangles_by_lats make test test_run +# CPPFLAGS=-DSW2_SolarPosition_Test__hourangles_by_lats make test_run # ``` # # Produce plots based on output generated above @@ -13,7 +13,7 @@ # ``` #------ -dir_out <- file.path("testing", "Output") +dir_out <- file.path("tests", "example", "Output") tag_filename <- "SW2_SolarPosition_Test__hourangles_by_lats" diff --git a/tools/run_debug.sh b/tools/run_debug.sh new file mode 100755 index 000000000..962027065 --- /dev/null +++ b/tools/run_debug.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# run as `./tools/run_debug.sh` +# note: consider cleaning previous build artifacts, e.g., `make clean_build` + +debug_flags="-g -O0 -DSWDEBUG" +instr_flags="-fstack-protector-all" + +SW2_FLAGS=""$debug_flags" "$instr_flags"" make bin_run diff --git a/tools/run_debug_severe.sh b/tools/run_debug_severe.sh new file mode 100755 index 000000000..11ee58db3 --- /dev/null +++ b/tools/run_debug_severe.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# run as `./tools/run_debug_severe.sh` +# note: consider cleaning previous build artifacts, e.g., `make clean_build` + +debug_flags="-g -O0 -DSWDEBUG" + +warning_flags_severe_cc="\ +-Wall -Wextra \ +-Wpedantic \ +-Werror \ +-Wcast-align \ +-Wmissing-declarations \ +-Wredundant-decls \ +-Wstrict-prototypes" +# '-Wstrict-prototypes' is valid for C/ObjC but not for C++ + +instr_flags_severe="\ +-fstack-protector-all \ +-fsanitize=undefined \ +-fsanitize=address \ +-fno-omit-frame-pointer \ +-fno-common" +# -fstack-protector-strong (gcc >= v4.9) +# (gcc >= 4.0) -D_FORTIFY_SOURCE: lightweight buffer overflow protection to some memory and string functions +# (gcc >= 4.8; llvm >= 3.1) -fsanitize=address: AdressSanitizer: replaces `mudflap` run time checker; https://github.com/google/sanitizers/wiki/AddressSanitizer +# -fno-omit-frame-pointer: allows fast unwinder to work properly for ASan +# -fno-common: allows ASan to instrument global variables +# (gcc >= 4.9; llvm >= 3.3) -fsanitize=undefined: UndefinedBehaviorSanitizer + + + +SW2_FLAGS=""$debug_flags" "$warning_flags_severe_cc" "$instr_flags_severe"" make bin_run diff --git a/tools/run_doxygen.sh b/tools/run_doxygen.sh index 79215e9fa..cd0a13064 100755 --- a/tools/run_doxygen.sh +++ b/tools/run_doxygen.sh @@ -18,6 +18,13 @@ log=${doc_path}"/log_doxygen.log" # logfile for doxygen output log_tmp=${doc_path}"/log_doxygen_tmp.log" doxexcept=${doc_path}"/doxygen_exceptions.txt" # filename that lists exceptions (one per line) +# Warn if bibtex is not available +# bibtex is required to resolve `\cite` and build bibliography +if ! type -P bibtex &> /dev/null; then + echo "bibtex is not available but required to build bibliography." + # exit 1 +fi + # Downgrade/upgrade Doxyfile in case this runs an different doxygen version, e.g., # travis-ci is currently on 1.8.11 #if [[ $(doxygen --version) != "1.8.17" ]]; then @@ -32,14 +39,19 @@ doxygen ${doxy} > ${log} 2>&1 # Make sure that there is such a (readable) file if [ -r ${doxexcept} ]; then # Make sure that there are exceptions in the file - if [ $(wc -l < ${doxexcept}) -ne 0 ]; then + if [ $(wc -w < ${doxexcept}) -ne 0 ]; then # Remove exceptions from the logfile - grep -v -f ${doxexcept} ${log} > ${log_tmp} + grep --invert-match -f ${doxexcept} ${log} > ${log_tmp} mv ${log_tmp} ${log} rm -f ${log_tmp} fi fi +# Fail if log file is completely empty (likely a grep-parsing error) +if [ $(wc -w < ${log}) -eq 0 ]; then + echo "doxygen log file is empty pointing to a likely error." + exit 1 +fi # Examine log file for remaining warnings/errors warnings="$(grep -iE "warning|error" ${log} 2>&1 || echo "")" diff --git a/tools/run_gcov.sh b/tools/run_gcov.sh index fcaac86c9..dc1102153 100755 --- a/tools/run_gcov.sh +++ b/tools/run_gcov.sh @@ -1,12 +1,27 @@ #!/bin/bash +# run as `./tools/run_gcov.sh` +# note: consider cleaning previous artifacts, e.g., `make clean_cov` + +# note: `--coverage` is equivalent to `-fprofile-arcs -ftest-coverage` + +# note: make sure that compiler and cover tool version agree, i.e., +# `CC=gcc CXX=g++ make clean cov` +# `CC=clang CXX=clang++ GCOV="llvm-cov-mp-14 gcov" make clean_cov cov` + +GCOV="${GCOV:-gcov}" + +SW2_FLAGS="--coverage" make test_run + +dir_build_test="build/test" +dir_src="src" sources_tests='SW_Main_lib.c SW_VegEstab.c SW_Control.c generic.c rands.c Times.c mymemory.c filefuncs.c SW_Files.c SW_Model.c SW_Site.c SW_SoilWater.c SW_Markov.c SW_Weather.c SW_Sky.c SW_Output_mock.c - SW_VegProd.c SW_Flow_lib.c SW_Flow.c SW_Carbon.c' + SW_VegProd.c SW_Flow_lib_PET.c SW_Flow_lib.c SW_Flow.c SW_Carbon.c' for sf in $sources_tests do - gcov $sf + ${GCOV} -r -p -s "$dir_src" -o "$dir_build_test" "$dir_src"/"$sf" done diff --git a/tools/run_test_leaks.sh b/tools/run_test_leaks.sh new file mode 100755 index 000000000..c06ab7236 --- /dev/null +++ b/tools/run_test_leaks.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +# run as `./tools/run_test_leaks.sh` +# note: consider cleaning previous build artifacts, e.g., `make clean_test` + +debug_flags="-g -O0 -DSWDEBUG" + +warning_flags_severe_cxx="\ +-Wall -Wextra \ +-Wpedantic \ +-Werror \ +-Wcast-align \ +-Wmissing-declarations \ +-Wredundant-decls \ +-Wno-error=deprecated" +# TODO: address underlying problems so that we can eliminate +# `-Wno-error=deprecated` +# (https://github.com/DrylandEcology/SOILWAT2/issues/208): +# "treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated" + + +instr_flags_severe="\ +-fstack-protector-all \ +-fsanitize=undefined \ +-fsanitize=address \ +-fno-omit-frame-pointer \ +-fno-common" +# -fstack-protector-strong (gcc >= v4.9) +# (gcc >= 4.0) -D_FORTIFY_SOURCE: lightweight buffer overflow protection to some memory and string functions +# (gcc >= 4.8; llvm >= 3.1) -fsanitize=address: AdressSanitizer: replaces `mudflap` run time checker; https://github.com/google/sanitizers/wiki/AddressSanitizer +# -fno-omit-frame-pointer: allows fast unwinder to work properly for ASan +# -fno-common: allows ASan to instrument global variables +# (gcc >= 4.9; llvm >= 3.3) -fsanitize=undefined: UndefinedBehaviorSanitizer + + +# Note: # Apple clang does not support "AddressSanitizer: detect_leaks" (at least as of clang-1200.0.32.29) +ASAN_OPTIONS=detect_leaks=1:strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1 LSAN_OPTIONS=suppressions=../.LSAN_suppr.txt SW2_FLAGS=""$debug_flags" "$warning_flags_severe_cxx" "$instr_flags_severe"" make test_run diff --git a/tools/run_test_severe.sh b/tools/run_test_severe.sh new file mode 100755 index 000000000..13c96be14 --- /dev/null +++ b/tools/run_test_severe.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# run as `./tools/run_test_severe.sh` +# note: consider cleaning previous build artifacts, e.g., `make clean_test` +# note: regular unit tests are run with `make test_run` + + +debug_flags="-g -O0 -DSWDEBUG" + +warning_flags_severe_cxx="\ +-Wall -Wextra \ +-Wpedantic \ +-Werror \ +-Wcast-align \ +-Wmissing-declarations \ +-Wredundant-decls \ +-Wno-error=deprecated" +# TODO: address underlying problems so that we can eliminate +# `-Wno-error=deprecated` +# (https://github.com/DrylandEcology/SOILWAT2/issues/208): +# "treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated" + + +instr_flags_severe="\ +-fstack-protector-all \ +-fsanitize=undefined \ +-fsanitize=address \ +-fno-omit-frame-pointer \ +-fno-common" +# -fstack-protector-strong (gcc >= v4.9) +# (gcc >= 4.0) -D_FORTIFY_SOURCE: lightweight buffer overflow protection to some memory and string functions +# (gcc >= 4.8; llvm >= 3.1) -fsanitize=address: AdressSanitizer: replaces `mudflap` run time checker; https://github.com/google/sanitizers/wiki/AddressSanitizer +# -fno-omit-frame-pointer: allows fast unwinder to work properly for ASan +# -fno-common: allows ASan to instrument global variables +# (gcc >= 4.9; llvm >= 3.3) -fsanitize=undefined: UndefinedBehaviorSanitizer + + + +SW2_FLAGS=""$debug_flags" "$warning_flags_severe_cxx" "$instr_flags_severe"" make test_run