diff --git a/NEWS.md b/NEWS.md index 314759842..8910117ff 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,19 @@ +# SOILWAT2 v6.7.0 +* This version produces exactly the same simulation output + as the previous release under default values + (i.e., vegetation establishment is turned off). + +* Functionality to calculate and output establishment/recruitment of species + now works again and is now covered by tests (issue #336, @dschlaep). + Note that this functionality assesses yearly the chances of + species to recruit/establish based on simulated daily conditions; + however, establishment/recruitment outcomes are not utilized to inform the + simulation. + +## Changes to inputs +* New input via `".estab"` sets the vegetation type of + a species establishment parameters'. + # SOILWAT2 v6.6.0 * Random number generators now produce sequences that can be exactly reproduced. diff --git a/SW_Control.c b/SW_Control.c index a3a77b344..978a19e8e 100644 --- a/SW_Control.c +++ b/SW_Control.c @@ -160,7 +160,7 @@ void SW_CTL_init_run(void) { SW_PET_init_run(); SW_SKY_init_run(); SW_SIT_init_run(); - // SW_VES_init_run() not needed + SW_VES_init_run(); // must run after `SW_SIT_init_run()` SW_VPD_init_run(); SW_FLW_init_run(); SW_ST_init_run(); diff --git a/SW_Control.h b/SW_Control.h index d1239c3aa..246fa4fbd 100644 --- a/SW_Control.h +++ b/SW_Control.h @@ -18,6 +18,8 @@ #ifndef SW_CONTROL_H #define SW_CONTROL_H +#include "generic.h" // for `Bool`, `swTRUE`, `swFALSE` + #ifdef __cplusplus extern "C" { #endif diff --git a/SW_Output.c b/SW_Output.c index f01717815..dc614b0dd 100644 --- a/SW_Output.c +++ b/SW_Output.c @@ -1946,8 +1946,7 @@ void SW_OUT_new_year(void) -int SW_OUT_read_onekey(OutKey k, OutSum sumtype, char period[], int first, - int last, char msg[]) +int SW_OUT_read_onekey(OutKey k, OutSum sumtype, int first, int last, char msg[]) { int res = 0; // return value indicating type of message if any @@ -1987,11 +1986,6 @@ int SW_OUT_read_onekey(OutKey k, OutSum sumtype, char period[], int first, SW_Output[k].sumtype = eSW_Sum; first = 1; last = 366; - #ifndef RSOILWAT - strcpy(period, "YR"); - #else - if (period) {} // avoid `-Wunused-parameter` - #endif } else if ((k == eSW_AllVeg || k == eSW_ET || k == eSW_AllWthr || k == eSW_AllH2O)) { @@ -2159,10 +2153,13 @@ void SW_OUT_read(void) #endif // Fill information into `SW_Output[k]` - msg_type = SW_OUT_read_onekey(k, + msg_type = SW_OUT_read_onekey( + k, str2stype(Str_ToUpper(sumtype, upsum)), - period, first, !Str_CompareI("END", (char *)last) ? 366 : atoi(last), - msg); + first, + !Str_CompareI("END", (char *)last) ? 366 : atoi(last), + msg + ); if (msg_type != 0) { if (msg_type > 0) { diff --git a/SW_Output.h b/SW_Output.h index d4a2f77fa..e9c2980f7 100644 --- a/SW_Output.h +++ b/SW_Output.h @@ -247,8 +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, char period[], int first, - int last, char msg[]); +int SW_OUT_read_onekey(OutKey k, OutSum sumtype, int first, int last, char msg[]); void SW_OUT_read(void); void SW_OUT_sum_today(ObjType otyp); void SW_OUT_write_today(void); diff --git a/SW_VegEstab.c b/SW_VegEstab.c index ccbf07805..24179929d 100644 --- a/SW_VegEstab.c +++ b/SW_VegEstab.c @@ -45,6 +45,7 @@ #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" @@ -130,7 +131,7 @@ void SW_VES_deconstruct(void) // De-allocate days and parameters if (SW_VegEstab.count > 0) { - if (!isnull(SW_VegEstab.p_oagg[pd]->days)) { + if (pd > eSW_Day && !isnull(SW_VegEstab.p_oagg[pd]->days)) { Mem_Free(SW_VegEstab.p_oagg[eSW_Year]->days); } @@ -163,54 +164,110 @@ void SW_VES_new_year(void) { } /** -@brief Reads in file for SW_VegEstab +@brief Reads in file for SW_VegEstab and species establishment parameters */ void SW_VES_read(void) { - /* =================================================== */ - FILE *f; + SW_VES_read2(swTRUE, swTRUE); +} - MyFileName = SW_F_name(eVegEstab); - f = OpenFile(MyFileName, "r"); - SW_VegEstab.use = swTRUE; +/** +@brief Reads in file for SW_VegEstab and species establishment parameters + +@param use_VegEstab Overall decision if user inputs for vegetation establishment + should be processed. +@param consider_InputFlag Should the user input flag read from `"estab.in"` be + considered for turning on/off calculations of vegetation establishment. + +@note + - Establishment is calculated under the following conditions + - there are input files with species establishment parameters + - at least one of those files is correctly listed in `"estab.in"` + - `use_VegEstab` is turned on (`swTRUE`) and + - `consider_InputFlag` is off + - OR `consider_InputFlag` is on and the input flag in `"estab.in"` is on + - Establishment results are included in the output files only + if `"ESTABL"` is turned on in `"outsetup.in"` +*/ +void SW_VES_read2(Bool use_VegEstab, Bool consider_InputFlag) { + + SW_VES_deconstruct(); + SW_VES_construct(); + + SW_VegEstab.count = 0; + SW_VegEstab.use = use_VegEstab; + + if (SW_VegEstab.use) { + FILE *f; + char buf[FILENAME_MAX]; + + MyFileName = SW_F_name(eVegEstab); + f = OpenFile(MyFileName, "r"); + + if (!GetALine(f, inbuf) || (consider_InputFlag && *inbuf == '0')) { + /* turn off vegetation establishment if either + * no species listed + * if user input flag is set to 0 and we don't ignore that input, + i.e.,`consider_InputFlag` is set to `swTRUE` + */ + SW_VegEstab.use = swFALSE; + if (EchoInits) { + LogError(logfp, LOGNOTE, "Establishment not used.\n"); + } - /* if data file empty or useflag=0, assume no - * establishment checks and just continue the model run. */ - if (!GetALine(f, inbuf) || *inbuf == '0') { - SW_VegEstab.use = swFALSE; - if (EchoInits) - LogError(logfp, LOGNOTE, "Establishment not used.\n"); - CloseFile(&f); - return; - } + } else { + /* read file names with species establishment parameters + and read those files one by one + */ + while (GetALine(f, inbuf)) { + strcpy(buf, _ProjDir); // add `_ProjDir` to path, e.g., for STEPWAT2 + strcat(buf, inbuf); + _read_spp(buf); + } - while (GetALine(f, inbuf)) { - _read_spp(inbuf); + SW_VegEstab_construct(); + } + + CloseFile(&f); } +} - CloseFile(&f); - SW_VegEstab_construct(); +/** +@brief Construct SW_VegEstab output variables +*/ +void SW_VegEstab_construct(void) +{ + if (SW_VegEstab.count > 0) { + SW_VegEstab.p_oagg[eSW_Year]->days = (TimeInt *) Mem_Calloc( + SW_VegEstab.count, sizeof(TimeInt), "SW_VegEstab_construct()"); - if (EchoInits) - _echo_VegEstab(); + SW_VegEstab.p_accu[eSW_Year]->days = (TimeInt *) Mem_Calloc( + SW_VegEstab.count, sizeof(TimeInt), "SW_VegEstab_construct()"); + } } + /** -@brief Construct SW_VegEstab variable + @brief Initialization and checks of species establishment parameters + + This works correctly only after + * species establishment parameters are read from file by `SW_VES_read()` + * soil layers are initialized by `SW_SIT_init_run()` */ -void SW_VegEstab_construct(void) -{ +void SW_VES_init_run(void) { IntU i; - for (i = 0; i < SW_VegEstab.count; i++) + for (i = 0; i < SW_VegEstab.count; i++) { _spp_init(i); + } - if (SW_VegEstab.count > 0) { - SW_VegEstab.p_accu[eSW_Year]->days = (TimeInt *) Mem_Calloc( - SW_VegEstab.count, sizeof(TimeInt), "SW_VegEstab_construct()"); + if (EchoInits) { + _echo_VegEstab(); } } + + /** @brief Check that each count coincides with a day of the year. */ @@ -327,7 +384,7 @@ static void _zero_state(unsigned int sppnum) { static void _read_spp(const char *infile) { /* =================================================== */ SW_VEGESTAB_INFO *v; - const int nitems = 15; + const int nitems = 16; FILE *f; int lineno = 0; char name[80]; /* only allow 4 char sppnames */ @@ -347,45 +404,48 @@ static void _read_spp(const char *infile) { strcpy(name, inbuf); break; case 1: - v->estab_lyrs = atoi(inbuf); + v->vegType = atoi(inbuf); break; case 2: - v->bars[SW_GERM_BARS] = fabs(atof(inbuf)); + v->estab_lyrs = atoi(inbuf); break; case 3: - v->bars[SW_ESTAB_BARS] = fabs(atof(inbuf)); + v->bars[SW_GERM_BARS] = fabs(atof(inbuf)); break; case 4: - v->min_pregerm_days = atoi(inbuf); + v->bars[SW_ESTAB_BARS] = fabs(atof(inbuf)); break; case 5: - v->max_pregerm_days = atoi(inbuf); + v->min_pregerm_days = atoi(inbuf); break; case 6: - v->min_wetdays_for_germ = atoi(inbuf); + v->max_pregerm_days = atoi(inbuf); break; case 7: - v->max_drydays_postgerm = atoi(inbuf); + v->min_wetdays_for_germ = atoi(inbuf); break; case 8: - v->min_wetdays_for_estab = atoi(inbuf); + v->max_drydays_postgerm = atoi(inbuf); break; case 9: - v->min_days_germ2estab = atoi(inbuf); + v->min_wetdays_for_estab = atoi(inbuf); break; case 10: - v->max_days_germ2estab = atoi(inbuf); + v->min_days_germ2estab = atoi(inbuf); break; case 11: - v->min_temp_germ = atof(inbuf); + v->max_days_germ2estab = atoi(inbuf); break; case 12: - v->max_temp_germ = atof(inbuf); + v->min_temp_germ = atof(inbuf); break; case 13: - v->min_temp_estab = atof(inbuf); + v->max_temp_germ = atof(inbuf); break; case 14: + v->min_temp_estab = atof(inbuf); + break; + case 15: v->max_temp_estab = atof(inbuf); break; } @@ -443,33 +503,85 @@ static void _sanity_check(unsigned int sppnum) { /* =================================================== */ SW_LAYER_INFO **lyr = SW_Site.lyr; SW_VEGESTAB_INFO *v = SW_VegEstab.parms[sppnum]; - LyrIndex min_transp_lyrs; - int k; - min_transp_lyrs = SW_Site.n_transp_lyrs[SW_TREES]; - ForEachVegType(k) { - min_transp_lyrs = min(min_transp_lyrs, SW_Site.n_transp_lyrs[k]); + double mean_wiltpt; + unsigned int i; + + if (v->vegType >= NVEGTYPES) { + LogError( + logfp, + LOGFATAL, + "%s (%s) : Specified vegetation type (%d) is not implemented.", + MyFileName, + v->sppname, + v->vegType + ); } - if (v->estab_lyrs > min_transp_lyrs) { - LogError(logfp, LOGFATAL, "%s : Layers requested (estab_lyrs) > (# transpiration layers=%d).", MyFileName, min_transp_lyrs); + if (v->estab_lyrs > SW_Site.n_transp_lyrs[v->vegType]) { + LogError( + logfp, + LOGFATAL, + "%s (%s) : Layers requested (estab_lyrs = %d) > " + "(# transpiration layers = %d).", + MyFileName, + v->sppname, + v->estab_lyrs, + SW_Site.n_transp_lyrs[v->vegType] + ); } if (v->min_pregerm_days > v->max_pregerm_days) { - LogError(logfp, LOGFATAL, "%s : First day of germination > last day of germination.", MyFileName); + LogError( + logfp, + LOGFATAL, + "%s (%s) : First day of germination > last day of germination.", + MyFileName, + v->sppname + ); } if (v->min_wetdays_for_estab > v->max_days_germ2estab) { - LogError(logfp, LOGFATAL, "%s : Minimum wetdays after germination (%d) > maximum days allowed for establishment (%d).", MyFileName, v->min_wetdays_for_estab, - v->max_days_germ2estab); + LogError( + logfp, + LOGFATAL, + "%s (%s) : Minimum wetdays after germination (%d) > " + "maximum days allowed for establishment (%d).", + MyFileName, + v->sppname, + v->min_wetdays_for_estab, + v->max_days_germ2estab + ); } if (v->min_swc_germ < lyr[0]->swcBulk_wiltpt) { - LogError(logfp, LOGFATAL, "%s : Minimum swc for germination (%.4f) < wiltpoint (%.4f)", MyFileName, v->min_swc_germ, lyr[0]->swcBulk_wiltpt); + LogError( + logfp, + LOGFATAL, + "%s (%s) : Minimum swc for germination (%.4f) < wiltpoint (%.4f)", + MyFileName, + v->sppname, + v->min_swc_germ, + lyr[0]->swcBulk_wiltpt + ); } - if (v->min_swc_estab < lyr[0]->swcBulk_wiltpt) { - LogError(logfp, LOGFATAL, "%s : Minimum swc for establishment (%.4f) < wiltpoint (%.4f)", MyFileName, v->min_swc_estab, lyr[0]->swcBulk_wiltpt); + mean_wiltpt = 0.; + for (i = 0; i < v->estab_lyrs; i++) { + mean_wiltpt += lyr[i]->swcBulk_wiltpt; + } + mean_wiltpt /= v->estab_lyrs; + + if (LT(v->min_swc_estab, mean_wiltpt)) { + LogError( + logfp, + LOGFATAL, + "%s (%s) : Minimum swc for establishment (%.4f) < wiltpoint (%.4f)", + MyFileName, + v->sppname, + v->min_swc_estab, + mean_wiltpt + ); } } @@ -512,7 +624,8 @@ void _echo_VegEstab(void) { strcpy(outstr, errstr); for (i = 0; i < SW_VegEstab.count; i++) { - sprintf(errstr, "Species: %s\n----------------\n" + sprintf( + errstr, "Species: %s (vegetation type %s [%d])\n----------------\n" "Germination parameters:\n" "\tMinimum SWP (bars) : -%.4f\n" "\tMinimum SWC (cm/cm) : %.4f\n" @@ -522,9 +635,18 @@ void _echo_VegEstab(void) { "\tFirst possible day : %d\n" "\tLast possible day : %d\n" "\tMinimum consecutive wet days (after first possible day): %d\n", - v[i]->sppname, 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); + 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" diff --git a/SW_VegEstab.h b/SW_VegEstab.h index bc6e69fcf..6ba251bfd 100644 --- a/SW_VegEstab.h +++ b/SW_VegEstab.h @@ -45,6 +45,7 @@ typedef struct { /* THESE VARIABLES DO NOT CHANGE DURING THE NORMAL MODEL RUN */ char sppFileName[MAX_FILENAMESIZE]; /* Store the file Name and Path, Mostly for Rsoilwat */ char sppname[MAX_SPECIESNAMELEN + 1]; /* one set of parms per species */ + unsigned int vegType; /**< Vegetation type of species (see "Indices to vegetation types") */ TimeInt min_pregerm_days, /* first possible day of germination */ max_pregerm_days, /* last possible day of germination */ min_wetdays_for_germ, /* number of consecutive days top layer must be */ @@ -75,7 +76,7 @@ typedef struct { typedef struct { TimeInt *days; /* only output the day of estab for each species in the input */ - /* this array is allocated via SW_VES_Init() */ + /* this array is allocated via `SW_VegEstab_construct()` */ /* each day in the array corresponds to the ordered species list */ } SW_VEGESTAB_OUTPUTS; @@ -100,9 +101,10 @@ extern SW_VEGESTAB SW_VegEstab; /* Global Function Declarations */ /* --------------------------------------------------- */ void SW_VES_read(void); +void SW_VES_read2(Bool use_VegEstab, Bool consider_InputFlag); void SW_VES_construct(void); void SW_VES_deconstruct(void); -void SW_VES_init(void); +void SW_VES_init_run(void); void SW_VegEstab_construct(void); void SW_VES_checkestab(void); void SW_VES_new_year(void); diff --git a/test/test_SW_VegEstab.cc b/test/test_SW_VegEstab.cc new file mode 100644 index 000000000..43b405cd2 --- /dev/null +++ b/test/test_SW_VegEstab.cc @@ -0,0 +1,36 @@ +#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 "sw_testhelpers.h" // for `Reset_SOILWAT2_after_UnitTest()` + + + +namespace { + // Run a simulation with vegetation establishment turn on + TEST(VegEstabTest, SimulateWithVegEstab) { + // Turn on vegetation establishment and process inputs (but ignore use flag) + SW_VES_read2(swTRUE, swFALSE); + + // Expect that vegetation establishment is turn on and contains species + EXPECT_TRUE(SW_VegEstab.use); + EXPECT_GT(SW_VegEstab.count, 0); + + // Run the simulation + SW_CTL_main(); + + // Expect valid 'day of year' 1-366 output for each species from the + // vegetation establishment calculations + // note: estab_doy == 0 means no establishment + for (unsigned int i = 0; i < SW_VegEstab.count; i++) { + EXPECT_GE(SW_VegEstab.parms[i]->estab_doy, 0); + EXPECT_LE(SW_VegEstab.parms[i]->estab_doy, 366); + } + + // Reset to previous global state + Reset_SOILWAT2_after_UnitTest(); + } + +} // namespace diff --git a/testing/Input/estab.in b/testing/Input/estab.in index d702c509e..644438fef 100644 --- a/testing/Input/estab.in +++ b/testing/Input/estab.in @@ -11,6 +11,7 @@ # soil moisture and timing parameters required for the # species to establish in a given year. # There is no limit to the number of files in the list. +# File names with paths relative to the SOILWAT2 working directory (-d flag) -Input/bouteloua.estab -Input/bromus.estab +Input/estab/bouteloua.estab +Input/estab/bromus.estab diff --git a/testing/Input/bouteloua.estab b/testing/Input/estab/bouteloua.estab similarity index 51% rename from testing/Input/bouteloua.estab rename to testing/Input/estab/bouteloua.estab index 90d6eacdf..43662ab26 100644 --- a/testing/Input/bouteloua.estab +++ b/testing/Input/estab/bouteloua.estab @@ -1,18 +1,19 @@ -bogr # 4-char name of species +bogr # 4-char name of species +3 # Vegetation type of species (0, trees; 1, shrubs; 2, forbs; 3 grasses) # soil layer parameters 2 # number of layers affecting establishment 10. # SWP (bars) requirement for germination (top layer) 15. # SWP (bars) requirement for establishment (average of top layers) # timing parameters in days 60 # first possible day of germination -180 # last possible day of germination +180 # last possible day of germination 2 # min number of consecutive "wet" days for germination to occur -40 # max number of consecutive "dry" days after germination allowing estab +40 # max number of consecutive "dry" days after germination allowing estab 5 # min number of consecutive "wet" days after germination before establishment -15 # min number of days between germination and establishment -75 # max number of days between germination and establishment +15 # min number of days between germination and establishment +75 # max number of days between germination and establishment # temperature parameters in C -5. # min temp threshold for germination -20. # max temp threshold for germination -0. # min temp threshold for establishment -20. # max temp threshold for establishment +5. # min temp threshold for germination +20. # max temp threshold for germination +0. # min temp threshold for establishment +20. # max temp threshold for establishment diff --git a/testing/Input/bromus.estab b/testing/Input/estab/bromus.estab similarity index 89% rename from testing/Input/bromus.estab rename to testing/Input/estab/bromus.estab index 3ae2beebf..5fe061039 100644 --- a/testing/Input/bromus.estab +++ b/testing/Input/estab/bromus.estab @@ -1,4 +1,5 @@ brte # 4-char name of species +3 # Vegetation type of species (0, trees; 1, shrubs; 2, forbs; 3 grasses) # soil layer parameters 3 # number of layers affecting establishment - 45 cm Harris and Hulbert 10. # SWP (bars) requirement for germination (top layer) - Harris @@ -7,7 +8,7 @@ brte # 4-char name of species 200 # first possible day of germination - Hulbert and Harris 365 # last possible day of germination - Hulbert 6 # min number of consecutive "wet" days for germination to occur - Hurlbert and Harris -45 # max number of consecutive "dry" days after germination allowing estab - Harris (longtime) +45 # max number of consecutive "dry" days after germination allowing estab - Harris (longtime) 6 # min number of consecutive "wet" days after germination before establishment - Harris 15 # min number of days between germination and establishment - Hulbert 90 # max number of days between germination and establishment - Harris and Hulbert @@ -15,4 +16,4 @@ brte # 4-char name of species 10. # min temp threshold for germination - Hulbert and Harris 30. # max temp threshold for germination - Hulbert and Harris 3. # min temp threshold for establishment - Harris -30. # max temp threshold for establishment - Harris and Hulbert +30. # max temp threshold for establishment - Harris and Hulbert