From ede207aa7a5b1107c5d9a616279e5e4efa5f5dd8 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Mon, 1 Nov 2021 17:14:05 -0400 Subject: [PATCH 001/326] Implement generalized functionality for SWRCs - previously, SOILWAT2 used * Campbell 1974 as soil water retention curve SWRC (to convert between SWC and SWP) * Cosby et al. 1984 as pedotransfer function PDF (to estimate Campbell's SWRC parameters) --> this commit encapsulates SWRCs and PDFs in generalized functions so that (in the future) additional SWRCs and PDFs could be implemented and selected at run-time - struct `SW_LAYER_INFO`: * the array "swrcp[]" (replacing "thetaMatric", "psisMatric", "bMatric", and "binverseMatric") to hold parameters of any type of SWRC * "swrc_type" indicates which SWRC is selected (per soil layer) * "pdf_type" indicates which PDF would be used to estimated parameters at runtime if "swrcp_from_pdf" is TRUE - generalized PDF function `SWRC_PDF_estimate_parameters()`, replacing `water_eqn`(), and Cosby-specific version `SWRC_PDF_Cosby1984_for_Campbell1974()` - generalized function to check parameters `SWRC_check_parameters()` and Cosby-specific version `SWRC_check_parameters_for_Campbell1974()` - generalized SWC->SWP conversion function `SW_SWRC_SWCtoSWP()` and `SWRC_SWCtoSWP()`, replacing `SW_SWCbulk2SWPmatric()`, and Campbell-specific version `SWRC_SWCtoSWP_Campbell1974()` - generalized SWP->SWC conversion function `SW_SWRC_SWPtoSWC()` and `SWRC_SWPtoSWC()`, replacing `SW_SWPmatric2VWCBulk()`, and Campbell-specific version `SWRC_SWPtoSWC_Campbell1974()` - the new functions are written such that rSOILWAT2 will be able to utilize them in R code - updated documentation and unit tests - this commit reproduced output from v6.2.1: `make bin bint_run` and `diff testing/Output/ testing/Output_ref/ -qs` --- SW_Defines.h | 12 +- SW_Flow_lib.c | 14 +- SW_Output_get_functions.c | 10 +- SW_Site.c | 354 +++++++++++++++++++++++++++----------- SW_Site.h | 36 +++- SW_SoilWater.c | 321 +++++++++++++++++++++++++--------- SW_SoilWater.h | 32 +++- SW_VegEstab.c | 7 +- doc/SOILWAT2.bib | 14 +- test/test_SW_Site.cc | 106 ++++++++---- test/test_SW_SoilWater.cc | 298 +++++++++++++++++++------------- 11 files changed, 847 insertions(+), 357 deletions(-) diff --git a/SW_Defines.h b/SW_Defines.h index 586ef1c57..c7eb6e7c8 100644 --- a/SW_Defines.h +++ b/SW_Defines.h @@ -51,13 +51,13 @@ 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 */ /* M_PI and M_PI_2 from if implementation conforms to POSIX extension @@ -80,7 +80,7 @@ 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 BARCONV 1024. /**< conversion factor from bars to cm water, i.e. 1 bar = 1024 cm water */ #define SEC_PER_DAY 86400. // the # of seconds in a day... (24 hrs * 60 mins/hr * 60 sec/min = 86400 seconds) @@ -104,7 +104,7 @@ 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 diff --git a/SW_Flow_lib.c b/SW_Flow_lib.c index b1bf532e6..57d41bead 100644 --- a/SW_Flow_lib.c +++ b/SW_Flow_lib.c @@ -374,7 +374,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]; } } @@ -486,10 +486,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 @@ -546,7 +546,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; @@ -751,7 +751,7 @@ void remove_from_soil(double swc[], double qty[], double *aet, unsigned int nlyr ST_RGR_VALUES *st = &stValues; for (i = 0; i < nlyrs; i++) { - swpfrac[i] = coeff[i] / SW_SWCbulk2SWPmatric(SW_Site.lyr[i]->fractionVolBulk_gravel, swc[i], i); + swpfrac[i] = coeff[i] / SW_SWRC_SWCtoSWP(swc[i], SW_Site.lyr[i]); sumswp += swpfrac[i]; } @@ -908,9 +908,9 @@ void hydraulic_redistribution(double swc[], double swcwp[], double lyrRootCo[], ST_RGR_VALUES *st = &stValues; for (i = 0; i < nlyrs; i++) { - swp[i] = SW_SWCbulk2SWPmatric(SW_Site.lyr[i]->fractionVolBulk_gravel, swc[i], i); + swp[i] = SW_SWRC_SWCtoSWP(swc[i], SW_Site.lyr[i]); relCondroot[i] = fmin( 1., fmax(0., 1./(1. + powe(swp[i]/swp50, shapeCond) ) ) ); - swpwp[i] = SW_SWCbulk2SWPmatric(SW_Site.lyr[i]->fractionVolBulk_gravel, swcwp[i], i); + swpwp[i] = SW_SWRC_SWCtoSWP(swcwp[i], SW_Site.lyr[i]); hydredmat[0][i] = hydredmat[i][0] = 0.; /* no hydred in top layer */ } diff --git a/SW_Output_get_functions.c b/SW_Output_get_functions.c index e073c0792..df1d440aa 100644 --- a/SW_Output_get_functions.c +++ b/SW_Output_get_functions.c @@ -1024,8 +1024,8 @@ 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); strcat(sw_outstr, str); @@ -1051,8 +1051,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]); } } @@ -1076,8 +1075,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); } diff --git a/SW_Site.c b/SW_Site.c index 9cad42714..d080467ce 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -107,85 +107,201 @@ static void _read_layers(void); /** - \brief Calculate soil moisture characteristics for each layer. + @brief Estimate parameters of selected soil water retention curve (SWRC) + using selected pedotransfer function (PDF) + + Implemented SWRCs (`swrc_type`): + 1. Campbell 1974 \cite Campbell1974 + + Implemented PDFs (`pdf_type`): + 1. Cosby et al. 1984 \cite Cosby1984 PDF estimates parameters of + Campbell 1974 \cite Campbell1974 SWRC + see `SWRC_PDF_Cosby1984_for_Campbell1974()` + + @param[in] swrc_type Identification number of selected SWRC + @param[in] pdf_type Identification number of selected PDF + @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] +*/ +void SWRC_PDF_estimate_parameters( + unsigned int swrc_type, unsigned int pdf_type, + double *swrcp, + double sand, double clay, double gravel +) { + + if (swrc_type == 1 && pdf_type == 1) { + SWRC_PDF_Cosby1984_for_Campbell1974(swrcp, sand, clay); + + } else { + LogError( + logfp, + LOGFATAL, + "PDF type %d for SWRC type %d is not implemented.", + pdf_type, swrc_type + ); + + + /**********************************/ + /* TODO: remove once other PDFs are implemented that utilize gravel */ + /* avoiding `error: unused parameter 'gravel' [-Werror=unused-parameter]` */ + if (gravel < 0.) {}; + /**********************************/ + } +} - Bulk refers to the whole soil, i.e., including the rock/gravel component, - whereas matric refers to the < 2 mm fraction. - 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 +/** + @brief Estimate Campbell's 1974 SWRC parameters \cite Campbell1974 + using Cosby et al. 1984 PDF \cite Cosby1984 + + Estimation of three (+1) SWRC parameter values `swrcp` + based on sand, clay, and (silt): + - `swrcp[0]` (previously named `thetasMatric`): + saturated volumetric water content for the matric component [cm/cm] + - `swrcp[1]` (`psisMatric`): saturated soil water matric potential [-bar] + - `swrcp[2]` (`bMatric`): slope of the linear log-log retention curve [-] + - `swrcp[3]` (`binverseMatric`): the inverse of `swrcp[2]` + (not a parameter per se but pre-calculated for convenience) + + See `SWRC_SWCtoSWP_Campbell1974()` and `SWRC_SWPtoSWC_Campbell1974()` + for implementation of Campbell's 1974 SWRC. + + @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_PDF_Cosby1984_for_Campbell1974( + double *swrcp, + double sand, double clay +) { + /* Table 4 */ + swrcp[0] = -14.2 * sand - 3.7 * clay + 50.5; + swrcp[1] = powe(10.0, -1.58 * sand - 0.63 * clay + 2.17); + swrcp[2] = -0.3 * sand + 15.7 * clay + 3.10; - Return from the function is void. Calculated values stored in SW_Site object. + swrcp[3] = ZRO(swrcp[2]) ? SW_MISSING : 1.0 / swrcp[2]; +} - 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. - sand + clay + silt must equal one. Fraction silt is calculated: 1 - (sand + clay). - \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. - \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). +/** + @brief Check Soil Water Retention Curve (SWRC) parameters + Implemented SWRCs: + 1. Campbell 1974 \cite Campbell1974 + + @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 1: + res = SWRC_check_parameters_for_Campbell1974(swrcp); + break; -void water_eqn(RealD fractionGravel, RealD sand, RealD clay, LyrIndex n) { + default: + LogError( + logfp, + LOGFATAL, + "Parameter check for SWRC type %d is not implemented.", + swrc_type + ); + break; + } - /* 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. */ + return res; +} - /* 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(SW_Site.lyr[n]->thetasMatric, 0.0) || - GT(SW_Site.lyr[n]->thetasMatric, 100.0) - ) { +/** + @brief Check Campbell's 1974 SWRC parameters \cite Campbell1974 + + See `SWRC_SWCtoSWP_Campbell1974()` and `SWRC_SWPtoSWC_Campbell1974()` + for implementation of Campbell's 1974 SWRC. + + See `SWRC_PDF_Cosby1984_for_Campbell1974()` to estimate parameters + using Cosby et al. 1984 pedotransfer functions. + + Campbell's 1974 SWRC uses three parameters: + - `swrcp[0]` (previously named `thetasMatric`): + saturated volumetric water content for the matric component [cm/cm] + - `swrcp[1]` (`psisMatric`): saturated soil water matric potential [-bar] + - `swrcp[2]` (`bMatric`): slope of the linear log-log retention curve [-] + + @param[in] *swrcp Vector of SWRC parameters + + @return A logical value indicating if parameters passed the checks. +*/ +Bool SWRC_check_parameters_for_Campbell1974(double *swrcp) { + Bool res = swTRUE; + + if (LE(swrcp[0], 0.0) || GT(swrcp[0], 100.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_Campbell1974(): invalid value of " + "theta(saturated, matric, [100 * cm/cm]) = %f (must within 0-100)\n", + swrcp[0] ); } - if (ZRO(SW_Site.lyr[n]->bMatric)) { + if (LE(swrcp[1], 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_Campbell1974(): invalid value of " + "psi(saturated, matric, [-bar]) = %f (must > 0)\n", + swrcp[1] ); } - SW_Site.lyr[n]->binverseMatric = 1.0 / SW_Site.lyr[n]->bMatric; + 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] + ); + } + + return res; +} +/** + @brief Saxton et al. 2006 PDFs \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[in] gravel Coarse fragments (> 2 mm; e.g., gravel) + of the whole soil [m3/m3] + @param[in] width Soil layer width [cm] + @param[out] *swc_sat Saturated water content [cm] to be estimated +*/ +void PDF_Saxton2006( + double *swc_sat, + double sand, double clay, double gravel, double width +) { /* 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. */ - RealD + double OM = 0., theta_S, theta_33, theta_33t, theta_S33, theta_S33t; @@ -199,7 +315,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 = @@ -224,19 +341,18 @@ void water_eqn(RealD fractionGravel, RealD sand, RealD clay, LyrIndex n) { LogError( logfp, LOGFATAL, - "water_eqn(): invalid value of " - "theta(saturated, [cm / cm]; Saxton et al. 2006) = %f " - "(must be within 0-1)\n", + "PDF_Saxton2006(): invalid value of " + "theta(saturated, [cm / cm]) = %f (must be within 0-1)\n", theta_S ); } - SW_Site.lyr[n]->swcBulk_saturated = - SW_Site.lyr[n]->width * (1. - fractionGravel) * theta_S; + *swc_sat = width * (1. - gravel) * theta_S; } + /** @brief Estimate soil density of the whole soil (bulk). @@ -344,6 +460,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; } @@ -854,6 +971,23 @@ void derive_soilRegions(int nRegions, RealD *regionLowerBounds){ } } + +/** + @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 @@ -983,28 +1117,51 @@ void SW_SIT_init_run(void) { lyr->fractionVolBulk_gravel ); - /* Calculate pedotransfer function parameters */ - water_eqn( - lyr->fractionVolBulk_gravel, - lyr->fractionWeightMatric_sand, - lyr->fractionWeightMatric_clay, - s - ); + +/**********************************/ +/* TODO: remove once new inputs are implemented */ +lyr->swrc_type = 1; +lyr->pdf_type = 1; +lyr->swrcp_from_pdf = swTRUE; +/**********************************/ + + if (lyr->swrcp_from_pdf) { + /* Use pedotransfer function PDF */ + /* estimate parameters of soil water retention curve (SWRC) for layer */ + SWRC_PDF_estimate_parameters( + lyr->swrc_type, + lyr->pdf_type, + lyr->swrcp, + lyr->fractionWeightMatric_sand, + lyr->fractionWeightMatric_clay, + lyr->fractionVolBulk_gravel + ); + } + + /* Check parameters of selected SWRC */ + if (!SWRC_check_parameters(lyr->swrc_type, lyr->swrcp)) { + LogError( + logfp, + LOGFATAL, + "Parameter checks for layer %d (SWRC type %d) failed.", + lyr->id, lyr->swrc_type + ); + } /* Calculate SWC at field capacity and at wilting point */ - lyr->swcBulk_fieldcap = lyr->width * SW_SWPmatric2VWCBulk( - lyr->fractionVolBulk_gravel, - 0.333, - s - ); + lyr->swcBulk_fieldcap = SW_SWRC_SWPtoSWC(0.333, lyr); + lyr->swcBulk_wiltpt = SW_SWRC_SWPtoSWC(15., lyr); - lyr->swcBulk_wiltpt = lyr->width * SW_SWPmatric2VWCBulk( + + /* Estimate additional properties */ + PDF_Saxton2006( + &(lyr->swcBulk_saturated), + lyr->fractionWeightMatric_sand, + lyr->fractionWeightMatric_clay, lyr->fractionVolBulk_gravel, - 15, - s + lyr->width ); - /* sum ev and tr coefficients for later */ evsum += lyr->evap_coeff; @@ -1013,8 +1170,10 @@ 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] = SW_SWPmatric2VWCBulk(lyr->fractionVolBulk_gravel, - SW_VegProd.veg[k].SWPcrit, s) * lyr->width; + lyr->swcBulk_atSWPcrit[k] = SW_SWRC_SWPtoSWC( + SW_VegProd.veg[k].SWPcrit, + lyr + ); /* Find which transpiration region the current soil layer * is in and check validity of result. Region bounds are @@ -1065,7 +1224,7 @@ void SW_SIT_init_run(void) { EQUATIONS FOR THE SOIL-WATER CHARACTERISTIC CURVE. Canadian Geotechnical Journal, 31, 521-532.) */ - swcmin_help2 = SW_SWPmatric2VWCBulk(lyr->fractionVolBulk_gravel, 30., s); + swcmin_help2 = SW_SWRC_SWPtoSWC(30., lyr) / lyr->width; // if `SW_VWCBulkRes()` returns SW_MISSING then use `swcmin_help2` if (missing(swcmin_help1)){ @@ -1077,11 +1236,7 @@ void SW_SIT_init_run(void) { } 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 - ); + lyr->swcBulk_min = SW_SWRC_SWPtoSWC(_SWCMinVal, lyr) / lyr->width; } else { /* input: fixed VWC value as minimum SWC; unit(_SWCMinVal) == cm/cm */ @@ -1097,27 +1252,27 @@ 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_wiltpt / 2, - SW_SWCbulk2SWPmatric( - lyr->fractionVolBulk_gravel, - lyr->swcBulk_wiltpt / 2, - s - ) + lyr->swcBulk_wiltpt / 2., + SW_SWRC_SWCtoSWP(lyr->swcBulk_wiltpt / 2., lyr) ); } #endif /* Calculate wet limit of SWC for what inputs defined as wet */ - lyr->swcBulk_wet = GE(_SWCWetVal, 1.0) ? SW_SWPmatric2VWCBulk(lyr->fractionVolBulk_gravel, _SWCWetVal, s) * lyr->width : _SWCWetVal * lyr->width; + lyr->swcBulk_wet = GE(_SWCWetVal, 1.0) ? + SW_SWRC_SWPtoSWC(_SWCWetVal, lyr) : + _SWCWetVal * lyr->width; /* Calculate initial SWC based on inputs */ - lyr->swcBulk_init = GE(_SWCInitVal, 1.0) ? SW_SWPmatric2VWCBulk(lyr->fractionVolBulk_gravel, _SWCInitVal, s) * lyr->width : _SWCInitVal * lyr->width; + lyr->swcBulk_init = GE(_SWCInitVal, 1.0) ? + SW_SWRC_SWPtoSWC(_SWCInitVal, lyr) : + _SWCInitVal * lyr->width; /* test validity of values */ if (LT(lyr->swcBulk_init, lyr->swcBulk_min)) @@ -1362,16 +1517,21 @@ 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]) + ); } diff --git a/SW_Site.h b/SW_Site.h index 0fa28a195..9bad06ff6 100644 --- a/SW_Site.h +++ b/SW_Site.h @@ -53,6 +53,7 @@ extern "C" { #endif +#define SWRC_PARAM_NMAX 6 /**< Maximal number of SWRC parameters implemented */ typedef unsigned int LyrIndex; @@ -60,6 +61,8 @@ 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) */ @@ -82,13 +85,15 @@ typedef struct { swcBulk_atSWPcrit[NVEGTYPES], /* SWC corresponding to critical SWP for transpiration */ /* Saxton et al. 2006 */ - swcBulk_saturated, /* saturated bulk SWC [cm] */ + swcBulk_saturated; /* saturated bulk SWC [cm] */ + - /* 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 */ + /* Soil water retention curve (SWRC) */ + unsigned int + swrc_type, /**< Type of SWRC: 1 = Campbell 1974 */ + pdf_type; /**< Type of PDF: 1 = Cosby et al. 1984 for Campbell 1974 */ + Bool swrcp_from_pdf; /**< Estimate SWRC parameters with a PDF from soils if TRUE; if FALSE, use values provided as inputs */ + RealD swrcp[SWRC_PARAM_NMAX]; /**< Parameters of SWRC: parameter interpretation specific to selected SWRC */ LyrIndex my_transp_rgn[NVEGTYPES]; /* which transp zones from Site am I in? */ } SW_LAYER_INFO; @@ -145,7 +150,24 @@ typedef struct { } SW_SITE; -void water_eqn(RealD fractionGravel, RealD sand, RealD clay, LyrIndex n); +void SWRC_PDF_estimate_parameters( + unsigned int swrc_type, unsigned int pdf_type, + double *swrcp, + double sand, double clay, double gravel +); +void SWRC_PDF_Cosby1984_for_Campbell1974( + double *swrcp, + double sand, double clay +); + +Bool SWRC_check_parameters(unsigned int swrc_type, double *swrcp); +Bool SWRC_check_parameters_for_Campbell1974(double *swrcp); + +void PDF_Saxton2006( + double *swc_sat, + double sand, double clay, double gravel, double width +); + RealD calculate_soilBulkDensity(RealD matricDensity, RealD fractionGravel); LyrIndex nlayers_bsevap(void); void nlayers_vegroots(LyrIndex n_transp_lyrs[]); diff --git a/SW_SoilWater.c b/SW_SoilWater.c index aa1169f03..5952862c5 100644 --- a/SW_SoilWater.c +++ b/SW_SoilWater.c @@ -998,120 +998,285 @@ RealD SW_SnowDepth(RealD SWE, RealD snowdensity) { } } + /** - @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. + SOILWAT2 convenience wrapper for `SWRC_SWCtoSWP()`. - 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 + Implemented SWRCs (`swrc_type`): + 1. Campbell 1974 \cite Campbell1974, see `SWRC_SWCtoSWP_Campbell1974()` + + @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 + ); +} -RealD SW_SWCbulk2SWPmatric(RealD fractionGravel, RealD swcBulk, LyrIndex n) { -/********************************************************************** +/** + @brief Convert soil water content to soil water potential using + specified soil water retention curve (SWRC) -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). + Implemented SWRCs (`swrc_type`): + 1. Campbell 1974 \cite Campbell1974, see `SWRC_SWCtoSWP_Campbell1974()` - 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 + @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] - 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 + @return Soil water potential [-bar] +**/ +double SWRC_SWCtoSWP( + double swcBulk, + unsigned int swrc_type, + double *swrcp, + double gravel, + double width +) { + double res = SW_MISSING; + + switch (swrc_type) { + case 1: + res = SWRC_SWCtoSWP_Campbell1974(swcBulk, swrcp, gravel, width); + break; - LOCAL VARIABLES: - theta1 - volumetric soil water content + default: + LogError( + logfp, + LOGFATAL, + "SWRC type %d is not implemented.", + swrc_type + ); + break; + } - DEFINED CONSTANTS: - barconv - conversion factor from bars to cm water. (i.e. - 1 bar = 1024cm water) + return res; +} - 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; +/** + @brief Convert soil water content to soil water potential using + Campbell's 1974 \cite Campbell1974 Soil Water Retention Curve - if (missing(swcBulk) || ZRO(swcBulk)) + 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_for_Campbell1974()` + and, if necessary, estimated by `SWRC_PDF_Cosby1984_for_Campbell1974()` + from soil texture: + - `swrcp[0]` (previously named `thetasMatric`): + saturated volumetric water content + of the matric component [100 * cm/cm]; must be within 0-100 + - `swrcp[1]` (`psisMatric`): + saturated soil water matric potential [-bar]; must be > 0 + - `swrcp[2]` (`bMatric`): + slope of the linear log-log retention curve [-]; must be != 0 + - `swrcp[3]` (`binverseMatric`): the inverse of `swrcp[2]` + (not a parameter per se but pre-calculated for convenience) + + @note + This function was previously named `SW_SWCbulk2SWPmatric()`. + + @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] + + @return Soil water potential [-bar] +**/ +double SWRC_SWCtoSWP_Campbell1974( + double swcBulk, + double *swrcp, + double gravel, + double width +) { + + if (missing(swcBulk) || ZRO(swcBulk) || EQ(gravel, 1.)) { return 0.0; + } - if (GT(swcBulk, 0.0)) { - // we have soil moisture + if (LE(swcBulk, 0.0)) { + LogError( + logfp, + LOGFATAL, + "SWRC_SWCtoSWP_Campbell1974(): " + "invalid value of SWC = %.4f (must be >= 0)\n", + swcBulk + ); + } - // calculate matric VWC [cm / cm %] from bulk VWC - theta1 = (swcBulk / lyr->width) * 100. / (1. - fractionGravel); + // we have soil moisture + double theta1, theta2; - // calculate (VWC / VWC(saturated)) ^ b - theta2 = powe(theta1 / lyr->thetasMatric, lyr->bMatric); + // calculate matric VWC [cm / cm %] from SWC + theta1 = (swcBulk / width) * 100. / (1. - gravel); - 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); - } else { - swp = lyr->psisMatric / theta2 / BARCONV; - } + // calculate (VWC / VWC(saturated)) ^ b + theta2 = powe(theta1 / swrcp[0], swrcp[2]); - } 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); + if (!isfinite(theta2) || ZRO(theta2)) { + LogError( + logfp, + LOGFATAL, + "SWRC_SWCtoSWP_Campbell1974(): " + "invalid value of (theta / theta(saturated)) ^ b = %f (must be != 0)\n", + theta2 + ); } - return swp; + return swrcp[1] / theta2 / BARCONV; +} + + + +/** + @brief Convert soil water potential to soil water content using + specified soil water retention curve (SWRC) + + SOILWAT2 convenience wrapper for `SWRC_SWPtoSWC()`. + + Implemented SWRCs (`swrc_type`): + 1. Campbell 1974 \cite Campbell1974, see `SWRC_SWPtoSWC_Campbell1974()` + + @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 + ); } + /** -@brief Convert soil water potential to bulk volumetric water content. + @brief Convert soil water potential to soil water content using + specified soil water retention curve (SWRC) -@param fractionGravel Fraction of soil containing gravel, percentage. -@param swpMatric lyr->psisMatric calculated in water equation function -@param n Layer of soil. + Implemented SWRCs (`swrc_type`): + 1. Campbell 1974 \cite Campbell1974, see `SWRC_SWPtoSWC_Campbell1974()` -@return Volumentric water content (cm H2O/cm SOIL). + @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] + + @return Soil water content in the layer [cm] **/ +double SWRC_SWPtoSWC( + double swpMatric, + unsigned int swrc_type, + double *swrcp, + double gravel, + double width +) { + double res = SW_MISSING; + + switch (swrc_type) { + case 1: + res = SWRC_SWPtoSWC_Campbell1974(swpMatric, swrcp, gravel, width); + break; + default: + LogError( + logfp, + LOGFATAL, + "SWRC type %d is not implemented.", + swrc_type + ); + break; + } + return res; +} -RealD SW_SWPmatric2VWCBulk(RealD fractionGravel, RealD swpMatric, LyrIndex n) { /** - History: - 27-Aug-03 (cwb) moved from the Site module. + @brief Convert soil water potential to soil water content using + Campbell's 1974 \cite Campbell1974 Soil Water Retention Curve + + 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_for_Campbell1974()` + and, if necessary, estimated by `SWRC_PDF_Cosby1984_for_Campbell1974()` + from soil texture: + - `swrcp[0]` (previously named `thetasMatric`): + saturated volumetric water content + of the matric component [100 * cm/cm]; must be within 0-100 + - `swrcp[1]` (`psisMatric`): + saturated soil water matric potential [-bar]; must be > 0 + - `swrcp[2]` (`bMatric`): + slope of the linear log-log retention curve [-]; must be != 0 + - `swrcp[3]` (`binverseMatric`): the inverse of `swrcp[2]` + (not a parameter per se but pre-calculated for convenience) + + @note + This function was previously named `SW_SWPmatric2VWCBulk()`. + + @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 res = SW_MISSING; + + if (GT(swpMatric, 0.)) { + res = + 0.01 * swrcp[0] * powe(swrcp[1] / (swpMatric * BARCONV), swrcp[3]) * + (1. - gravel) * width; - 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); + } else { + LogError( + logfp, + LOGFATAL, + "SWRC_SWPtoSWC_Campbell1974(): " + "invalid value of SWP = %.4f (must be > 0)\n", + swpMatric + ); + } + + return res; } + + /** @brief Calculates 'Brooks-Corey' residual volumetric soil water. diff --git a/SW_SoilWater.h b/SW_SoilWater.h index 4beb8ba34..68f1a61f1 100644 --- a/SW_SoilWater.h +++ b/SW_SoilWater.h @@ -147,11 +147,39 @@ 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 +); +double SWRC_SWCtoSWP_Campbell1974( + double swcBulk, + double *swrcp, + double gravel, + double width +); + +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 +); +double SWRC_SWPtoSWC_Campbell1974( + double swpMatric, + double *swrcp, + double gravel, + double width +); + #ifdef SWDEBUG void SW_WaterBalance_Checks(void); #endif diff --git a/SW_VegEstab.c b/SW_VegEstab.c index 8d850f64b..0f4aa13af 100644 --- a/SW_VegEstab.c +++ b/SW_VegEstab.c @@ -425,14 +425,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); diff --git a/doc/SOILWAT2.bib b/doc/SOILWAT2.bib index 0d2709150..ba3a998d4 100644 --- a/doc/SOILWAT2.bib +++ b/doc/SOILWAT2.bib @@ -70,6 +70,7 @@ @Article{Cosby1984 journal = "Water Resources Research", volume = 20, pages = {682-690}, + doi = {10.1029/WR020i006p00682} } @Article{Eitzinger2000, @@ -198,7 +199,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} @@ -332,3 +332,15 @@ @article{huang2018JAMC publisher = {American Meteorological Society}, 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} +} + diff --git a/test/test_SW_Site.cc b/test/test_SW_Site.cc index acd33fe17..2c488d863 100644 --- a/test/test_SW_Site.cc +++ b/test/test_SW_Site.cc @@ -46,60 +46,102 @@ extern LyrIndex _TranspRgnBounds[]; 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 pedotransfer functions + TEST(SWSiteTest, PDFs) { + // inputs + RealD + swrcp[SWRC_PARAM_NMAX], + swc_sat, + sand = 0.33, + clay = 0.33, + gravel = 0.1, + width = 10.; + unsigned int swrc_type, pdf_type; + + + //--- Test Cosby et al. 1984 PDF for Campbell's 1974 SWRC + swrc_type = 1; + pdf_type = 1; + + SWRC_PDF_estimate_parameters( + swrc_type, pdf_type, + swrcp, + sand, clay, gravel + ); + + EXPECT_EQ( + SWRC_check_parameters(swrc_type, swrcp), + swTRUE + ); // Test thetasMatric - EXPECT_GT(SW_Site.lyr[n]->thetasMatric, 36.3); /* Value should always be greater + EXPECT_GT(swrcp[0], 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 + EXPECT_LT(swrcp[0], 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 + EXPECT_DOUBLE_EQ(swrcp[0], 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 + EXPECT_GT(swrcp[1], 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 + EXPECT_LT(swrcp[1], 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 + EXPECT_DOUBLE_EQ(swrcp[1], 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 + EXPECT_GT(swrcp[2], 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 + EXPECT_LT(swrcp[2], 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, + EXPECT_DOUBLE_EQ(swrcp[2], 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 `swcBulk_saturated` + PDF_Saxton2006(&swc_sat, sand, clay, gravel, width); + + // The swcBulk_saturated should be greater than 0 + EXPECT_GT(swc_sat, 0.); + // The swcBulk_saturated can't be greater than the width of the layer + EXPECT_LT(swc_sat, width); + + + + //--- Test bad parameters: Cosby et al. 1984 PDF for Campbell's 1974 SWRC + swrc_type = 1; + pdf_type = 1; + + sand = 10. + 1./3.; // unrealistic but forces `bmatric` to become 0 + + SWRC_PDF_estimate_parameters( + swrc_type, pdf_type, + swrcp, + sand, clay, gravel + ); + + EXPECT_EQ( + SWRC_check_parameters(swrc_type, swrcp), + swFALSE + ); } - // Test that water equation function 'water_eqn' fails - TEST(SWSiteTest, WaterEquationDeathTest) { - //declare inputs - RealD fractionGravel = 0.1; - LyrIndex n = 1; + // Test fatal failures of SWRC parameter checks + TEST(SWSiteTest, PDFsDeathTest) { - // 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; + // inputs + unsigned int swrc_type; + RealD swrcp[SWRC_PARAM_NMAX]; - EXPECT_DEATH_IF_SUPPORTED(water_eqn(fractionGravel, sand, clay, n), "@ generic.c LogError"); + //--- Test unimplemented SWRC + swrc_type = 255; + EXPECT_DEATH_IF_SUPPORTED( + SWRC_check_parameters(swrc_type, swrcp), + "@ generic.c LogError" + ); } diff --git a/test/test_SW_SoilWater.cc b/test/test_SW_SoilWater.cc index 34daf35c9..6fbba07cf 100644 --- a/test/test_SW_SoilWater.cc +++ b/test/test_SW_SoilWater.cc @@ -68,6 +68,7 @@ namespace{ Reset_SOILWAT2_after_UnitTest(); } + // Test the 'SW_SoilWater' function 'SW_SWC_adjust_snow' TEST(SWSoilWaterTest, SWSWCSdjustSnow){ // setup mock variables @@ -114,151 +115,212 @@ namespace{ EXPECT_EQ(snowmelt, 0); } - // Test the 'SW_SoilWater' function 'SW_SWCbulk2SWPmatric' - TEST(SWSoilWaterTest, SWSWCbulk2SWPmatric){ - // 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; + // Test the 'SW_SoilWater' function 'SWRC_SWCtoSWP' + TEST(SWSoilWaterTest, SWRC_SWCtoSWP) { + // set up mock variables + RealD + res, + swcBulk, swp, swc_fc, swc_wp, + swrcp[SWRC_PARAM_NMAX], + sand = 0.33, + clay = 0.33, + gravel = 0.2, + width = 10.; + + //--- Cosby et al. 1984 PDF for Campbell's 1974 SWRC + unsigned int swrc_type = 1, pdf_type = 1; + + SWRC_PDF_estimate_parameters( + swrc_type, + pdf_type, + swrcp, + sand, + clay, + gravel + ); // when swc is 0, we expect res == 0 - res = SW_SWCbulk2SWPmatric(fractionGravel, 0., n); + res = SWRC_SWCtoSWP(0., swrc_type, swrcp, gravel, width); EXPECT_EQ(res, 0.0); // when swc is SW_MISSING, we expect res == 0 - res = SW_SWCbulk2SWPmatric(fractionGravel, SW_MISSING, n); + res = SWRC_SWCtoSWP(SW_MISSING, swrc_type, swrcp, gravel, width); 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); + swp = 1. / 3.; + swc_fc = SWRC_SWPtoSWC(swp, swrc_type, swrcp, gravel, width); + res = SWRC_SWCtoSWP(swc_fc + 0.1, swrc_type, swrcp, gravel, width); + EXPECT_LT(res, swp); // 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); + res = SWRC_SWCtoSWP(swc_fc, swrc_type, swrcp, gravel, width); + EXPECT_NEAR(res, 1. / 3., tol9); // 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); + swc_wp = SWRC_SWPtoSWC(15., swrc_type, swrcp, gravel, width); + swcBulk = (swc_wp + swc_fc) / 2.; + res = SWRC_SWCtoSWP(swcBulk, swrc_type, swrcp, gravel, width); + EXPECT_GT(res, 1. / 3.); + EXPECT_LT(res, 15.); // 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); + res = SWRC_SWCtoSWP(swc_wp, swrc_type, swrcp, gravel, width); + EXPECT_NEAR(res, 15., tol9); // 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(); + res = SWRC_SWCtoSWP(swc_wp / 2., swrc_type, swrcp, gravel, width); + EXPECT_GT(res, 15.); + + // --- 3a) if theta1 == 0 (e.g., gravel == 1) + res = SWRC_SWCtoSWP(swc_wp, swrc_type, swrcp, 1., width); + EXPECT_DOUBLE_EQ(res, 0.); } - TEST(SWSoilWaterDeathTest, SWSWCbulk2SWPmatricDeathTest) { - 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(SWSoilWaterDeathTest, SWRC_SWCtoSWPDeathTest) { + // set up mock variables + RealD + swrcp[SWRC_PARAM_NMAX], + sand = 0.33, + clay = 0.33, + gravel = 0.1, + width = 10.; + + unsigned int swrc_type; + + + //--- we expect fatal errors in three situations + + //--- 1) Unimplemented SWRC + swrc_type = 255; + EXPECT_DEATH_IF_SUPPORTED( + SWRC_SWCtoSWP(0., swrc_type, swrcp, gravel, width), + "@ generic.c LogError" + ); + + + //--- Cosby et al. 1984 PDF for Campbell's 1974 SWRC + swrc_type = 1; + + SWRC_PDF_estimate_parameters( + swrc_type, + 1, + swrcp, + sand, + clay, + gravel + ); + + // --- 2) swc < 0: water content cannot be negative + EXPECT_DEATH_IF_SUPPORTED( + SWRC_SWCtoSWP(-1., swrc_type, swrcp, gravel, width), + "@ generic.c LogError" + ); + + + // --- 3) if theta_sat == 0 + // note: this case is normally prevented due to checks of inputs by + // function `SWRC_check_parameters_for_Campbell1974` and + // function `_read_layers` + swrcp[0] = 0.; + EXPECT_DEATH_IF_SUPPORTED( + SWRC_SWCtoSWP(5., swrc_type, swrcp, gravel, width), + "@ generic.c LogError" + ); } - // Test the 'SW_SoilWater' function 'SW_SWPmatric2VWCBulk' - TEST(SWSoilWaterTest, SWSWPmatric2VWCBulk){ - // 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); + // Test the 'SW_SoilWater' function 'SWRC_SWPtoSWC' + TEST(SWSoilWaterTest, SWRC_SWPtoSWC) { + // set up mock variables + RealD + res, + swpMatric = 15.0, + swrcp[SWRC_PARAM_NMAX], + sand = 0.33, + clay = 0.33, + gravel, + width = 10.; + short i; + + //--- Campbell's 1974 SWRC (using Cosby et al. 1984 PDF) + unsigned int swrc_type = 1, pdf_type = 1; + + // set gravel fractions on the interval [.0, 1], step .1 + for (i = 0; i <= 10; i++) { + gravel = i / 10.; + + SWRC_PDF_estimate_parameters( + swrc_type, + pdf_type, + swrcp, + sand, + clay, + gravel + ); + + res = SWRC_SWPtoSWC(swpMatric, swrc_type, swrcp, gravel, width); + + EXPECT_GE(res, 0.); + EXPECT_LE(res, width * (1. - gravel)); } - 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(); + EXPECT_EQ( + SWRC_SWPtoSWC(swpMatric, swrc_type, swrcp, 1., width), + 0. + ); + + // when width is 0, we expect t == 0 + EXPECT_EQ( + SWRC_SWPtoSWC(swpMatric, swrc_type, swrcp, 0., 0.), + 0. + ); + } + + + TEST(SWSoilWaterDeathTest, SWRC_SWPtoSWCDeathTest) { + // set up mock variables + RealD + swrcp[SWRC_PARAM_NMAX], + sand = 0.33, + clay = 0.33, + gravel = 0.1, + width = 10.; + + unsigned int swrc_type; + + + //--- we expect fatal errors in two situations + + //--- 1) Unimplemented SWRC + swrc_type = 255; + EXPECT_DEATH_IF_SUPPORTED( + SWRC_SWPtoSWC(0., swrc_type, swrcp, gravel, width), + "@ generic.c LogError" + ); + + + //--- Cosby et al. 1984 PDF for Campbell's 1974 SWRC + swrc_type = 1; + + SWRC_PDF_estimate_parameters( + swrc_type, + 1, + swrcp, + sand, + clay, + gravel + ); + + // --- 2) swp <= 0: water content cannot be negative + EXPECT_DEATH_IF_SUPPORTED( + SWRC_SWPtoSWC(-1., swrc_type, swrcp, gravel, width), + "@ generic.c LogError" + ); } } From 422a7098a2d27484e872556f412949ea82eca11e Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Mon, 10 Jan 2022 07:49:24 -0500 Subject: [PATCH 002/326] Improve documentation of `SWRC_PDF_Cosby1984_for_Campbell1974()` --- SW_Site.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/SW_Site.c b/SW_Site.c index 1453fab51..611810ccd 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -236,7 +236,7 @@ void SWRC_PDF_estimate_parameters( /** @brief Estimate Campbell's 1974 SWRC parameters \cite Campbell1974 - using Cosby et al. 1984 PDF \cite Cosby1984 + using Cosby et al. 1984 multivariate PDF \cite Cosby1984 Estimation of three (+1) SWRC parameter values `swrcp` based on sand, clay, and (silt): @@ -247,6 +247,10 @@ void SWRC_PDF_estimate_parameters( - `swrcp[3]` (`binverseMatric`): the inverse of `swrcp[2]` (not a parameter per se but pre-calculated for convenience) + Multivariate PDFs are from Cosby et al. 1984 Table 4; + Cosby et al. 1984 provided also univariate PDFs in Table 5 + but they are not used here. + See `SWRC_SWCtoSWP_Campbell1974()` and `SWRC_SWPtoSWC_Campbell1974()` for implementation of Campbell's 1974 SWRC. @@ -260,6 +264,8 @@ void SWRC_PDF_Cosby1984_for_Campbell1974( ) { /* Table 4 */ swrcp[0] = -14.2 * sand - 3.7 * clay + 50.5; + /* swrcp[1] = psisMatric: originally formulated as function of silt */ + /* here re-formulated as function of clay */ swrcp[1] = powe(10.0, -1.58 * sand - 0.63 * clay + 2.17); swrcp[2] = -0.3 * sand + 15.7 * clay + 3.10; From bacc4b7c1d266ad44dd4d339e886eb27dbc8d390 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 4 Feb 2022 10:52:54 -0500 Subject: [PATCH 003/326] Remove unused variables from `PDF_Saxton2006()` - they cropped in erroneously from recent merge from master branch - avoid compiler warning: `variable 'theta_1500' set but not used [-Wunused-but-set-variable]` --- SW_Site.c | 47 ++++++++++++++++++++++------------------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/SW_Site.c b/SW_Site.c index 57e506123..e8bcedcb5 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -390,19 +390,7 @@ void PDF_Saxton2006( double OM = 0., - theta_S, theta_33, theta_33t, theta_S33, theta_S33t, 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); + theta_S, theta_33, theta_33t, theta_S33, theta_S33t; /* Eq. 2: 33 kPa moisture */ theta_33t = @@ -449,19 +437,30 @@ void PDF_Saxton2006( *swc_sat = width * (1. - gravel) * theta_S; -// currently, not used: -/* - double R_w, alpha; -*/ +// 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 */ -/* SW_Site.lyr[n]->Saxton2006_lambda = (log(theta_33) - log(theta_1500)) / (log(1500.) - log(33.)); -*/ + /* Eq. 19: Gravel volume <-> weight fraction */ -/* alpha = SW_Site.lyr[n]->soilMatric_density / 2.65; if (GT(fractionGravel, 0.)) { @@ -469,17 +468,15 @@ void PDF_Saxton2006( } else { R_w = 0.; } -*/ + /* Eq. 16: saturated conductivity [cm / day] */ -/* SW_Site.lyr[n]->Saxton2006_K_sat_matric = 24. / 10. * 1930. \ * pow(theta_S - theta_33, 3. - SW_Site.lyr[n]->Saxton2006_lambda); -*/ + /* Eq. 22: saturated conductivity in bulk soils */ -/* SW_Site.lyr[n]->Saxton2006_K_sat_bulk = SW_Site.lyr[n]->Saxton2006_K_sat_matric; @@ -493,7 +490,7 @@ void PDF_Saxton2006( } else { SW_Site.lyr[n]->Saxton2006_fK_gravel = 1.; } -*/ +#endif } From b387a8758c10f0613f58ed5390bbfdf407134d0e Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Thu, 17 Feb 2022 09:47:23 -0500 Subject: [PATCH 004/326] Upgrade function that reads soil properties from disk - The function that was reading reading soil properties from the file "soils.in" on disk is renamed from `_read_layers()` to `SW_LYR_read()`. - The function is no longer static so that it can now be called from `SW_CTL_read_inputs_from_disk()` instead of being called by `SW_SIT_read()`. --- SW_Control.c | 9 ++- SW_Site.c | 167 +++++++++++++++++++++++++++------------------------ 2 files changed, 94 insertions(+), 82 deletions(-) diff --git a/SW_Control.c b/SW_Control.c index a3a77b344..844690eee 100644 --- a/SW_Control.c +++ b/SW_Control.c @@ -272,9 +272,14 @@ void SW_CTL_read_inputs_from_disk(void) { if (debug) swprintf(" > 'veg'"); #endif - SW_SIT_read(); // inputs also soil layer data + SW_SIT_read(); #ifdef SWDEBUG - if (debug) swprintf(" > 'site' + 'soils'"); + if (debug) swprintf(" > 'site'"); + #endif + + SW_LYR_read(); + #ifdef RSWDEBUG + if (debug) swprintf(" > 'soils'"); #endif SW_VES_read(); diff --git a/SW_Site.c b/SW_Site.c index e8bcedcb5..a1e48d5ee 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -102,85 +102,6 @@ static char *MyFileName; /* Local Function Definitions */ /* --------------------------------------------------- */ -static void _read_layers(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, matricd, 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, - &matricd, - &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]->soilMatric_density = matricd; - 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]->sTemp = 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 - ); - } - } - - CloseFile(&f); -} - /* =================================================== */ @@ -849,10 +770,96 @@ void SW_SIT_read(void) { } } +} + + + +/** 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, matricd, 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, + &matricd, + &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]->soilMatric_density = matricd; + 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]->sTemp = 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) From b3c6995af83296640d0e5dfdfc2e15c08c8eaaf2 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Tue, 22 Feb 2022 10:49:24 -0500 Subject: [PATCH 005/326] Export `styp2str` (for rSOILWAT2) --- SW_Output.h | 1 + 1 file changed, 1 insertion(+) diff --git a/SW_Output.h b/SW_Output.h index b48ef924d..e2cec800d 100644 --- a/SW_Output.h +++ b/SW_Output.h @@ -233,6 +233,7 @@ extern IntUS ncol_OUT[SW_OUTNKEYS]; extern char const *key2str[]; extern char const *pd2longstr[]; +extern char const *styp2str[]; From 70e029f73f3f21a881b2518c018950fce8df6f4d Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Tue, 22 Feb 2022 11:45:27 -0500 Subject: [PATCH 006/326] Move general sanity checks from specific to general SWRCs - previously, checks that we had positive moisture etc. were contained in the Campbell1974-specific functions - however, these checks will need to pass for any SWRC --- SW_SoilWater.c | 60 +++++++++++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/SW_SoilWater.c b/SW_SoilWater.c index aed45fbaf..33744c5dc 100644 --- a/SW_SoilWater.c +++ b/SW_SoilWater.c @@ -1062,6 +1062,19 @@ double SWRC_SWCtoSWP( double gravel, double width ) { + if (missing(swcBulk) || ZRO(swcBulk) || EQ(gravel, 1.)) { + return 0.0; + } + + if (LE(swcBulk, 0.0)) { + LogError( + logfp, + LOGFATAL, + "SWRC_SWCtoSWP(): invalid SWC = %.4f (must be >= 0)\n", + swcBulk + ); + } + double res = SW_MISSING; switch (swrc_type) { @@ -1121,22 +1134,7 @@ double SWRC_SWCtoSWP_Campbell1974( double gravel, double width ) { - - if (missing(swcBulk) || ZRO(swcBulk) || EQ(gravel, 1.)) { - return 0.0; - } - - if (LE(swcBulk, 0.0)) { - LogError( - logfp, - LOGFATAL, - "SWRC_SWCtoSWP_Campbell1974(): " - "invalid value of SWC = %.4f (must be >= 0)\n", - swcBulk - ); - } - - // we have soil moisture + // assume that we have soil moisture double theta1, theta2; // calculate matric VWC [cm / cm %] from SWC @@ -1210,6 +1208,15 @@ double SWRC_SWPtoSWC( double gravel, double width ) { + if (LE(swpMatric, 0.)) { + LogError( + logfp, + LOGFATAL, + "SWRC_SWPtoSWC(): invalid SWP = %.4f (must be > 0)\n", + swpMatric + ); + } + double res = SW_MISSING; switch (swrc_type) { @@ -1269,25 +1276,14 @@ double SWRC_SWPtoSWC_Campbell1974( double gravel, double width ) { - double res = SW_MISSING; + // assume that `swpMatric` > 0 + return + 0.01 * swrcp[0] * powe(swrcp[1] / (swpMatric * BARCONV), swrcp[3]) * + (1. - gravel) * width; +} - if (GT(swpMatric, 0.)) { - res = - 0.01 * swrcp[0] * powe(swrcp[1] / (swpMatric * BARCONV), swrcp[3]) * - (1. - gravel) * width; - } else { - LogError( - logfp, - LOGFATAL, - "SWRC_SWPtoSWC_Campbell1974(): " - "invalid value of SWP = %.4f (must be > 0)\n", - swpMatric - ); - } - return res; -} From f0aca31f3db369e437b0e9204de1bf424b2d615b Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 23 Feb 2022 18:37:46 -0500 Subject: [PATCH 007/326] Export `SW_LYR_read()` - fix commit b387a8758c10f0613f58ed5390bbfdf407134d0e ("Upgrade function that reads soil properties from disk") --- SW_Site.h | 1 + 1 file changed, 1 insertion(+) diff --git a/SW_Site.h b/SW_Site.h index 8603ada83..3cfac1bfe 100644 --- a/SW_Site.h +++ b/SW_Site.h @@ -201,6 +201,7 @@ void SW_SIT_init_run(void); void _echo_inputs(void); /* these used to be in Layers */ +void SW_LYR_read(void); void SW_SIT_clear_layers(void); LyrIndex _newlayer(void); void add_deepdrain_layer(void); From 80a154ebe48a785efb665dc2c1011c9ad2005738 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Thu, 24 Feb 2022 09:56:59 -0500 Subject: [PATCH 008/326] New `SW_check_soil_properties()` - move code chunk from `SW_SIT_init_run()` to own dedicated function - no change in behavior --- SW_Site.c | 189 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 106 insertions(+), 83 deletions(-) diff --git a/SW_Site.c b/SW_Site.c index a1e48d5ee..0654ed70a 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -102,6 +102,100 @@ static char *MyFileName; /* Local Function Definitions */ /* --------------------------------------------------- */ +/** Check validity of soil properties + + @param[in] *lyr Soil information. + + @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; + + + if (LE(lyr->width, 0.)) { + res = swFALSE; + fval = lyr->width; + errtype = Str_Dup("layer width"); + + } else if (LT(lyr->soilMatric_density, 0.)) { + res = swFALSE; + fval = lyr->soilMatric_density; + 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 + ); + } + + return res; +} + + /* =================================================== */ @@ -1070,12 +1164,9 @@ 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"; #ifdef SWDEBUG int debug = 0; @@ -1098,84 +1189,16 @@ void SW_SIT_init_run(void) { 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()` */ - 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"); - - } 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"); - - } 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"); - - } 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; - } - } - } - if (fail) { + /* Check soil properties for valid values */ + if (!SW_check_soil_properties(lyr)) { LogError( logfp, LOGFATAL, - "Invalid %s (%5.4f) in layer %d.\n", - errtype, fval, s + 1 + "Invalid soil properties in layer %d.\n", + lyr->id + 1 ); } @@ -1310,7 +1333,7 @@ lyr->swrcp_from_pdf = swTRUE; "%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 ); } @@ -1320,7 +1343,7 @@ lyr->swrcp_from_pdf = swTRUE; "%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 ); } @@ -1331,7 +1354,7 @@ lyr->swrcp_from_pdf = swTRUE; " 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_SWRC_SWCtoSWP(lyr->swcBulk_halfwiltpt, lyr), lyr->swcBulk_min, @@ -1347,7 +1370,7 @@ lyr->swrcp_from_pdf = swTRUE; "%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 ); } @@ -1399,7 +1422,7 @@ lyr->swrcp_from_pdf = swTRUE; " <= `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, @@ -1468,7 +1491,7 @@ lyr->swrcp_from_pdf = swTRUE; " 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 @@ -1497,7 +1520,7 @@ lyr->swrcp_from_pdf = swTRUE; { SW_Site.lyr[s]->evap_coeff /= evsum; LogError(logfp, LOGNOTE, " Layer %2d : %.4f", - s + 1, SW_Site.lyr[s]->evap_coeff); + lyr->id + 1, SW_Site.lyr[s]->evap_coeff); } LogError(logfp, LOGQUIET, ""); @@ -1517,7 +1540,7 @@ lyr->swrcp_from_pdf = swTRUE; { 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]); + lyr->id + 1, SW_Site.lyr[s]->transp_coeff[k]); } } From 4ce49e9a475acdece4aa7dffd23df8a1ea2aa033 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 25 Feb 2022 17:03:31 -0500 Subject: [PATCH 009/326] Update SWRC parameters for Campbell1974 - use units of [cm/cm] instead of [100 * cm/cm] for theta - switch order of psi_sat (now swrcp[0]) and theta_sat (now swrcp[1]) - clean up function documentation - add more detailed code documentation --- SW_Defines.h | 1 - SW_Site.c | 77 ++++++++++++++++++++-------------- SW_SoilWater.c | 88 ++++++++++++++++----------------------- test/test_SW_Site.cc | 22 +++++----- test/test_SW_SoilWater.cc | 4 +- 5 files changed, 94 insertions(+), 98 deletions(-) diff --git a/SW_Defines.h b/SW_Defines.h index c7eb6e7c8..0675080f9 100644 --- a/SW_Defines.h +++ b/SW_Defines.h @@ -80,7 +80,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. /**< conversion factor from bars to cm water, i.e. 1 bar = 1024 cm water */ #define SEC_PER_DAY 86400. // the # of seconds in a day... (24 hrs * 60 mins/hr * 60 sec/min = 86400 seconds) diff --git a/SW_Site.c b/SW_Site.c index 0654ed70a..079b9cc90 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -253,14 +253,10 @@ void SWRC_PDF_estimate_parameters( @brief Estimate Campbell's 1974 SWRC parameters \cite Campbell1974 using Cosby et al. 1984 multivariate PDF \cite Cosby1984 - Estimation of three (+1) SWRC parameter values `swrcp` - based on sand, clay, and (silt): - - `swrcp[0]` (previously named `thetasMatric`): - saturated volumetric water content for the matric component [cm/cm] - - `swrcp[1]` (`psisMatric`): saturated soil water matric potential [-bar] - - `swrcp[2]` (`bMatric`): slope of the linear log-log retention curve [-] - - `swrcp[3]` (`binverseMatric`): the inverse of `swrcp[2]` - (not a parameter per se but pre-calculated for convenience) + Estimation of three SWRC parameter values `swrcp` + based on sand, clay, and (silt). + + Parameters are explained in `SWRC_check_parameters_for_Campbell1974()`. Multivariate PDFs are from Cosby et al. 1984 Table 4; Cosby et al. 1984 provided also univariate PDFs in Table 5 @@ -278,13 +274,14 @@ void SWRC_PDF_Cosby1984_for_Campbell1974( double sand, double clay ) { /* Table 4 */ - swrcp[0] = -14.2 * sand - 3.7 * clay + 50.5; - /* swrcp[1] = psisMatric: originally formulated as function of silt */ - /* here re-formulated as function of clay */ - swrcp[1] = powe(10.0, -1.58 * sand - 0.63 * clay + 2.17); - swrcp[2] = -0.3 * sand + 15.7 * clay + 3.10; - swrcp[3] = ZRO(swrcp[2]) ? SW_MISSING : 1.0 / swrcp[2]; + /* 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] = -0.3 * sand + 15.7 * clay + 3.10; } @@ -293,8 +290,7 @@ void SWRC_PDF_Cosby1984_for_Campbell1974( /** @brief Check Soil Water Retention Curve (SWRC) parameters - Implemented SWRCs: - 1. Campbell 1974 \cite Campbell1974 + See `swrc2str` for implemented SWRCs. @param[in] swrc_type Identification number of selected SWRC @param[in] *swrcp Vector of SWRC parameters @@ -334,9 +330,9 @@ Bool SWRC_check_parameters(unsigned int swrc_type, double *swrcp) { using Cosby et al. 1984 pedotransfer functions. Campbell's 1974 SWRC uses three parameters: - - `swrcp[0]` (previously named `thetasMatric`): + - `swrcp[0]` (`psisMatric`): saturated soil water matric potential [-bar] + - `swrcp[1]` (previously named `thetasMatric`): saturated volumetric water content for the matric component [cm/cm] - - `swrcp[1]` (`psisMatric`): saturated soil water matric potential [-bar] - `swrcp[2]` (`bMatric`): slope of the linear log-log retention curve [-] @param[in] *swrcp Vector of SWRC parameters @@ -346,24 +342,24 @@ Bool SWRC_check_parameters(unsigned int swrc_type, double *swrcp) { Bool SWRC_check_parameters_for_Campbell1974(double *swrcp) { Bool res = swTRUE; - if (LE(swrcp[0], 0.0) || GT(swrcp[0], 100.0)) { + if (LE(swrcp[0], 0.0)) { res = swFALSE; LogError( logfp, LOGWARN, "SWRC_check_parameters_for_Campbell1974(): invalid value of " - "theta(saturated, matric, [100 * cm/cm]) = %f (must within 0-100)\n", - swrcp[0] + "psi(saturated, matric, [-bar]) = %f (must > 0)\n", + swrcp[1] ); } - if (LE(swrcp[1], 0.0)) { + if (LE(swrcp[1], 0.0) || GT(swrcp[1], 1.0)) { res = swFALSE; LogError( logfp, LOGWARN, "SWRC_check_parameters_for_Campbell1974(): invalid value of " - "psi(saturated, matric, [-bar]) = %f (must > 0)\n", + "theta(saturated, matric, [cm/cm]) = %f (must within 0-1)\n", swrcp[1] ); } @@ -843,25 +839,44 @@ 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) + ); } } } diff --git a/SW_SoilWater.c b/SW_SoilWater.c index 33744c5dc..31d4de273 100644 --- a/SW_SoilWater.c +++ b/SW_SoilWater.c @@ -1019,9 +1019,6 @@ RealD SW_SnowDepth(RealD SWE, RealD snowdensity) { SOILWAT2 convenience wrapper for `SWRC_SWCtoSWP()`. - Implemented SWRCs (`swrc_type`): - 1. Campbell 1974 \cite Campbell1974, see `SWRC_SWCtoSWP_Campbell1974()` - @param[in] swcBulk Soil water content in the layer [cm] @param[in] *lyr Soil information including SWRC type, SWRC parameters, @@ -1043,8 +1040,12 @@ RealD SW_SWRC_SWCtoSWP(RealD swcBulk, SW_LAYER_INFO *lyr) { @brief Convert soil water content to soil water potential using specified soil water retention curve (SWRC) - Implemented SWRCs (`swrc_type`): - 1. Campbell 1974 \cite Campbell1974, see `SWRC_SWCtoSWP_Campbell1974()` + + 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] swcBulk Soil water content in the layer [cm] @param[in] swrc_type Identification number of selected SWRC @@ -1100,22 +1101,7 @@ double SWRC_SWCtoSWP( @brief Convert soil water content to soil water potential using Campbell's 1974 \cite Campbell1974 Soil Water Retention Curve - 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_for_Campbell1974()` - and, if necessary, estimated by `SWRC_PDF_Cosby1984_for_Campbell1974()` - from soil texture: - - `swrcp[0]` (previously named `thetasMatric`): - saturated volumetric water content - of the matric component [100 * cm/cm]; must be within 0-100 - - `swrcp[1]` (`psisMatric`): - saturated soil water matric potential [-bar]; must be > 0 - - `swrcp[2]` (`bMatric`): - slope of the linear log-log retention curve [-]; must be != 0 - - `swrcp[3]` (`binverseMatric`): the inverse of `swrcp[2]` - (not a parameter per se but pre-calculated for convenience) + Parameters are explained in `SWRC_check_parameters_for_Campbell1974()`. @note This function was previously named `SW_SWCbulk2SWPmatric()`. @@ -1135,25 +1121,28 @@ double SWRC_SWCtoSWP_Campbell1974( double width ) { // assume that we have soil moisture - double theta1, theta2; + double theta, tmp, res; - // calculate matric VWC [cm / cm %] from SWC - theta1 = (swcBulk / width) * 100. / (1. - gravel); + // convert bulk SWC [cm] to theta = matric VWC [cm / cm] + theta = swcBulk / (width * (1. - gravel)); - // calculate (VWC / VWC(saturated)) ^ b - theta2 = powe(theta1 / swrcp[0], swrcp[2]); + // calculate (theta / theta_s) ^ b + tmp = powe(theta / swrcp[1], swrcp[2]); - if (!isfinite(theta2) || ZRO(theta2)) { + if (!isfinite(tmp) || ZRO(tmp)) { LogError( logfp, LOGFATAL, "SWRC_SWCtoSWP_Campbell1974(): " "invalid value of (theta / theta(saturated)) ^ b = %f (must be != 0)\n", - theta2 + tmp ); } - return swrcp[1] / theta2 / BARCONV; + res = swrcp[0] / tmp; + + // convert [cm of H20; SOILWAT2 legacy value] to [bar] + return res / 1024.; } @@ -1164,8 +1153,6 @@ double SWRC_SWCtoSWP_Campbell1974( SOILWAT2 convenience wrapper for `SWRC_SWPtoSWC()`. - Implemented SWRCs (`swrc_type`): - 1. Campbell 1974 \cite Campbell1974, see `SWRC_SWPtoSWC_Campbell1974()` @param[in] swpMatric Soil water potential [-bar] @param[in] *lyr Soil information including @@ -1189,8 +1176,12 @@ RealD SW_SWRC_SWPtoSWC(RealD swpMatric, SW_LAYER_INFO *lyr) { @brief Convert soil water potential to soil water content using specified soil water retention curve (SWRC) - Implemented SWRCs (`swrc_type`): - 1. Campbell 1974 \cite Campbell1974, see `SWRC_SWPtoSWC_Campbell1974()` + + 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 @@ -1242,22 +1233,7 @@ double SWRC_SWPtoSWC( @brief Convert soil water potential to soil water content using Campbell's 1974 \cite Campbell1974 Soil Water Retention Curve - 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_for_Campbell1974()` - and, if necessary, estimated by `SWRC_PDF_Cosby1984_for_Campbell1974()` - from soil texture: - - `swrcp[0]` (previously named `thetasMatric`): - saturated volumetric water content - of the matric component [100 * cm/cm]; must be within 0-100 - - `swrcp[1]` (`psisMatric`): - saturated soil water matric potential [-bar]; must be > 0 - - `swrcp[2]` (`bMatric`): - slope of the linear log-log retention curve [-]; must be != 0 - - `swrcp[3]` (`binverseMatric`): the inverse of `swrcp[2]` - (not a parameter per se but pre-calculated for convenience) + Parameters are explained in `SWRC_check_parameters_for_Campbell1974()`. @note This function was previously named `SW_SWPmatric2VWCBulk()`. @@ -1276,10 +1252,16 @@ double SWRC_SWPtoSWC_Campbell1974( double gravel, double width ) { - // assume that `swpMatric` > 0 - return - 0.01 * swrcp[0] * powe(swrcp[1] / (swpMatric * BARCONV), swrcp[3]) * - (1. - gravel) * width; + double phi, res; + + // convert SWP [-bar] to phi [cm of H20; SOILWAT2 legacy value] + phi = swpMatric * 1024.; + + // calculate matric theta [cm / cm]; assuming `swpMatric` > 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; } diff --git a/test/test_SW_Site.cc b/test/test_SW_Site.cc index 84dbb1434..80b9a811d 100644 --- a/test/test_SW_Site.cc +++ b/test/test_SW_Site.cc @@ -68,22 +68,22 @@ namespace { swTRUE ); - // Test thetasMatric - EXPECT_GT(swrcp[0], 36.3); /* Value should always be greater - than 36.3 based upon complete consideration of potential range of sand and clay values */ - EXPECT_LT(swrcp[0], 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(swrcp[0], 44.593); /* If sand is .33 and - clay is .33, thetasMatric should be 44.593 */ - // Test psisMatric - EXPECT_GT(swrcp[1], 3.890451); /* Value should always be greater + EXPECT_GT(swrcp[0], 3.890451); /* Value should always be greater than 3.890451 based upon complete consideration of potential range of sand and clay values */ - EXPECT_LT(swrcp[1], 34.67369); /* Value should always be less + EXPECT_LT(swrcp[0], 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(swrcp[1], 27.586715750763947); /* If sand is + EXPECT_DOUBLE_EQ(swrcp[0], 27.586715750763947); /* If sand is .33 and clay is .33, psisMatric should be 27.5867 */ + // Test thetasMatric + EXPECT_GT(swrcp[1], 0.363); /* Value should always be greater + than 36.3 based upon complete consideration of potential range of sand and clay values */ + EXPECT_LT(swrcp[1], 0.468); /* Value should always be less + than 46.8 based upon complete consideration of potential range of sand and clay values */ + EXPECT_DOUBLE_EQ(swrcp[1], 0.44593); /* If sand is .33 and + clay is .33, thetasMatric should be 44.593 */ + // Test bMatric EXPECT_GT(swrcp[2], 2.8); /* Value should always be greater than 2.8 based upon complete consideration of potential range of sand and clay values */ diff --git a/test/test_SW_SoilWater.cc b/test/test_SW_SoilWater.cc index 003a8cad5..bdce5d7d3 100644 --- a/test/test_SW_SoilWater.cc +++ b/test/test_SW_SoilWater.cc @@ -195,7 +195,7 @@ namespace{ //--- 1) Unimplemented SWRC swrc_type = 255; EXPECT_DEATH_IF_SUPPORTED( - SWRC_SWCtoSWP(0., swrc_type, swrcp, gravel, width), + SWRC_SWCtoSWP(1., swrc_type, swrcp, gravel, width), "@ generic.c LogError" ); @@ -223,7 +223,7 @@ namespace{ // note: this case is normally prevented due to checks of inputs by // function `SWRC_check_parameters_for_Campbell1974` and // function `_read_layers` - swrcp[0] = 0.; + swrcp[1] = 0.; EXPECT_DEATH_IF_SUPPORTED( SWRC_SWCtoSWP(5., swrc_type, swrcp, gravel, width), "@ generic.c LogError" From 1fd9564fe3d5aa39457d6b931cb591ef6591f1a6 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 25 Feb 2022 17:52:30 -0500 Subject: [PATCH 010/326] Select SWRC/PDF by name instead of integer code - new arrays `swrc2str` and `pdf2str` translate (now) internal integer codes to character string representation - new `encode_str2swrc()` and `encode_str2pdf()` translate names to internal integer codes - struct `SW_SITE` gained "site-level" values for selected SWRC/PDF names (and their internal integer codes) - element `swrcp_from_pdf` is no longer needed (pdf_type > 0 does the same) - thus, interpretation of requested PDF value is now "independent" of "SWRC" --> `SWRC_PDF_estimate_parameters()` no longer requires argument "swrc_type" --- SW_Site.c | 148 +++++++++++++++++++++++++++++++------- SW_Site.h | 25 +++++-- SW_SoilWater.c | 11 +-- test/test_SW_Site.cc | 12 ++-- test/test_SW_SoilWater.cc | 26 +++---- 5 files changed, 169 insertions(+), 53 deletions(-) diff --git a/SW_Site.c b/SW_Site.c index 079b9cc90..90c9d5139 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -91,6 +91,31 @@ RealD _SWCMinVal; /* lower bound on swc. */ +/** Character representation of implemented Soil Water Retention Curves (SWRC) +*/ +/* MAINTENANCE: + - Values must exactly match those provided in `siteparam.in`. + - If entries are added/removed/change, then update `check_SWRC_vs_PDF()`, + `SWRC_check_parameters()`, `SWRC_SWCtoSWP()`, and `SWRC_SWPtoSWC()`. +*/ +char const *swrc2str[N_SWRCs] = { + "Campbell1974" +}; + +/** Character representation of implemented Pedotransfer Functions (PDF) +*/ +/* MAINTENANCE: + - Values must exactly match those provided in `siteparam.in`. + - The first value must be "NoPDF". + - If entries are added/removed/change, then update `check_SWRC_vs_PDF()` and + `SWRC_PDF_estimate_parameters()`. +*/ +char const *pdf2str[N_PDFs] = { + "NoPDF", + "Cosby1984AndOthers", + "Cosby1984" +}; + /* =================================================== */ /* Local Variables */ @@ -202,19 +227,71 @@ static Bool SW_check_soil_properties(SW_LAYER_INFO *lyr) { /* Global Function Definitions */ /* --------------------------------------------------- */ + +/** + @brief Translate a SWRC name into a SWRC type number + + @param[in] *swrc_name Name selected 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 PDF name into a PDF type number + + @param[in] *pdf_name Name selected PDF + + @return Internal identification number of selected PDF +*/ +unsigned int encode_str2pdf(char *pdf_name) { + unsigned int k; + + for ( + k = 0; + k < N_PDFs && Str_CompareI(pdf_name, (char *)pdf2str[k]); + k++ + ); + + if (k == N_PDFs) { + LogError( + logfp, + LOGFATAL, + "PDF '%s' is not implemented.", + pdf_name + ); + } + + return k; +} + + + /** @brief Estimate parameters of selected soil water retention curve (SWRC) using selected pedotransfer function (PDF) - Implemented SWRCs (`swrc_type`): - 1. Campbell 1974 \cite Campbell1974 - - Implemented PDFs (`pdf_type`): - 1. Cosby et al. 1984 \cite Cosby1984 PDF estimates parameters of - Campbell 1974 \cite Campbell1974 SWRC - see `SWRC_PDF_Cosby1984_for_Campbell1974()` + See `pdf2str` for implemented PDFs. - @param[in] swrc_type Identification number of selected SWRC @param[in] pdf_type Identification number of selected PDF @param[out] *swrcp Vector of SWRC parameters to be estimated @param[in] sand Sand content of the matric soil (< 2 mm fraction) [g/g] @@ -223,29 +300,47 @@ static Bool SW_check_soil_properties(SW_LAYER_INFO *lyr) { of the whole soil [m3/m3] */ void SWRC_PDF_estimate_parameters( - unsigned int swrc_type, unsigned int pdf_type, + unsigned int pdf_type, double *swrcp, double sand, double clay, double gravel ) { - if (swrc_type == 1 && pdf_type == 1) { - SWRC_PDF_Cosby1984_for_Campbell1974(swrcp, sand, clay); - - } else { + if (pdf_type == 0) { LogError( logfp, - LOGFATAL, - "PDF type %d for SWRC type %d is not implemented.", - pdf_type, swrc_type + LOGNOTE, + "`SWRC_PDF_estimate_parameters()` was called even though " + "`pdf_type` %d was requested " + "(which uses values from 'swrc_params.in' instead of estimation).", + pdf_type ); + } else { + + /* Initialize swrcp[] to 0 */ + memset(swrcp, 0., SWRC_PARAM_NMAX * sizeof(swrcp[0])); + + if (pdf_type == 1) { + SWRC_PDF_Cosby1984_for_Campbell1974(swrcp, sand, clay); + + } else if (pdf_type == 2) { + SWRC_PDF_Cosby1984_for_Campbell1974(swrcp, sand, clay); - /**********************************/ - /* TODO: remove once other PDFs are implemented that utilize gravel */ - /* avoiding `error: unused parameter 'gravel' [-Werror=unused-parameter]` */ - if (gravel < 0.) {}; - /**********************************/ + } else { + LogError( + logfp, + LOGFATAL, + "PDF (type %d) is not implemented in SOILWAT2.", + pdf_type + ); + } } + + /**********************************/ + /* TODO: remove once other PDFs are implemented that utilize gravel */ + /* avoiding `error: unused parameter 'gravel' [-Werror=unused-parameter]` */ + if (gravel < 0.) {}; + /**********************************/ } @@ -301,7 +396,7 @@ Bool SWRC_check_parameters(unsigned int swrc_type, double *swrcp) { Bool res = swFALSE; switch (swrc_type) { - case 1: + case 0: res = SWRC_check_parameters_for_Campbell1974(swrcp); break; @@ -309,7 +404,7 @@ Bool SWRC_check_parameters(unsigned int swrc_type, double *swrcp) { LogError( logfp, LOGFATAL, - "Parameter check for SWRC type %d is not implemented.", + "Selected SWRC (type %d) is not implemented.", swrc_type ); break; @@ -1229,14 +1324,12 @@ void SW_SIT_init_run(void) { /* TODO: remove once new inputs are implemented */ lyr->swrc_type = 1; lyr->pdf_type = 1; -lyr->swrcp_from_pdf = swTRUE; /**********************************/ if (lyr->swrcp_from_pdf) { /* Use pedotransfer function PDF */ /* estimate parameters of soil water retention curve (SWRC) for layer */ SWRC_PDF_estimate_parameters( - lyr->swrc_type, lyr->pdf_type, lyr->swrcp, lyr->fractionWeightMatric_sand, @@ -1250,8 +1343,9 @@ lyr->swrcp_from_pdf = swTRUE; LogError( logfp, LOGFATAL, - "Parameter checks for layer %d (SWRC type %d) failed.", - lyr->id, lyr->swrc_type + "Checks of parameters for SWRC '%s' in layer %d failed.", + swrc2str[lyr->swrc_type], + lyr->id + 1 ); } diff --git a/SW_Site.h b/SW_Site.h index 3cfac1bfe..f73900347 100644 --- a/SW_Site.h +++ b/SW_Site.h @@ -54,6 +54,9 @@ extern "C" { #endif #define SWRC_PARAM_NMAX 6 /**< Maximal number of SWRC parameters implemented */ +#define N_SWRCs 1 /**< Number of implemented SWRCs */ +#define N_PDFs 3 /**< Number of implemented PDFs */ + typedef unsigned int LyrIndex; @@ -96,9 +99,8 @@ typedef struct { /* Soil water retention curve (SWRC) */ unsigned int - swrc_type, /**< Type of SWRC: 1 = Campbell 1974 */ - pdf_type; /**< Type of PDF: 1 = Cosby et al. 1984 for Campbell 1974 */ - Bool swrcp_from_pdf; /**< Estimate SWRC parameters with a PDF from soils if TRUE; if FALSE, use values provided as inputs */ + swrc_type, /**< Type of SWRC (see `swrc2str[]`) */ + pdf_type; /**< Type of PDF (see `pdf2str[]`) */ RealD swrcp[SWRC_PARAM_NMAX]; /**< Parameters of SWRC: parameter interpretation specific to selected SWRC */ LyrIndex my_transp_rgn[NVEGTYPES]; /* which transp zones from Site am I in? */ @@ -154,6 +156,15 @@ 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_pdf_type; + + char + site_swrc_name[64], + site_pdf_name[64]; + } SW_SITE; @@ -165,13 +176,19 @@ extern SW_SITE SW_Site; extern LyrIndex _TranspRgnBounds[MAX_TRANSP_REGIONS]; extern RealD _SWCInitVal, _SWCWetVal, _SWCMinVal; +extern char const *swrc2str[]; +extern char const *pdf2str[]; + /* =================================================== */ /* Global Function Declarations */ /* --------------------------------------------------- */ +unsigned int encode_str2swrc(char *swrc_name); +unsigned int encode_str2pdf(char *pdf_name); + void SWRC_PDF_estimate_parameters( - unsigned int swrc_type, unsigned int pdf_type, + unsigned int pdf_type, double *swrcp, double sand, double clay, double gravel ); diff --git a/SW_SoilWater.c b/SW_SoilWater.c index 31d4de273..5dd2da286 100644 --- a/SW_SoilWater.c +++ b/SW_SoilWater.c @@ -1040,6 +1040,7 @@ RealD SW_SWRC_SWCtoSWP(RealD swcBulk, SW_LAYER_INFO *lyr) { @brief Convert soil water content to soil water potential using specified soil water retention curve (SWRC) + See `swrc2str()` for implemented SWRCs. The code assumes the following conditions: - checked by `SW_SIT_init_run()` @@ -1079,7 +1080,7 @@ double SWRC_SWCtoSWP( double res = SW_MISSING; switch (swrc_type) { - case 1: + case 0: res = SWRC_SWCtoSWP_Campbell1974(swcBulk, swrcp, gravel, width); break; @@ -1087,7 +1088,7 @@ double SWRC_SWCtoSWP( LogError( logfp, LOGFATAL, - "SWRC type %d is not implemented.", + "SWRC (type %d) is not implemented.", swrc_type ); break; @@ -1153,6 +1154,7 @@ double SWRC_SWCtoSWP_Campbell1974( SOILWAT2 convenience wrapper for `SWRC_SWPtoSWC()`. + See `swrc2str()` for implemented SWRCs. @param[in] swpMatric Soil water potential [-bar] @param[in] *lyr Soil information including @@ -1176,6 +1178,7 @@ RealD SW_SWRC_SWPtoSWC(RealD swpMatric, SW_LAYER_INFO *lyr) { @brief Convert soil water potential to soil water content using specified soil water retention curve (SWRC) + See `swrc2str()` for implemented SWRCs. The code assumes the following conditions: - checked by `SW_SIT_init_run()` @@ -1211,7 +1214,7 @@ double SWRC_SWPtoSWC( double res = SW_MISSING; switch (swrc_type) { - case 1: + case 0: res = SWRC_SWPtoSWC_Campbell1974(swpMatric, swrcp, gravel, width); break; @@ -1219,7 +1222,7 @@ double SWRC_SWPtoSWC( LogError( logfp, LOGFATAL, - "SWRC type %d is not implemented.", + "SWRC (type %d) is not implemented.", swrc_type ); break; diff --git a/test/test_SW_Site.cc b/test/test_SW_Site.cc index 80b9a811d..2c634a282 100644 --- a/test/test_SW_Site.cc +++ b/test/test_SW_Site.cc @@ -54,11 +54,11 @@ namespace { //--- Test Cosby et al. 1984 PDF for Campbell's 1974 SWRC - swrc_type = 1; - pdf_type = 1; + swrc_type = encode_str2swrc(Str_Dup("Campbell1974")); + pdf_type = encode_str2pdf(Str_Dup("Cosby1984AndOthers")); SWRC_PDF_estimate_parameters( - swrc_type, pdf_type, + pdf_type, swrcp, sand, clay, gravel ); @@ -104,13 +104,13 @@ namespace { //--- Test bad parameters: Cosby et al. 1984 PDF for Campbell's 1974 SWRC - swrc_type = 1; - pdf_type = 1; + swrc_type = encode_str2swrc(Str_Dup("Campbell1974")); + pdf_type = encode_str2pdf(Str_Dup("Cosby1984AndOthers")); sand = 10. + 1./3.; // unrealistic but forces `bmatric` to become 0 SWRC_PDF_estimate_parameters( - swrc_type, pdf_type, + pdf_type, swrcp, sand, clay, gravel ); diff --git a/test/test_SW_SoilWater.cc b/test/test_SW_SoilWater.cc index bdce5d7d3..2cb81a8f8 100644 --- a/test/test_SW_SoilWater.cc +++ b/test/test_SW_SoilWater.cc @@ -127,10 +127,11 @@ namespace{ width = 10.; //--- Cosby et al. 1984 PDF for Campbell's 1974 SWRC - unsigned int swrc_type = 1, pdf_type = 1; + unsigned int + swrc_type = encode_str2swrc(Str_Dup("Campbell1974")), + pdf_type = encode_str2pdf(Str_Dup("Cosby1984AndOthers")); SWRC_PDF_estimate_parameters( - swrc_type, pdf_type, swrcp, sand, @@ -187,7 +188,7 @@ namespace{ gravel = 0.1, width = 10.; - unsigned int swrc_type; + unsigned int swrc_type, pdf_type; //--- we expect fatal errors in three situations @@ -201,11 +202,11 @@ namespace{ //--- Cosby et al. 1984 PDF for Campbell's 1974 SWRC - swrc_type = 1; + swrc_type = encode_str2swrc(Str_Dup("Campbell1974")); + pdf_type = encode_str2pdf(Str_Dup("Cosby1984AndOthers")); SWRC_PDF_estimate_parameters( - swrc_type, - 1, + pdf_type, swrcp, sand, clay, @@ -245,14 +246,15 @@ namespace{ short i; //--- Campbell's 1974 SWRC (using Cosby et al. 1984 PDF) - unsigned int swrc_type = 1, pdf_type = 1; + unsigned int + swrc_type = encode_str2swrc(Str_Dup("Campbell1974")), + pdf_type = encode_str2pdf(Str_Dup("Cosby1984AndOthers")); // set gravel fractions on the interval [.0, 1], step .1 for (i = 0; i <= 10; i++) { gravel = i / 10.; SWRC_PDF_estimate_parameters( - swrc_type, pdf_type, swrcp, sand, @@ -290,7 +292,7 @@ namespace{ gravel = 0.1, width = 10.; - unsigned int swrc_type; + unsigned int swrc_type, pdf_type; //--- we expect fatal errors in two situations @@ -304,11 +306,11 @@ namespace{ //--- Cosby et al. 1984 PDF for Campbell's 1974 SWRC - swrc_type = 1; + swrc_type = encode_str2swrc(Str_Dup("Campbell1974")); + pdf_type = encode_str2pdf(Str_Dup("Cosby1984AndOthers")); SWRC_PDF_estimate_parameters( - swrc_type, - 1, + pdf_type, swrcp, sand, clay, From 5a2fcffa6377efa550ca014ca9b9a09a07fc39a2 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 25 Feb 2022 17:56:20 -0500 Subject: [PATCH 011/326] New `check_SWRC_vs_PDF()` to check compatibility between selected SWRC and PDF --- SW_Site.c | 46 +++++++++++++++++++++++++++++++++++++++++++++- SW_Site.h | 1 + 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/SW_Site.c b/SW_Site.c index 90c9d5139..77543c230 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -380,6 +380,34 @@ void SWRC_PDF_Cosby1984_for_Campbell1974( } +/** + @brief Check whether selected PDF and SWRC are compatible + + @param[in] *swrc_name Name selected SWRC + @param[in] *pdf_name Name selected PDF + + @return A logical value indicating if SWRC and PDF are compatible. +*/ +Bool check_SWRC_vs_PDF(char *swrc_name, char *pdf_name) { + Bool res = swFALSE; + + if (Str_CompareI(pdf_name, Str_Dup("NoPDF")) == 0) { + res = swTRUE; + } else { + + if ( + Str_CompareI(swrc_name, Str_Dup("Campbell1974")) == 0 && + ( + Str_CompareI(pdf_name, Str_Dup("Cosby1984AndOthers")) == 0 || + Str_CompareI(pdf_name, Str_Dup("Cosby1984")) == 0 + ) + ) { + res = swTRUE; + } + } + + return res; +} /** @@ -1293,13 +1321,29 @@ void SW_SIT_init_run(void) { add_deepdrain_layer(); + /* Check compatibility between selected SWRC and PDF */ + if (!check_SWRC_vs_PDF(sp->site_swrc_name, sp->site_pdf_name)) { + LogError( + logfp, + LOGFATAL, + "Selected PDF '%s' is incompatible with selected SWRC '%s'\n", + sp->site_pdf_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: + /* Copy site-level SWRC/PDF information to each layer: + We currently allow specifying one SWRC/PDF for a site for all layers; + remove in the future if we allow SWRC/PDF to vary by soil layer */ + lyr->swrc_type = sp->site_swrc_type; + lyr->pdf_type = sp->site_pdf_type; /* Check soil properties for valid values */ diff --git a/SW_Site.h b/SW_Site.h index f73900347..4418ee975 100644 --- a/SW_Site.h +++ b/SW_Site.h @@ -198,6 +198,7 @@ void SWRC_PDF_Cosby1984_for_Campbell1974( ); +Bool check_SWRC_vs_PDF(char *swrc_name, char *pdf_name); Bool SWRC_check_parameters(unsigned int swrc_type, double *swrcp); Bool SWRC_check_parameters_for_Campbell1974(double *swrcp); From 7e461bfb25eba177c4f5ee5b16031418b00d95dc Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 25 Feb 2022 17:58:26 -0500 Subject: [PATCH 012/326] Update `_echo_inputs()` for "SW_Site" - print selected SWRC/PDF and SWRC parameter values --- SW_Site.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/SW_Site.c b/SW_Site.c index 77543c230..c4498406e 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -1895,11 +1895,55 @@ void _echo_inputs(void) { 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, + " PDF type: %d (%s)\n", + s->site_pdf_type, + s->site_pdf_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 From 4eafd7eed064c45d47aacd3c329fec45976238f5 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 25 Feb 2022 18:06:32 -0500 Subject: [PATCH 013/326] New inputs to select SWRC/PDF and (optionally) SWRC parameter values - `siteparam.in` gains two new inputs: "swrc_name" and "pdf_name" - if "pdf_name" is "NoPDF", then a new file `swrc_params.in` must be provided with parameter values instead -> new `SW_SWRC_read()` to read the new file `swrc_params.in` -> `files.in` is updated to hold path of new file `swrc_params.in` -> `SW_SIT_read()` is updated to read in "swrc_name" and "pdf_name" (they are stored into "site_*" and translated to internal codes), and later (by `SW_SIT_init_run()`) copied to soil layers --- SW_Control.c | 5 ++ SW_Files.c | 20 ++++---- SW_Files.h | 22 +++++++-- SW_Site.c | 94 ++++++++++++++++++++++++++++++++---- SW_Site.h | 1 + testing/Input/siteparam.in | 26 ++++++++++ testing/Input/swrc_params.in | 22 +++++++++ testing/files.in | 1 + 8 files changed, 167 insertions(+), 24 deletions(-) create mode 100644 testing/Input/swrc_params.in diff --git a/SW_Control.c b/SW_Control.c index 844690eee..41af8a251 100644 --- a/SW_Control.c +++ b/SW_Control.c @@ -282,6 +282,11 @@ void SW_CTL_read_inputs_from_disk(void) { if (debug) swprintf(" > 'soils'"); #endif + SW_SWRC_read(); + #ifdef SWDEBUG + if (debug) swprintf(" > 'swrc parameters'"); + #endif + SW_VES_read(); #ifdef SWDEBUG if (debug) swprintf(" > 'establishment'"); diff --git a/SW_Files.c b/SW_Files.c index 6b42a83ab..69a615c98 100644 --- a/SW_Files.c +++ b/SW_Files.c @@ -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]); diff --git a/SW_Files.h b/SW_Files.h index 6c3e10c99..c0b97d50a 100644 --- a/SW_Files.h +++ b/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_Site.c b/SW_Site.c index c4498406e..94a4a8aac 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -937,8 +937,17 @@ void SW_SIT_read(void) { if (debug) swprintf("'SW_SIT_read': scenario = %s\n", c->scenario); #endif break; + case 41: + strcpy(v->site_swrc_name, inbuf); + v->site_swrc_type = encode_str2swrc(v->site_swrc_name); + break; + case 42: + strcpy(v->site_pdf_name, inbuf); + v->site_pdf_type = encode_str2pdf(v->site_pdf_name); + break; + default: - if (lineno > 40 + MAX_TRANSP_REGIONS) + if (lineno > 42 + MAX_TRANSP_REGIONS) break; /* skip extra lines */ if (MAX_TRANSP_REGIONS < v->n_transp_rgn) { @@ -1273,6 +1282,73 @@ 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 PDF */ + if (SW_Site.site_pdf_type != 0) 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 @@ -1364,15 +1440,12 @@ void SW_SIT_init_run(void) { ); -/**********************************/ -/* TODO: remove once new inputs are implemented */ -lyr->swrc_type = 1; -lyr->pdf_type = 1; -/**********************************/ - - if (lyr->swrcp_from_pdf) { - /* Use pedotransfer function PDF */ - /* estimate parameters of soil water retention curve (SWRC) for layer */ + if (lyr->pdf_type > 0) { + /* Use pedotransfer function PDF + estimate parameters of soil water retention curve (SWRC) for layer. + If pdf_type == 0, then the parameters were already obtained from disk + by `SW_SWRC_read()` + */ SWRC_PDF_estimate_parameters( lyr->pdf_type, lyr->swrcp, @@ -1894,6 +1967,7 @@ void _echo_inputs(void) { SW_SWRC_SWCtoSWP(s->lyr[i]->swcBulk_min, s->lyr[i]), SW_SWRC_SWCtoSWP(s->lyr[i]->swcBulk_init, s->lyr[i]) ); + } LogError( logfp, diff --git a/SW_Site.h b/SW_Site.h index 4418ee975..021d128b8 100644 --- a/SW_Site.h +++ b/SW_Site.h @@ -220,6 +220,7 @@ 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); diff --git a/testing/Input/siteparam.in b/testing/Input/siteparam.in index 28b936e99..c60318928 100644 --- a/testing/Input/siteparam.in +++ b/testing/Input/siteparam.in @@ -83,6 +83,32 @@ 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 water retention curve (SWRC) ------ +# +# Implemented options (`swrc_name`/`pdf_name`, see `swrc2str[]`/`pdf2str[]`): +# - pdf_name = "NoPDF": parameters for any SWRC obtained from +# input file ("swrc_params.in") instead of estimated from PDF +# - swrc_name = "Campbell1974": (Campbell 1974) +# * pdf_name = "Cosby1984AndOthers": PDFs by Cosby et al. 1984 +# (but Saxton et al. 2006 for `swc_sat` and Rawls et al. 1985 for `swc_min`) +# * pdf_name = "Cosby1984": PDFs by Cosby et al. 1984 +# (including `swc_sat` and `swc_min` = 0) +# - swrc_name = "vanGenuchten1980": van Genuchten-Mualem (van Genuchten 1980) +# * pdf_name = "Rosetta3": not implemented in C (but see rSOILWAT2) +# - swrc_name = "BrooksCorey1964": (Brooks and Corey 1964) +# * pdf_name: currently, no implemented PDF (but see rSOILWAT2) +# - swrc_name = "Brunswick": (Weber et al. 2019) +# * pdf_name: currently, no implemented PDF (but see rSOILWAT2) +# - swrc_name = "FXW": (Fredlund and Xing 1994, Wang et al. 2018, Rudiyanto et al. 2020) +# * pdf_name: currently, no implemented PDF (but see rSOILWAT2) +# +# Note: option "Campbell1974"/"Cosby1984AndOthers" was hard-coded < v7.0.0 +# +Campbell1974 # Select one of the implemented SWRCs +Cosby1984AndOthers # Select one of the implemented PDFs (or "NoPDF") + + #---- 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/swrc_params.in b/testing/Input/swrc_params.in new file mode 100644 index 000000000..0fcb66c7b --- /dev/null +++ b/testing/Input/swrc_params.in @@ -0,0 +1,22 @@ +#------ 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" +# * param1 = "saturation" soil water matric potential [-bar] +# * param2 = saturated volumetric water content for the matric component [cm/cm] +# * param3 = b, slope of the linear log-log retention curve [-] + +# param1 param2 param3 param4 param5 param6 +18.6080 0.42703 5.3020 0.0000 0.0000 0.0000 +20.4644 0.43290 7.0500 0.0000 0.0000 0.0000 +22.8402 0.44013 9.4320 0.0000 0.0000 0.0000 +24.0381 0.44291 10.0690 0.0000 0.0000 0.0000 +24.2159 0.44359 10.3860 0.0000 0.0000 0.0000 +23.3507 0.44217 10.3830 0.0000 0.0000 0.0000 +12.3880 0.41370 7.3250 0.0000 0.0000 0.0000 +12.3880 0.41370 7.3250 0.0000 0.0000 0.0000 diff --git a/testing/files.in b/testing/files.in index 53174483c..fe3aa484e 100755 --- a/testing/files.in +++ b/testing/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 From 253073dc0cd8fd5dcc1f7253c767b2d9852e890a Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Mon, 28 Feb 2022 08:21:08 -0500 Subject: [PATCH 014/326] Fix messages in `SW_SIT_init_run()` - fix commit 80a154ebe48a785efb665dc2c1011c9ad2005738 --> that commit replaced the index variable `s` with `lyr->id` to print soil layer information for messages in `SW_SIT_init_run()` but "lyr" is not available outside of soil-layer loops --> use `SW_Site.lyr[s]->id` instead where `lyr` is not defined --- SW_Site.c | 68 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/SW_Site.c b/SW_Site.c index 94a4a8aac..33d0ba60f 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -1737,16 +1737,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", - lyr->id + 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, ""); @@ -1754,19 +1764,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", - lyr->id + 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] + ); } } @@ -1782,17 +1802,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 From a539dfa8d662961f7dd3752d01fee1b85fafa4b5 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Mon, 28 Feb 2022 09:12:12 -0500 Subject: [PATCH 015/326] Fix memory leaks associated with creating strings for SWRCs/PDFs - strings created by `Str_Dup()` must be freed which commit 1fd9564fe3d5aa39457d6b931cb591ef6591f1a6 did not do -> replace `Str_Dup()` with `(char *) "..."` which does not need to be freed --- SW_Site.c | 8 ++++---- test/test_SW_Site.cc | 8 ++++---- test/test_SW_SoilWater.cc | 16 ++++++++-------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/SW_Site.c b/SW_Site.c index 33d0ba60f..43ad37f16 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -391,15 +391,15 @@ void SWRC_PDF_Cosby1984_for_Campbell1974( Bool check_SWRC_vs_PDF(char *swrc_name, char *pdf_name) { Bool res = swFALSE; - if (Str_CompareI(pdf_name, Str_Dup("NoPDF")) == 0) { + if (Str_CompareI(pdf_name, (char *) "NoPDF") == 0) { res = swTRUE; } else { if ( - Str_CompareI(swrc_name, Str_Dup("Campbell1974")) == 0 && + Str_CompareI(swrc_name, (char *) "Campbell1974") == 0 && ( - Str_CompareI(pdf_name, Str_Dup("Cosby1984AndOthers")) == 0 || - Str_CompareI(pdf_name, Str_Dup("Cosby1984")) == 0 + Str_CompareI(pdf_name, (char *) "Cosby1984AndOthers") == 0 || + Str_CompareI(pdf_name, (char *) "Cosby1984") == 0 ) ) { res = swTRUE; diff --git a/test/test_SW_Site.cc b/test/test_SW_Site.cc index 2c634a282..3b96744f1 100644 --- a/test/test_SW_Site.cc +++ b/test/test_SW_Site.cc @@ -54,8 +54,8 @@ namespace { //--- Test Cosby et al. 1984 PDF for Campbell's 1974 SWRC - swrc_type = encode_str2swrc(Str_Dup("Campbell1974")); - pdf_type = encode_str2pdf(Str_Dup("Cosby1984AndOthers")); + swrc_type = encode_str2swrc((char *) "Campbell1974"); + pdf_type = encode_str2pdf((char *) "Cosby1984AndOthers"); SWRC_PDF_estimate_parameters( pdf_type, @@ -104,8 +104,8 @@ namespace { //--- Test bad parameters: Cosby et al. 1984 PDF for Campbell's 1974 SWRC - swrc_type = encode_str2swrc(Str_Dup("Campbell1974")); - pdf_type = encode_str2pdf(Str_Dup("Cosby1984AndOthers")); + swrc_type = encode_str2swrc((char *) "Campbell1974"); + pdf_type = encode_str2pdf((char *) "Cosby1984AndOthers"); sand = 10. + 1./3.; // unrealistic but forces `bmatric` to become 0 diff --git a/test/test_SW_SoilWater.cc b/test/test_SW_SoilWater.cc index 2cb81a8f8..2791b76e6 100644 --- a/test/test_SW_SoilWater.cc +++ b/test/test_SW_SoilWater.cc @@ -128,8 +128,8 @@ namespace{ //--- Cosby et al. 1984 PDF for Campbell's 1974 SWRC unsigned int - swrc_type = encode_str2swrc(Str_Dup("Campbell1974")), - pdf_type = encode_str2pdf(Str_Dup("Cosby1984AndOthers")); + swrc_type = encode_str2swrc((char *) "Campbell1974"), + pdf_type = encode_str2pdf((char *) "Cosby1984AndOthers"); SWRC_PDF_estimate_parameters( pdf_type, @@ -202,8 +202,8 @@ namespace{ //--- Cosby et al. 1984 PDF for Campbell's 1974 SWRC - swrc_type = encode_str2swrc(Str_Dup("Campbell1974")); - pdf_type = encode_str2pdf(Str_Dup("Cosby1984AndOthers")); + swrc_type = encode_str2swrc((char *) "Campbell1974"); + pdf_type = encode_str2pdf((char *) "Cosby1984AndOthers"); SWRC_PDF_estimate_parameters( pdf_type, @@ -247,8 +247,8 @@ namespace{ //--- Campbell's 1974 SWRC (using Cosby et al. 1984 PDF) unsigned int - swrc_type = encode_str2swrc(Str_Dup("Campbell1974")), - pdf_type = encode_str2pdf(Str_Dup("Cosby1984AndOthers")); + swrc_type = encode_str2swrc((char *) "Campbell1974"), + pdf_type = encode_str2pdf((char *) "Cosby1984AndOthers"); // set gravel fractions on the interval [.0, 1], step .1 for (i = 0; i <= 10; i++) { @@ -306,8 +306,8 @@ namespace{ //--- Cosby et al. 1984 PDF for Campbell's 1974 SWRC - swrc_type = encode_str2swrc(Str_Dup("Campbell1974")); - pdf_type = encode_str2pdf(Str_Dup("Cosby1984AndOthers")); + swrc_type = encode_str2swrc((char *) "Campbell1974"); + pdf_type = encode_str2pdf((char *) "Cosby1984AndOthers"); SWRC_PDF_estimate_parameters( pdf_type, From 5fc1e204dea6b896ec8d099e32a68a7ec5a5e94d Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Mon, 28 Feb 2022 17:46:42 -0500 Subject: [PATCH 016/326] New "van Genuchten" SWRC with (R-based) "Rosetta3" PDF - implemented with names "vanGenuchten1980" and "Rosetta3" - updated values: `N_SWRCs`, `N_PDFs`, `swrc2str[]`, `pdf2str[]` - updated functions: `check_SWRC_vs_PDF()`, `SWRC_check_parameters()`, `SWRC_SWCtoSWP()`, `SWRC_SWPtoSWC()` - new functions: `SWRC_check_parameters_for_vanGenuchten1980()`, `SWRC_SWCtoSWP_vanGenuchten1980`, `SWRC_SWPtoSWC_vanGenuchten1980() - updated input files: `swrc_param.in` (to explain meaning and units of parameters if "vanGenuchten1980" is selected) --- SW_Site.c | 97 +++++++++++++++++++++++++++++++++++- SW_Site.h | 5 +- SW_SoilWater.c | 91 +++++++++++++++++++++++++++++++++ SW_SoilWater.h | 12 +++++ doc/SOILWAT2.bib | 20 ++++++++ testing/Input/swrc_params.in | 8 ++- 6 files changed, 228 insertions(+), 5 deletions(-) diff --git a/SW_Site.c b/SW_Site.c index 43ad37f16..441adcff3 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -99,7 +99,8 @@ RealD `SWRC_check_parameters()`, `SWRC_SWCtoSWP()`, and `SWRC_SWPtoSWC()`. */ char const *swrc2str[N_SWRCs] = { - "Campbell1974" + "Campbell1974", + "vanGenuchten1980" }; /** Character representation of implemented Pedotransfer Functions (PDF) @@ -113,7 +114,8 @@ char const *swrc2str[N_SWRCs] = { char const *pdf2str[N_PDFs] = { "NoPDF", "Cosby1984AndOthers", - "Cosby1984" + "Cosby1984", + "Rosetta3" }; @@ -404,6 +406,12 @@ Bool check_SWRC_vs_PDF(char *swrc_name, char *pdf_name) { ) { res = swTRUE; } + else if ( + Str_CompareI(swrc_name, (char *) "vanGenuchten1980") == 0 && + Str_CompareI(pdf_name, (char *) "Rosetta3") == 0 + ) { + res = swTRUE; + } } return res; @@ -428,6 +436,10 @@ Bool SWRC_check_parameters(unsigned int swrc_type, double *swrcp) { res = SWRC_check_parameters_for_Campbell1974(swrcp); break; + case 1: + res = SWRC_check_parameters_for_vanGenuchten1980(swrcp); + break; + default: LogError( logfp, @@ -501,6 +513,87 @@ Bool SWRC_check_parameters_for_Campbell1974(double *swrcp) { return res; } +/** + @brief Check van Genuchten 1980 SWRC parameters \cite vanGenuchten1980 + + See `SWRC_SWCtoSWP_vanGenuchten1980()` and `SWRC_SWPtoSWC_vanGenuchten1980()` + for implementation of van Genuchten's 1980 SWRC. + + van Genuchten's 1980 SWRC uses four parameters: + - `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 [-] + + @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; + + 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 within 0-1)\n", + swrcp[0] + ); + } + + 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 within 0-1)\n", + swrcp[1] + ); + } + + 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(swrcp[3], 1.0)) { + res = swFALSE; + LogError( + logfp, + LOGWARN, + "SWRC_check_parameters_for_vanGenuchten1980(): invalid value of " + "n = %f (must be > 1)\n", + swrcp[3] + ); + } + + return res; +} + /** @brief Saxton et al. 2006 PDFs \cite Saxton2006 diff --git a/SW_Site.h b/SW_Site.h index 021d128b8..112a19e07 100644 --- a/SW_Site.h +++ b/SW_Site.h @@ -54,8 +54,8 @@ extern "C" { #endif #define SWRC_PARAM_NMAX 6 /**< Maximal number of SWRC parameters implemented */ -#define N_SWRCs 1 /**< Number of implemented SWRCs */ -#define N_PDFs 3 /**< Number of implemented PDFs */ +#define N_SWRCs 2 /**< Number of implemented SWRCs */ +#define N_PDFs 4 /**< Number of implemented PDFs */ typedef unsigned int LyrIndex; @@ -201,6 +201,7 @@ void SWRC_PDF_Cosby1984_for_Campbell1974( Bool check_SWRC_vs_PDF(char *swrc_name, char *pdf_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); void PDF_Saxton2006( double *swc_sat, diff --git a/SW_SoilWater.c b/SW_SoilWater.c index 5dd2da286..876f1cf7f 100644 --- a/SW_SoilWater.c +++ b/SW_SoilWater.c @@ -1084,6 +1084,10 @@ double SWRC_SWCtoSWP( res = SWRC_SWCtoSWP_Campbell1974(swcBulk, swrcp, gravel, width); break; + case 1: + res = SWRC_SWCtoSWP_vanGenuchten1980(swcBulk, swrcp, gravel, width); + break; + default: LogError( logfp, @@ -1147,6 +1151,55 @@ double SWRC_SWCtoSWP_Campbell1974( } +/** + @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()`. + + @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] + + @return Soil water potential [-bar] +**/ +double SWRC_SWCtoSWP_vanGenuchten1980( + double swcBulk, + double *swrcp, + double gravel, + double width +) { + double res, tmp, theta; + + // convert bulk SWC [cm] to theta = matric VWC [cm / cm] + theta = swcBulk / (width * (1. - gravel)); + + // calculate inverse of normalized theta + tmp = theta - swrcp[0]; + + if (!isfinite(tmp) || LE(tmp, 0.)) { + LogError( + logfp, + LOGFATAL, + "SWRC_SWCtoSWP_vanGenuchten1980(): " + "invalid value of (theta - theta(residual)) = %f (must be > 0)\n", + tmp + ); + } + + tmp = (swrcp[1] - swrcp[0]) / tmp; + + + // calculate tension [cm of H20] + tmp = powe(tmp, 1. / (1. - 1. / swrcp[3])); // tmp values are in 0-1 + res = pow(-1. + tmp, 1. / swrcp[3]) / swrcp[2]; // use `pow()` because x < 0 + + // convert [cm of H20 at 4 C; value from `soilDB::KSSL_VG_model()`] to [bar] + return res / 1019.716; +} + /** @brief Convert soil water potential to soil water content using @@ -1218,6 +1271,10 @@ double SWRC_SWPtoSWC( res = SWRC_SWPtoSWC_Campbell1974(swpMatric, swrcp, gravel, width); break; + case 1: + res = SWRC_SWPtoSWC_vanGenuchten1980(swpMatric, swrcp, gravel, width); + break; + default: LogError( logfp, @@ -1268,8 +1325,42 @@ double SWRC_SWPtoSWC_Campbell1974( } +/** + @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()`. + @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 H20 at 4 C; + // value from `soilDB::KSSL_VG_model()`] + phi = swpMatric * 1019.716; + + // assume that `swpMatric` > 0 + tmp = powe(swrcp[2] * phi, swrcp[3]); + tmp = powe(1. + tmp, 1. - 1. / swrcp[3]); + + // calculate matric theta [cm / cm] + res = swrcp[0] + (swrcp[1] - swrcp[0]) / tmp; + + // convert matric theta [cm / cm] to bulk SWC [cm] + return (1. - gravel) * width * res; +} /** diff --git a/SW_SoilWater.h b/SW_SoilWater.h index 6bb31f3ad..3eb5e5c4c 100644 --- a/SW_SoilWater.h +++ b/SW_SoilWater.h @@ -174,6 +174,12 @@ double SWRC_SWCtoSWP_Campbell1974( double gravel, double width ); +double SWRC_SWCtoSWP_vanGenuchten1980( + double swcBulk, + double *swrcp, + double gravel, + double width +); RealD SW_SWRC_SWPtoSWC(RealD swpMatric, SW_LAYER_INFO *lyr); double SWRC_SWPtoSWC( @@ -189,6 +195,12 @@ double SWRC_SWPtoSWC_Campbell1974( double gravel, double width ); +double SWRC_SWPtoSWC_vanGenuchten1980( + double swpMatric, + double *swrcp, + double gravel, + double width +); #ifdef SWDEBUG void SW_WaterBalance_Checks(void); diff --git a/doc/SOILWAT2.bib b/doc/SOILWAT2.bib index ba3a998d4..8051f3823 100644 --- a/doc/SOILWAT2.bib +++ b/doc/SOILWAT2.bib @@ -344,3 +344,23 @@ @article{Campbell1974 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} +} diff --git a/testing/Input/swrc_params.in b/testing/Input/swrc_params.in index 0fcb66c7b..867487821 100644 --- a/testing/Input/swrc_params.in +++ b/testing/Input/swrc_params.in @@ -6,11 +6,17 @@ # selected SWRC (see `siteparam.in`) # - unused columns are ignored (if selected SWRC uses fewer than 6 parameters) -# swrc = "Campbell1974" +# swrc = "Campbell1974" (default values below) # * param1 = "saturation" soil water matric potential [-bar] # * param2 = saturated volumetric water content for the matric component [cm/cm] # * param3 = b, slope of the linear log-log retention curve [-] +# 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 [-] + # param1 param2 param3 param4 param5 param6 18.6080 0.42703 5.3020 0.0000 0.0000 0.0000 20.4644 0.43290 7.0500 0.0000 0.0000 0.0000 From f993d4254697907a68c91ce0e3e15e7bd7ff8251 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Tue, 1 Mar 2022 11:33:17 -0500 Subject: [PATCH 017/326] `check_SWRC_vs_PDF()`: new "isSW2" differentiates if a PDF is implemented in C - `pdf2str[]` lists all available PDFs (whether implemented in C or rSOILWAT2) - `check_SWRC_vs_PDF()` now uses "isSW2" to determine if a PDF is available in C, in rSOILWAT2, or is not a match for the SWRC --- SW_Site.c | 7 +++++-- SW_Site.h | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/SW_Site.c b/SW_Site.c index 441adcff3..4aba7aad2 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -387,10 +387,11 @@ void SWRC_PDF_Cosby1984_for_Campbell1974( @param[in] *swrc_name Name selected SWRC @param[in] *pdf_name Name selected PDF + @param[in] isSW2 Logical if scope of PDF implementation is "SOILWAT2". @return A logical value indicating if SWRC and PDF are compatible. */ -Bool check_SWRC_vs_PDF(char *swrc_name, char *pdf_name) { +Bool check_SWRC_vs_PDF(char *swrc_name, char *pdf_name, Bool isSW2) { Bool res = swFALSE; if (Str_CompareI(pdf_name, (char *) "NoPDF") == 0) { @@ -407,7 +408,9 @@ Bool check_SWRC_vs_PDF(char *swrc_name, char *pdf_name) { res = swTRUE; } else if ( + !isSW2 && Str_CompareI(swrc_name, (char *) "vanGenuchten1980") == 0 && + // "Rosetta3" PDF is not implemented in SOILWAT2 (but in rSOILWAT2) Str_CompareI(pdf_name, (char *) "Rosetta3") == 0 ) { res = swTRUE; @@ -1491,7 +1494,7 @@ void SW_SIT_init_run(void) { /* Check compatibility between selected SWRC and PDF */ - if (!check_SWRC_vs_PDF(sp->site_swrc_name, sp->site_pdf_name)) { + if (!check_SWRC_vs_PDF(sp->site_swrc_name, sp->site_pdf_name, swTRUE)) { LogError( logfp, LOGFATAL, diff --git a/SW_Site.h b/SW_Site.h index 112a19e07..3bf3c78a3 100644 --- a/SW_Site.h +++ b/SW_Site.h @@ -198,7 +198,7 @@ void SWRC_PDF_Cosby1984_for_Campbell1974( ); -Bool check_SWRC_vs_PDF(char *swrc_name, char *pdf_name); +Bool check_SWRC_vs_PDF(char *swrc_name, char *pdf_name, Bool isSW2); 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); From e3836907b006a6e32a7b120af33df0577ef01271 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Tue, 1 Mar 2022 11:36:24 -0500 Subject: [PATCH 018/326] `SWRC_SWCtoSWP()` now fails if no soil moisture - previously, the function returned for complete absence of soil moisture a matric tension = 0 bar (suggesting wettest conditions) -> the correct value would be something like infinity --> now, the code throws an error if there is no soil moisture * this has no consequences for normal SOILWAT2 runs, but a few unit tests needed to be updated --- SW_SoilWater.c | 8 ++------ test/test_SW_Flow_Lib.cc | 10 ---------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/SW_SoilWater.c b/SW_SoilWater.c index 876f1cf7f..f20094ffd 100644 --- a/SW_SoilWater.c +++ b/SW_SoilWater.c @@ -1064,15 +1064,11 @@ double SWRC_SWCtoSWP( double gravel, double width ) { - if (missing(swcBulk) || ZRO(swcBulk) || EQ(gravel, 1.)) { - return 0.0; - } - - if (LE(swcBulk, 0.0)) { + if (missing(swcBulk) || LE(swcBulk, 0.) || EQ(gravel, 1.) || LE(width, 0.)) { LogError( logfp, LOGFATAL, - "SWRC_SWCtoSWP(): invalid SWC = %.4f (must be >= 0)\n", + "SWRC_SWCtoSWP(): invalid SWC = %.4f (must be > 0)\n", swcBulk ); } diff --git a/test/test_SW_Flow_Lib.cc b/test/test_SW_Flow_Lib.cc index 734dda5d3..77159420c 100644 --- a/test/test_SW_Flow_Lib.cc +++ b/test/test_SW_Flow_Lib.cc @@ -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); From 6ff44516082e3e351acca3578ba5d76bf44313b1 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Tue, 1 Mar 2022 11:38:02 -0500 Subject: [PATCH 019/326] Update and improved unit tests for SWRCs/PDFs - improved coverage of edge cases - new unit tests for newly implemented "vanGenuchten1980" SWRC - loop over implemented SWRCs to translate SWC <-> SWP --- test/test_SW_Site.cc | 246 ++++++++++++++++++++++++-------- test/test_SW_SoilWater.cc | 286 ++++++++++++++++++++------------------ 2 files changed, 332 insertions(+), 200 deletions(-) diff --git a/test/test_SW_Site.cc b/test/test_SW_Site.cc index 3b96744f1..c3c7390c1 100644 --- a/test/test_SW_Site.cc +++ b/test/test_SW_Site.cc @@ -40,97 +40,139 @@ namespace { + // List SWRC: PDFs + const char *ns_pdfca2C1974[] = { + "Campbell1974", + "Cosby1984AndOthers", "Cosby1984" + }; + const char *ns_pdfa2vG1980[] = { + "vanGenuchten1980", + // all PDFs + "Rosetta3" + }; + const char *ns_pdfc2vG1980[] = { + "vanGenuchten1980" + // PDFs implemented in C + }; + + // Test pedotransfer functions - TEST(SWSiteTest, PDFs) { + TEST(SiteTest, PDFs) { // inputs RealD swrcp[SWRC_PARAM_NMAX], - swc_sat, sand = 0.33, clay = 0.33, - gravel = 0.1, - width = 10.; - unsigned int swrc_type, pdf_type; - + gravel = 0.1; + unsigned int swrc_type, k; - //--- Test Cosby et al. 1984 PDF for Campbell's 1974 SWRC - swrc_type = encode_str2swrc((char *) "Campbell1974"); - pdf_type = encode_str2pdf((char *) "Cosby1984AndOthers"); - SWRC_PDF_estimate_parameters( - pdf_type, - swrcp, - sand, clay, gravel - ); + //--- Matching PDF-SWRC pairs + // (k starts at 1 because 0 holds the SWRC) - EXPECT_EQ( - SWRC_check_parameters(swrc_type, swrcp), - swTRUE - ); + swrc_type = encode_str2swrc((char *) ns_pdfca2C1974[0]); + for (k = 1; k < length(ns_pdfca2C1974); k++) { + SWRC_PDF_estimate_parameters( + encode_str2pdf((char *) ns_pdfca2C1974[k]), + swrcp, + sand, clay, gravel + ); + EXPECT_TRUE((bool) SWRC_check_parameters(swrc_type, swrcp)); + } - // Test psisMatric - EXPECT_GT(swrcp[0], 3.890451); /* Value should always be greater - than 3.890451 based upon complete consideration of potential range of sand and clay values */ - EXPECT_LT(swrcp[0], 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(swrcp[0], 27.586715750763947); /* If sand is - .33 and clay is .33, psisMatric should be 27.5867 */ + swrc_type = encode_str2swrc((char *) ns_pdfc2vG1980[0]); + for (k = 1; k < length(ns_pdfc2vG1980); k++) { + SWRC_PDF_estimate_parameters( + encode_str2pdf((char *) ns_pdfc2vG1980[k]), + swrcp, + sand, clay, gravel + ); + EXPECT_TRUE((bool) SWRC_check_parameters(swrc_type, swrcp)); + } + } - // Test thetasMatric - EXPECT_GT(swrcp[1], 0.363); /* Value should always be greater - than 36.3 based upon complete consideration of potential range of sand and clay values */ - EXPECT_LT(swrcp[1], 0.468); /* Value should always be less - than 46.8 based upon complete consideration of potential range of sand and clay values */ - EXPECT_DOUBLE_EQ(swrcp[1], 0.44593); /* If sand is .33 and - clay is .33, thetasMatric should be 44.593 */ - // Test bMatric - EXPECT_GT(swrcp[2], 2.8); /* Value should always be greater than - 2.8 based upon complete consideration of potential range of sand and clay values */ - EXPECT_LT(swrcp[2], 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(swrcp[2], 8.182); /* If sand is .33 and clay is .33, - thetasMatric should be 8.182 */ + // Test fatal failures of PDF estimation + TEST(SiteDeathTest, PDFs) { + RealD + swrcp[SWRC_PARAM_NMAX], + sand = 0.33, + clay = 0.33, + gravel = 0.1; + unsigned int pdf_type; - //--- Test `swcBulk_saturated` - PDF_Saxton2006(&swc_sat, sand, clay, gravel, width); - // The swcBulk_saturated should be greater than 0 - EXPECT_GT(swc_sat, 0.); - // The swcBulk_saturated can't be greater than the width of the layer - EXPECT_LT(swc_sat, width); + //--- Test unimplemented PDF + pdf_type = N_PDFs + 1; + EXPECT_DEATH_IF_SUPPORTED( + SWRC_PDF_estimate_parameters(pdf_type, swrcp, sand, clay, gravel), + "@ generic.c LogError" + ); + } - //--- Test bad parameters: Cosby et al. 1984 PDF for Campbell's 1974 SWRC - swrc_type = encode_str2swrc((char *) "Campbell1974"); - pdf_type = encode_str2pdf((char *) "Cosby1984AndOthers"); + // Test PDF-SWRC pairings + TEST(SiteTest, PDF2SWRC) { + unsigned int k; // `length()` returns "unsigned long" - sand = 10. + 1./3.; // unrealistic but forces `bmatric` to become 0 + // Matching/incorrect PDF-SWRC pairs + // (k starts at 1 because 0 holds the SWRC) + for (k = 0; k < N_SWRCs; k++) { + EXPECT_TRUE( + (bool) check_SWRC_vs_PDF((char *) swrc2str[k], (char *) "NoPDF", swTRUE) + ); + } - SWRC_PDF_estimate_parameters( - pdf_type, - swrcp, - sand, clay, gravel - ); + for (k = 1; k < length(ns_pdfca2C1974); k++) { + EXPECT_TRUE( + (bool) check_SWRC_vs_PDF( + (char *) ns_pdfca2C1974[0], + (char *) ns_pdfca2C1974[k], + swTRUE + ) + ); + + EXPECT_FALSE( + (bool) check_SWRC_vs_PDF( + (char *) ns_pdfa2vG1980[0], + (char *) ns_pdfca2C1974[k], + swTRUE + ) + ); + } - EXPECT_EQ( - SWRC_check_parameters(swrc_type, swrcp), - swFALSE - ); + for (k = 1; k < length(ns_pdfa2vG1980); k++) { + EXPECT_TRUE( + (bool) check_SWRC_vs_PDF( + (char *) ns_pdfa2vG1980[0], + (char *) ns_pdfa2vG1980[k], + swFALSE + ) + ); + + EXPECT_FALSE( + (bool) check_SWRC_vs_PDF( + (char *) ns_pdfca2C1974[0], + (char *) ns_pdfa2vG1980[k], + swFALSE + ) + ); + } } // Test fatal failures of SWRC parameter checks - TEST(SiteDeathTest, PDFs) { + TEST(SiteDeathTest, SWRCpChecks) { // inputs - unsigned int swrc_type; RealD swrcp[SWRC_PARAM_NMAX]; + unsigned int swrc_type; + //--- Test unimplemented SWRC - swrc_type = 255; + swrc_type = N_SWRCs + 1; EXPECT_DEATH_IF_SUPPORTED( SWRC_check_parameters(swrc_type, swrcp), @@ -139,6 +181,88 @@ namespace { } + // 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; + 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; + 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; + } + + // Test that `SW_SIT_init_run` fails on bad soil inputs TEST(SiteDeathTest, SoilParameters) { LyrIndex n1 = 0, n2 = 1, k = 2; diff --git a/test/test_SW_SoilWater.cc b/test/test_SW_SoilWater.cc index 2791b76e6..e5e54db0c 100644 --- a/test/test_SW_SoilWater.cc +++ b/test/test_SW_SoilWater.cc @@ -114,213 +114,221 @@ namespace{ } - // Test the 'SW_SoilWater' function 'SWRC_SWCtoSWP' - TEST(SWSoilWaterTest, SWCtoSWP) { + // Test the 'SW_SoilWater' functions 'SWRC_SWCtoSWP' and `SWRC_SWPtoSWC` + TEST(SWSoilWaterTest, TranslateBetweenSWCandSWP) { // set up mock variables RealD - res, + phi, swcBulk, swp, swc_fc, swc_wp, swrcp[SWRC_PARAM_NMAX], sand = 0.33, clay = 0.33, gravel = 0.2, width = 10.; + unsigned int swrc_type, pdf_type; - //--- Cosby et al. 1984 PDF for Campbell's 1974 SWRC - unsigned int - swrc_type = encode_str2swrc((char *) "Campbell1974"), - pdf_type = encode_str2pdf((char *) "Cosby1984AndOthers"); - - SWRC_PDF_estimate_parameters( - pdf_type, - swrcp, - sand, - clay, - gravel - ); - // when swc is 0, we expect res == 0 - res = SWRC_SWCtoSWP(0., swrc_type, swrcp, gravel, width); - EXPECT_EQ(res, 0.0); - - // when swc is SW_MISSING, we expect res == 0 - res = SWRC_SWCtoSWP(SW_MISSING, swrc_type, swrcp, gravel, width); - EXPECT_EQ(res, 0.0); - - // if swc > field capacity, then we expect res < 0.33 bar - swp = 1. / 3.; - swc_fc = SWRC_SWPtoSWC(swp, swrc_type, swrcp, gravel, width); - res = SWRC_SWCtoSWP(swc_fc + 0.1, swrc_type, swrcp, gravel, width); - EXPECT_LT(res, swp); - - // if swc = field capacity, then we expect res == 0.33 bar - res = SWRC_SWCtoSWP(swc_fc, swrc_type, swrcp, gravel, width); - EXPECT_NEAR(res, 1. / 3., tol9); - - // if field capacity > swc > wilting point, then - // we expect 15 bar > res > 0.33 bar - swc_wp = SWRC_SWPtoSWC(15., swrc_type, swrcp, gravel, width); - swcBulk = (swc_wp + swc_fc) / 2.; - res = SWRC_SWCtoSWP(swcBulk, swrc_type, swrcp, gravel, width); - EXPECT_GT(res, 1. / 3.); - EXPECT_LT(res, 15.); - - // if swc = wilting point, then we expect res == 15 bar - res = SWRC_SWCtoSWP(swc_wp, swrc_type, swrcp, gravel, width); - EXPECT_NEAR(res, 15., tol9); - - // if swc < wilting point, then we expect res > 15 bar - res = SWRC_SWCtoSWP(swc_wp / 2., swrc_type, swrcp, gravel, width); - EXPECT_GT(res, 15.); - - // --- 3a) if theta1 == 0 (e.g., gravel == 1) - res = SWRC_SWCtoSWP(swc_wp, swrc_type, swrcp, 1., width); - EXPECT_DOUBLE_EQ(res, 0.); + // 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 PDF to generate `SWRCp` + // (start `k2` at 1 because 0 codes to "NoPDF") + for ( + pdf_type = 1; + pdf_type < N_PDFs && !check_SWRC_vs_PDF( + (char *) swrc2str[swrc_type], + (char *) pdf2str[pdf_type], + swTRUE + ); + pdf_type++ + ) {} + + // Obtain SWRCp + if (pdf_type < N_PDFs) { + // PDF implemented in C: estimate parameters + SWRC_PDF_estimate_parameters( + pdf_type, + swrcp, + sand, + clay, + gravel + ); + + } else { + // PDF 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; + + } else { + FAIL() << "No SWRC parameters available for " << swrc2str[swrc_type]; + } + } + + + //------ Tests SWC -> SWP + + // if swc > field capacity, then we expect phi < 0.33 bar + swp = 1. / 3.; + swc_fc = SWRC_SWPtoSWC(swp, swrc_type, swrcp, gravel, width); + phi = SWRC_SWCtoSWP(swc_fc + 0.1, swrc_type, swrcp, gravel, width); + EXPECT_LT(phi, swp); + + // if swc = field capacity, then we expect phi == 0.33 bar + phi = SWRC_SWCtoSWP(swc_fc, swrc_type, swrcp, gravel, width); + EXPECT_NEAR(phi, 1. / 3., tol9); + + // if field capacity > swc > wilting point, then + // we expect 15 bar > phi > 0.33 bar + swc_wp = SWRC_SWPtoSWC(15., swrc_type, swrcp, gravel, width); + swcBulk = (swc_wp + swc_fc) / 2.; + phi = SWRC_SWCtoSWP(swcBulk, swrc_type, swrcp, gravel, width); + EXPECT_GT(phi, 1. / 3.); + EXPECT_LT(phi, 15.); + + // if swc = wilting point, then we expect phi == 15 bar + phi = SWRC_SWCtoSWP(swc_wp, swrc_type, swrcp, gravel, width); + EXPECT_NEAR(phi, 15., tol9); + + // if swc < wilting point, then we expect phi > 15 bar + swcBulk = SWRC_SWPtoSWC(2. * 15., swrc_type, swrcp, gravel, width); + phi = SWRC_SWCtoSWP(swcBulk, swrc_type, swrcp, gravel, width); + EXPECT_GT(phi, 15.); + + + //------ Tests SWP -> SWC + // when fractionGravel is 1, we expect theta == 0 + EXPECT_EQ( + SWRC_SWPtoSWC(15., swrc_type, swrcp, 1., width), + 0. + ); + + // when width is 0, we expect theta == 0 + EXPECT_EQ( + SWRC_SWPtoSWC(15., swrc_type, swrcp, gravel, 0.), + 0. + ); + + // check bounds of swc + swcBulk = SWRC_SWPtoSWC(15., swrc_type, swrcp, gravel, width); + EXPECT_GE(swcBulk, 0.); + EXPECT_LE(swcBulk, width * (1. - gravel)); + } } + // Death Tests of 'SW_SoilWater' function 'SWRC_SWCtoSWP' TEST(SoilWaterDeathTest, SWCtoSWP) { // set up mock variables RealD swrcp[SWRC_PARAM_NMAX], - sand = 0.33, - clay = 0.33, gravel = 0.1, width = 10.; - unsigned int swrc_type, pdf_type; + unsigned int swrc_type; - //--- we expect fatal errors in three situations + //--- we expect fatal errors in a few situations //--- 1) Unimplemented SWRC - swrc_type = 255; + swrc_type = N_SWRCs + 1; EXPECT_DEATH_IF_SUPPORTED( SWRC_SWCtoSWP(1., swrc_type, swrcp, gravel, width), "@ generic.c LogError" ); - //--- Cosby et al. 1984 PDF for Campbell's 1974 SWRC - swrc_type = encode_str2swrc((char *) "Campbell1974"); - pdf_type = encode_str2pdf((char *) "Cosby1984AndOthers"); - - SWRC_PDF_estimate_parameters( - pdf_type, - swrcp, - sand, - clay, - gravel + // --- 2) swc < 0: water content cannot be missing, zero or negative + swrc_type = 0; // any SWRC + EXPECT_DEATH_IF_SUPPORTED( + SWRC_SWCtoSWP(SW_MISSING, swrc_type, swrcp, gravel, width), + "@ generic.c LogError" ); - - // --- 2) swc < 0: water content cannot be negative EXPECT_DEATH_IF_SUPPORTED( SWRC_SWCtoSWP(-1., swrc_type, swrcp, gravel, width), "@ generic.c LogError" ); - - - // --- 3) if theta_sat == 0 - // note: this case is normally prevented due to checks of inputs by - // function `SWRC_check_parameters_for_Campbell1974` and - // function `_read_layers` - swrcp[1] = 0.; EXPECT_DEATH_IF_SUPPORTED( - SWRC_SWCtoSWP(5., swrc_type, swrcp, gravel, width), + SWRC_SWCtoSWP(0., swrc_type, swrcp, gravel, width), + "@ generic.c LogError" + ); + EXPECT_DEATH_IF_SUPPORTED( + SWRC_SWCtoSWP(1., swrc_type, swrcp, 1., width), + "@ generic.c LogError" + ); + EXPECT_DEATH_IF_SUPPORTED( + SWRC_SWCtoSWP(1., swrc_type, swrcp, gravel, 0.), "@ generic.c LogError" ); - } - - // Test the 'SW_SoilWater' function 'SWRC_SWPtoSWC' - TEST(SWSoilWaterTest, SWPtoSWC) { - // set up mock variables - RealD - res, - swpMatric = 15.0, - swrcp[SWRC_PARAM_NMAX], - sand = 0.33, - clay = 0.33, - gravel, - width = 10.; - short i; - - //--- Campbell's 1974 SWRC (using Cosby et al. 1984 PDF) - unsigned int - swrc_type = encode_str2swrc((char *) "Campbell1974"), - pdf_type = encode_str2pdf((char *) "Cosby1984AndOthers"); - - // set gravel fractions on the interval [.0, 1], step .1 - for (i = 0; i <= 10; i++) { - gravel = i / 10.; - - SWRC_PDF_estimate_parameters( - pdf_type, - swrcp, - sand, - clay, - gravel - ); - res = SWRC_SWPtoSWC(swpMatric, swrc_type, swrcp, gravel, width); + // --- 3) if theta_sat == 0 (specific to Campbell1974) + // note: this case is normally prevented due to checks of inputs + swrc_type = encode_str2swrc((char *) "Campbell1974"); + memset(swrcp, 0., SWRC_PARAM_NMAX * sizeof(swrcp[0])); + swrcp[0] = 24.2159; + swrcp[2] = 10.3860; - EXPECT_GE(res, 0.); - EXPECT_LE(res, width * (1. - gravel)); - } + swrcp[1] = 0.; // instead of 0.4436 + EXPECT_DEATH_IF_SUPPORTED( + SWRC_SWCtoSWP(5., swrc_type, swrcp, gravel, width), + "@ generic.c LogError" + ); - // when fractionGravel is 1, we expect t == 0 - EXPECT_EQ( - SWRC_SWPtoSWC(swpMatric, swrc_type, swrcp, 1., width), - 0. - ); + // --- 4) 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; - // when width is 0, we expect t == 0 - EXPECT_EQ( - SWRC_SWPtoSWC(swpMatric, swrc_type, swrcp, 0., 0.), - 0. + EXPECT_DEATH_IF_SUPPORTED( + SWRC_SWCtoSWP(0.99 * swrcp[0], swrc_type, swrcp, gravel, width), + "@ generic.c LogError" ); } + // Death Tests of 'SW_SoilWater' function 'SWRC_SWPtoSWC' TEST(SoilWaterDeathTest, SWPtoSWC) { // set up mock variables RealD swrcp[SWRC_PARAM_NMAX], - sand = 0.33, - clay = 0.33, gravel = 0.1, width = 10.; - unsigned int swrc_type, pdf_type; + unsigned int swrc_type; //--- we expect fatal errors in two situations //--- 1) Unimplemented SWRC - swrc_type = 255; + swrc_type = N_SWRCs + 1; EXPECT_DEATH_IF_SUPPORTED( SWRC_SWPtoSWC(0., swrc_type, swrcp, gravel, width), "@ generic.c LogError" ); - - //--- Cosby et al. 1984 PDF for Campbell's 1974 SWRC - swrc_type = encode_str2swrc((char *) "Campbell1974"); - pdf_type = encode_str2pdf((char *) "Cosby1984AndOthers"); - - SWRC_PDF_estimate_parameters( - pdf_type, - swrcp, - sand, - clay, - gravel - ); - - // --- 2) swp <= 0: water content cannot be negative + // --- 2) swp <= 0: water content cannot be zero or negative (any SWRC) + swrc_type = 0; EXPECT_DEATH_IF_SUPPORTED( SWRC_SWPtoSWC(-1., swrc_type, swrcp, gravel, width), "@ generic.c LogError" ); + EXPECT_DEATH_IF_SUPPORTED( + SWRC_SWPtoSWC(0., swrc_type, swrcp, gravel, width), + "@ generic.c LogError" + ); } } From 7fad73b711144b4314a0ce7815b2639dbb2a3816 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Tue, 1 Mar 2022 11:48:43 -0500 Subject: [PATCH 020/326] Add explanation on how to update SWRC/PDF framework Implement a new SWRC "XXX" and corresponding PDF "YYY" * update number of `N_SWRCs` and `N_PDFs` * add new names to `swrc2str[]` and `pdf2str[]` * implement new functions * `SWRC_check_parameters_for_XXX()` to validate parameter values * `SWRC_PDF_YYY_for_XXX()` to estimate parameter values (if implemented) * `SWRC_SWCtoSWP_XXX()` to translate moisture content to potential * `SWRC_SWPtoSWC_XXX()` to translate water potential to content * update "wrapper" functions to call new XXX/YYY-specific functions * `check_SWRC_vs_PDF()` * `SWRC_PDF_estimate_parameters()` (if PDF is implemented) * `SWRC_check_parameters()` * `SWRC_SWCtoSWP()` * `SWRC_SWPtoSWC()` * update `siteparam.in` and `swrc_param.in` * update and add new unit tests that utilize new XXX/YYY functions --- SW_Site.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/SW_Site.h b/SW_Site.h index 3bf3c78a3..4c52362fe 100644 --- a/SW_Site.h +++ b/SW_Site.h @@ -53,6 +53,24 @@ extern "C" { #endif +/* Soil Water Retention Curves (SWRC) -- Pedotransfer functions (PDFs) */ +/* MAINTENANCE: Implement a new SWRC "XXX" and corresponding PDF "YYY" + * update number of `N_SWRCs` and `N_PDFs` + * add new names to `swrc2str[]` and `pdf2str[]` + * implement new functions + * `SWRC_check_parameters_for_XXX()` to validate parameter values + * `SWRC_PDF_YYY_for_XXX()` to estimate parameter values (if implemented) + * `SWRC_SWCtoSWP_XXX()` to translate moisture content to potential + * `SWRC_SWPtoSWC_XXX()` to translate water potential to content + * update "wrapper" functions to call new XXX/YYY-specific functions + * `check_SWRC_vs_PDF()` + * `SWRC_PDF_estimate_parameters()` (if PDF is implemented) + * `SWRC_check_parameters()` + * `SWRC_SWCtoSWP()` + * `SWRC_SWPtoSWC()` + * update `siteparam.in` and `swrc_param.in` + * update and add new unit tests that utilize new XXX/YYY functions +*/ #define SWRC_PARAM_NMAX 6 /**< Maximal number of SWRC parameters implemented */ #define N_SWRCs 2 /**< Number of implemented SWRCs */ #define N_PDFs 4 /**< Number of implemented PDFs */ From 237f08339e7ebfe901310bb9320b08c313a7de21 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Tue, 1 Mar 2022 14:40:16 -0500 Subject: [PATCH 021/326] Improve documentation for SWRC/PDF --- SW_Site.c | 31 ++++++++--------- SW_Site.h | 45 ++++++++++++++----------- SW_SoilWater.c | 8 +++-- doc/additional_pages/SOILWAT2_Inputs.md | 7 ++++ 4 files changed, 51 insertions(+), 40 deletions(-) diff --git a/SW_Site.c b/SW_Site.c index 4aba7aad2..21aaf10a0 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -233,7 +233,7 @@ static Bool SW_check_soil_properties(SW_LAYER_INFO *lyr) { /** @brief Translate a SWRC name into a SWRC type number - @param[in] *swrc_name Name selected SWRC + @param[in] *swrc_name Name of a SWRC @return Internal identification number of selected SWRC */ @@ -261,7 +261,7 @@ unsigned int encode_str2swrc(char *swrc_name) { /** @brief Translate a PDF name into a PDF type number - @param[in] *pdf_name Name selected PDF + @param[in] *pdf_name Name of a PDF @return Internal identification number of selected PDF */ @@ -347,20 +347,20 @@ void SWRC_PDF_estimate_parameters( /** - @brief Estimate Campbell's 1974 SWRC parameters \cite Campbell1974 - using Cosby et al. 1984 multivariate PDF \cite Cosby1984 + @brief Estimate Campbell's 1974 SWRC parameters + using Cosby et al. 1984 multivariate PDF Estimation of three SWRC parameter values `swrcp` based on sand, clay, and (silt). Parameters are explained in `SWRC_check_parameters_for_Campbell1974()`. - Multivariate PDFs are from Cosby et al. 1984 Table 4; + Multivariate PDFs are from Cosby et al. 1984 (\cite Cosby1984) Table 4; Cosby et al. 1984 provided also univariate PDFs in Table 5 but they are not used here. See `SWRC_SWCtoSWP_Campbell1974()` and `SWRC_SWPtoSWC_Campbell1974()` - for implementation of Campbell's 1974 SWRC. + 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] @@ -385,9 +385,9 @@ void SWRC_PDF_Cosby1984_for_Campbell1974( /** @brief Check whether selected PDF and SWRC are compatible - @param[in] *swrc_name Name selected SWRC - @param[in] *pdf_name Name selected PDF - @param[in] isSW2 Logical if scope of PDF implementation is "SOILWAT2". + @param[in] *swrc_name Name of selected SWRC + @param[in] *pdf_name Name of selected PDF + @param[in] isSW2 Logical; TRUE if scope of PDF implementation is "SOILWAT2". @return A logical value indicating if SWRC and PDF are compatible. */ @@ -424,7 +424,7 @@ Bool check_SWRC_vs_PDF(char *swrc_name, char *pdf_name, Bool isSW2) { /** @brief Check Soil Water Retention Curve (SWRC) parameters - See `swrc2str` for implemented SWRCs. + See #swrc2str for implemented SWRCs. @param[in] swrc_type Identification number of selected SWRC @param[in] *swrcp Vector of SWRC parameters @@ -459,13 +459,10 @@ Bool SWRC_check_parameters(unsigned int swrc_type, double *swrcp) { /** - @brief Check Campbell's 1974 SWRC parameters \cite Campbell1974 + @brief Check Campbell's 1974 SWRC parameters See `SWRC_SWCtoSWP_Campbell1974()` and `SWRC_SWPtoSWC_Campbell1974()` - for implementation of Campbell's 1974 SWRC. - - See `SWRC_PDF_Cosby1984_for_Campbell1974()` to estimate parameters - using Cosby et al. 1984 pedotransfer functions. + for implementation of Campbell's 1974 SWRC (\cite Campbell1974). Campbell's 1974 SWRC uses three parameters: - `swrcp[0]` (`psisMatric`): saturated soil water matric potential [-bar] @@ -517,10 +514,10 @@ Bool SWRC_check_parameters_for_Campbell1974(double *swrcp) { } /** - @brief Check van Genuchten 1980 SWRC parameters \cite vanGenuchten1980 + @brief Check van Genuchten 1980 SWRC parameters See `SWRC_SWCtoSWP_vanGenuchten1980()` and `SWRC_SWPtoSWC_vanGenuchten1980()` - for implementation of van Genuchten's 1980 SWRC. + for implementation of van Genuchten's 1980 SWRC (\cite vanGenuchten1980). van Genuchten's 1980 SWRC uses four parameters: - `swrcp[0]` (`theta_r`): residual volumetric water content diff --git a/SW_Site.h b/SW_Site.h index 4c52362fe..e697cc328 100644 --- a/SW_Site.h +++ b/SW_Site.h @@ -53,23 +53,28 @@ extern "C" { #endif -/* Soil Water Retention Curves (SWRC) -- Pedotransfer functions (PDFs) */ -/* MAINTENANCE: Implement a new SWRC "XXX" and corresponding PDF "YYY" - * update number of `N_SWRCs` and `N_PDFs` - * add new names to `swrc2str[]` and `pdf2str[]` - * implement new functions - * `SWRC_check_parameters_for_XXX()` to validate parameter values - * `SWRC_PDF_YYY_for_XXX()` to estimate parameter values (if implemented) - * `SWRC_SWCtoSWP_XXX()` to translate moisture content to potential - * `SWRC_SWPtoSWC_XXX()` to translate water potential to content - * update "wrapper" functions to call new XXX/YYY-specific functions - * `check_SWRC_vs_PDF()` - * `SWRC_PDF_estimate_parameters()` (if PDF is implemented) - * `SWRC_check_parameters()` - * `SWRC_SWCtoSWP()` - * `SWRC_SWPtoSWC()` - * update `siteparam.in` and `swrc_param.in` - * update and add new unit tests that utilize new XXX/YYY functions + + +/** + @defgroup swrc_pdf Overview over Soil Water Retention Curves (SWRCs) -- Pedotransfer functions (PDFs) + + __Implement a new SWRC "XXX" and corresponding PDF "YYY"__ + + - update number of `N_SWRCs` and `N_PDFs` + - add new names to `swrc2str[]` and `pdf2str[]` + - implement new functions + - `SWRC_check_parameters_for_XXX()` to validate parameter values + - `SWRC_PDF_YYY_for_XXX()` to estimate parameter values (if implemented) + - `SWRC_SWCtoSWP_XXX()` to translate moisture content to potential + - `SWRC_SWPtoSWC_XXX()` to translate water potential to content + - update "wrapper" functions to call new XXX/YYY-specific functions + - `check_SWRC_vs_PDF()` + - `SWRC_PDF_estimate_parameters()` (if PDF is implemented) + - `SWRC_check_parameters()` + - `SWRC_SWCtoSWP()` + - `SWRC_SWPtoSWC()` + - update `siteparam.in` and `swrc_param.in` + - update and add new unit tests that utilize new XXX/YYY functions */ #define SWRC_PARAM_NMAX 6 /**< Maximal number of SWRC parameters implemented */ #define N_SWRCs 2 /**< Number of implemented SWRCs */ @@ -117,9 +122,9 @@ typedef struct { /* Soil water retention curve (SWRC) */ unsigned int - swrc_type, /**< Type of SWRC (see `swrc2str[]`) */ - pdf_type; /**< Type of PDF (see `pdf2str[]`) */ - RealD swrcp[SWRC_PARAM_NMAX]; /**< Parameters of SWRC: parameter interpretation specific to selected SWRC */ + swrc_type, /**< Type of SWRC (see #swrc2str) */ + pdf_type; /**< Type of PDF (see #pdf2str) */ + 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; diff --git a/SW_SoilWater.c b/SW_SoilWater.c index f20094ffd..43692cd46 100644 --- a/SW_SoilWater.c +++ b/SW_SoilWater.c @@ -1019,6 +1019,8 @@ RealD SW_SnowDepth(RealD SWE, RealD snowdensity) { 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, @@ -1040,7 +1042,7 @@ RealD SW_SWRC_SWCtoSWP(RealD swcBulk, SW_LAYER_INFO *lyr) { @brief Convert soil water content to soil water potential using specified soil water retention curve (SWRC) - See `swrc2str()` for implemented SWRCs. + See #swrc2str() for implemented SWRCs. The code assumes the following conditions: - checked by `SW_SIT_init_run()` @@ -1203,7 +1205,7 @@ double SWRC_SWCtoSWP_vanGenuchten1980( SOILWAT2 convenience wrapper for `SWRC_SWPtoSWC()`. - See `swrc2str()` for implemented SWRCs. + See #swrc2str() for implemented SWRCs. @param[in] swpMatric Soil water potential [-bar] @param[in] *lyr Soil information including @@ -1227,7 +1229,7 @@ RealD SW_SWRC_SWPtoSWC(RealD swpMatric, SW_LAYER_INFO *lyr) { @brief Convert soil water potential to soil water content using specified soil water retention curve (SWRC) - See `swrc2str()` for implemented SWRCs. + See #swrc2str() for implemented SWRCs. The code assumes the following conditions: - checked by `SW_SIT_init_run()` diff --git a/doc/additional_pages/SOILWAT2_Inputs.md b/doc/additional_pages/SOILWAT2_Inputs.md index ab7d23509..926d1b3c2 100644 --- a/doc/additional_pages/SOILWAT2_Inputs.md +++ b/doc/additional_pages/SOILWAT2_Inputs.md @@ -41,6 +41,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 @@ -80,6 +81,12 @@ Go back to the \ref explain_inputs "list of input files" Go back to the \ref explain_inputs "list of input files"
+\section swrcpin swrc_params.in +\verbinclude testing/Input/swrc_params.in + +Go back to the \ref explain_inputs "list of input files" +
+ \section weathsetupin weathsetup.in \verbinclude testing/Input/weathsetup.in From 0f6ac5d6757cfb3a284163fa486cf2e26851b4ec Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Tue, 1 Mar 2022 16:26:01 -0500 Subject: [PATCH 022/326] Option for non-fatal failure of SWC-SWP conversions - new argument "errmode" of `SWRC_SWPtoSWC()`, `SWRC_SWCtoSWP()`, `double SWRC_SWCtoSWP_Campbell1974()`, and `SWRC_SWCtoSWP_vanGenuchten1980()` - SOILWAT2 internal usage of `errmode` will be `LOGFATAL` for fatal errors if the SWC-SWP conversion cannot be carried out - other applications (e.g., rSOILWAT2) may use an `errmode` of `LOGWARN` (or similar) for non-fatal errors (and the above functions will return `SW_MISSING`) - updated unit tests to cover both fatal and non-fatal situations --- SW_SoilWater.c | 68 ++++++++++++++++++------- SW_SoilWater.h | 12 +++-- test/test_SW_SoilWater.cc | 101 ++++++++++++++++++++++++++++---------- 3 files changed, 134 insertions(+), 47 deletions(-) diff --git a/SW_SoilWater.c b/SW_SoilWater.c index 43692cd46..f59920aa6 100644 --- a/SW_SoilWater.c +++ b/SW_SoilWater.c @@ -1034,7 +1034,8 @@ RealD SW_SWRC_SWCtoSWP(RealD swcBulk, SW_LAYER_INFO *lyr) { lyr->swrc_type, lyr->swrcp, lyr->fractionVolBulk_gravel, - lyr->width + lyr->width, + LOGFATAL ); } @@ -1056,6 +1057,9 @@ RealD SW_SWRC_SWCtoSWP(RealD swcBulk, SW_LAYER_INFO *lyr) { @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] **/ @@ -1064,32 +1068,41 @@ double SWRC_SWCtoSWP( unsigned int swrc_type, double *swrcp, double gravel, - double width + double width, + const int errmode ) { + double res = SW_MISSING; + if (missing(swcBulk) || LE(swcBulk, 0.) || EQ(gravel, 1.) || LE(width, 0.)) { LogError( logfp, - LOGFATAL, + errmode, "SWRC_SWCtoSWP(): invalid SWC = %.4f (must be > 0)\n", swcBulk ); - } - double res = SW_MISSING; + return res; + } switch (swrc_type) { case 0: - res = SWRC_SWCtoSWP_Campbell1974(swcBulk, swrcp, gravel, width); + res = SWRC_SWCtoSWP_Campbell1974( + swcBulk, swrcp, gravel, width, + errmode + ); break; case 1: - res = SWRC_SWCtoSWP_vanGenuchten1980(swcBulk, swrcp, gravel, width); + res = SWRC_SWCtoSWP_vanGenuchten1980( + swcBulk, swrcp, gravel, width, + errmode + ); break; default: LogError( logfp, - LOGFATAL, + errmode, "SWRC (type %d) is not implemented.", swrc_type ); @@ -1114,6 +1127,9 @@ double SWRC_SWCtoSWP( @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] **/ @@ -1121,7 +1137,8 @@ double SWRC_SWCtoSWP_Campbell1974( double swcBulk, double *swrcp, double gravel, - double width + double width, + const int errmode ) { // assume that we have soil moisture double theta, tmp, res; @@ -1135,11 +1152,13 @@ double SWRC_SWCtoSWP_Campbell1974( if (!isfinite(tmp) || ZRO(tmp)) { LogError( logfp, - LOGFATAL, + errmode, "SWRC_SWCtoSWP_Campbell1974(): " "invalid value of (theta / theta(saturated)) ^ b = %f (must be != 0)\n", tmp ); + + return SW_MISSING; } res = swrcp[0] / tmp; @@ -1160,6 +1179,9 @@ double SWRC_SWCtoSWP_Campbell1974( @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] **/ @@ -1167,7 +1189,8 @@ double SWRC_SWCtoSWP_vanGenuchten1980( double swcBulk, double *swrcp, double gravel, - double width + double width, + const int errmode ) { double res, tmp, theta; @@ -1180,11 +1203,13 @@ double SWRC_SWCtoSWP_vanGenuchten1980( if (!isfinite(tmp) || LE(tmp, 0.)) { LogError( logfp, - LOGFATAL, + errmode, "SWRC_SWCtoSWP_vanGenuchten1980(): " "invalid value of (theta - theta(residual)) = %f (must be > 0)\n", tmp ); + + return SW_MISSING; } tmp = (swrcp[1] - swrcp[0]) / tmp; @@ -1220,7 +1245,8 @@ RealD SW_SWRC_SWPtoSWC(RealD swpMatric, SW_LAYER_INFO *lyr) { lyr->swrc_type, lyr->swrcp, lyr->fractionVolBulk_gravel, - lyr->width + lyr->width, + LOGFATAL ); } @@ -1243,6 +1269,9 @@ RealD SW_SWRC_SWPtoSWC(RealD swpMatric, SW_LAYER_INFO *lyr) { @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] **/ @@ -1251,18 +1280,21 @@ double SWRC_SWPtoSWC( unsigned int swrc_type, double *swrcp, double gravel, - double width + double width, + const int errmode ) { + double res = SW_MISSING; + if (LE(swpMatric, 0.)) { LogError( logfp, - LOGFATAL, + errmode, "SWRC_SWPtoSWC(): invalid SWP = %.4f (must be > 0)\n", swpMatric ); - } - double res = SW_MISSING; + return res; + } switch (swrc_type) { case 0: @@ -1276,7 +1308,7 @@ double SWRC_SWPtoSWC( default: LogError( logfp, - LOGFATAL, + errmode, "SWRC (type %d) is not implemented.", swrc_type ); diff --git a/SW_SoilWater.h b/SW_SoilWater.h index 3eb5e5c4c..bac7e3131 100644 --- a/SW_SoilWater.h +++ b/SW_SoilWater.h @@ -166,19 +166,22 @@ double SWRC_SWCtoSWP( unsigned int swrc_type, double *swrcp, double gravel, - double width + double width, + const int errmode ); double SWRC_SWCtoSWP_Campbell1974( double swcBulk, double *swrcp, double gravel, - double width + double width, + const int errmode ); double SWRC_SWCtoSWP_vanGenuchten1980( double swcBulk, double *swrcp, double gravel, - double width + double width, + const int errmode ); RealD SW_SWRC_SWPtoSWC(RealD swpMatric, SW_LAYER_INFO *lyr); @@ -187,7 +190,8 @@ double SWRC_SWPtoSWC( unsigned int swrc_type, double *swrcp, double gravel, - double width + double width, + const int errmode ); double SWRC_SWPtoSWC_Campbell1974( double swpMatric, diff --git a/test/test_SW_SoilWater.cc b/test/test_SW_SoilWater.cc index e5e54db0c..141130da1 100644 --- a/test/test_SW_SoilWater.cc +++ b/test/test_SW_SoilWater.cc @@ -126,7 +126,7 @@ namespace{ gravel = 0.2, width = 10.; unsigned int swrc_type, pdf_type; - + const int em = LOGFATAL; // Loop over SWRCs for (swrc_type = 0; swrc_type < N_SWRCs; swrc_type++) { @@ -178,47 +178,47 @@ namespace{ // if swc > field capacity, then we expect phi < 0.33 bar swp = 1. / 3.; - swc_fc = SWRC_SWPtoSWC(swp, swrc_type, swrcp, gravel, width); - phi = SWRC_SWCtoSWP(swc_fc + 0.1, swrc_type, swrcp, gravel, width); + swc_fc = SWRC_SWPtoSWC(swp, swrc_type, swrcp, gravel, width, em); + phi = SWRC_SWCtoSWP(swc_fc + 0.1, swrc_type, swrcp, gravel, width, em); EXPECT_LT(phi, swp); // if swc = field capacity, then we expect phi == 0.33 bar - phi = SWRC_SWCtoSWP(swc_fc, swrc_type, swrcp, gravel, width); + phi = SWRC_SWCtoSWP(swc_fc, swrc_type, swrcp, gravel, width, em); EXPECT_NEAR(phi, 1. / 3., tol9); // if field capacity > swc > wilting point, then // we expect 15 bar > phi > 0.33 bar - swc_wp = SWRC_SWPtoSWC(15., swrc_type, swrcp, gravel, width); + swc_wp = SWRC_SWPtoSWC(15., swrc_type, swrcp, gravel, width, em); swcBulk = (swc_wp + swc_fc) / 2.; - phi = SWRC_SWCtoSWP(swcBulk, swrc_type, swrcp, gravel, width); + phi = SWRC_SWCtoSWP(swcBulk, swrc_type, swrcp, gravel, width, em); EXPECT_GT(phi, 1. / 3.); EXPECT_LT(phi, 15.); // if swc = wilting point, then we expect phi == 15 bar - phi = SWRC_SWCtoSWP(swc_wp, swrc_type, swrcp, gravel, width); + phi = SWRC_SWCtoSWP(swc_wp, swrc_type, swrcp, gravel, width, em); EXPECT_NEAR(phi, 15., tol9); // if swc < wilting point, then we expect phi > 15 bar - swcBulk = SWRC_SWPtoSWC(2. * 15., swrc_type, swrcp, gravel, width); - phi = SWRC_SWCtoSWP(swcBulk, swrc_type, swrcp, gravel, width); + swcBulk = SWRC_SWPtoSWC(2. * 15., swrc_type, swrcp, gravel, width, em); + phi = SWRC_SWCtoSWP(swcBulk, swrc_type, swrcp, gravel, width, em); EXPECT_GT(phi, 15.); //------ Tests SWP -> SWC // when fractionGravel is 1, we expect theta == 0 EXPECT_EQ( - SWRC_SWPtoSWC(15., swrc_type, swrcp, 1., width), + SWRC_SWPtoSWC(15., swrc_type, swrcp, 1., width, em), 0. ); // when width is 0, we expect theta == 0 EXPECT_EQ( - SWRC_SWPtoSWC(15., swrc_type, swrcp, gravel, 0.), + SWRC_SWPtoSWC(15., swrc_type, swrcp, gravel, 0., em), 0. ); // check bounds of swc - swcBulk = SWRC_SWPtoSWC(15., swrc_type, swrcp, gravel, width); + swcBulk = SWRC_SWPtoSWC(15., swrc_type, swrcp, gravel, width, em); EXPECT_GE(swcBulk, 0.); EXPECT_LE(swcBulk, width * (1. - gravel)); } @@ -236,38 +236,67 @@ namespace{ unsigned int swrc_type; - //--- we expect fatal errors in a few situations + //--- 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), + SWRC_SWCtoSWP(1., swrc_type, swrcp, gravel, width, LOGFATAL), "@ generic.c LogError" ); + EXPECT_EQ( + SWRC_SWCtoSWP(1., swrc_type, swrcp, gravel, width, LOGWARN), + SW_MISSING + ); // --- 2) swc < 0: water content cannot be missing, zero or negative swrc_type = 0; // any SWRC EXPECT_DEATH_IF_SUPPORTED( - SWRC_SWCtoSWP(SW_MISSING, swrc_type, swrcp, gravel, width), + SWRC_SWCtoSWP(SW_MISSING, swrc_type, swrcp, gravel, width, LOGFATAL), "@ generic.c LogError" ); + EXPECT_EQ( + SWRC_SWCtoSWP(SW_MISSING, swrc_type, swrcp, gravel, width, LOGWARN), + SW_MISSING + ); + EXPECT_DEATH_IF_SUPPORTED( - SWRC_SWCtoSWP(-1., swrc_type, swrcp, gravel, width), + SWRC_SWCtoSWP(-1., swrc_type, swrcp, gravel, width, LOGFATAL), "@ generic.c LogError" ); + EXPECT_EQ( + SWRC_SWCtoSWP(-1., swrc_type, swrcp, gravel, width, LOGWARN), + SW_MISSING + ); + EXPECT_DEATH_IF_SUPPORTED( - SWRC_SWCtoSWP(0., swrc_type, swrcp, gravel, width), + SWRC_SWCtoSWP(0., swrc_type, swrcp, gravel, width, LOGFATAL), "@ generic.c LogError" ); + EXPECT_EQ( + SWRC_SWCtoSWP(0., swrc_type, swrcp, gravel, width, LOGWARN), + SW_MISSING + ); + EXPECT_DEATH_IF_SUPPORTED( - SWRC_SWCtoSWP(1., swrc_type, swrcp, 1., width), + SWRC_SWCtoSWP(1., swrc_type, swrcp, 1., width, LOGFATAL), "@ generic.c LogError" ); + EXPECT_EQ( + SWRC_SWCtoSWP(1., swrc_type, swrcp, 1., width, LOGWARN), + SW_MISSING + ); + EXPECT_DEATH_IF_SUPPORTED( - SWRC_SWCtoSWP(1., swrc_type, swrcp, gravel, 0.), + SWRC_SWCtoSWP(1., swrc_type, swrcp, gravel, 0., LOGFATAL), "@ generic.c LogError" ); + EXPECT_EQ( + SWRC_SWCtoSWP(1., swrc_type, swrcp, gravel, 0., LOGWARN), + SW_MISSING + ); // --- 3) if theta_sat == 0 (specific to Campbell1974) @@ -279,9 +308,13 @@ namespace{ swrcp[1] = 0.; // instead of 0.4436 EXPECT_DEATH_IF_SUPPORTED( - SWRC_SWCtoSWP(5., swrc_type, swrcp, gravel, width), + SWRC_SWCtoSWP(5., swrc_type, swrcp, gravel, width, LOGFATAL), "@ generic.c LogError" ); + EXPECT_EQ( + SWRC_SWCtoSWP(5., swrc_type, swrcp, gravel, width, LOGWARN), + SW_MISSING + ); // --- 4) if (theta - theta_res) <= 0 (specific to vanGenuchten1980) @@ -294,9 +327,13 @@ namespace{ swrcp[3] = 1.2673; EXPECT_DEATH_IF_SUPPORTED( - SWRC_SWCtoSWP(0.99 * swrcp[0], swrc_type, swrcp, gravel, width), + SWRC_SWCtoSWP(0.99 * swrcp[0], swrc_type, swrcp, gravel, width, LOGFATAL), "@ generic.c LogError" ); + EXPECT_EQ( + SWRC_SWCtoSWP(0.99 * swrcp[0], swrc_type, swrcp, gravel, width, LOGWARN), + SW_MISSING + ); } @@ -311,24 +348,38 @@ namespace{ unsigned int swrc_type; - //--- we expect fatal errors in two situations + //--- 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(0., swrc_type, swrcp, gravel, width), + SWRC_SWPtoSWC(0., swrc_type, swrcp, gravel, width, LOGFATAL), "@ generic.c LogError" ); + EXPECT_EQ( + SWRC_SWPtoSWC(0., swrc_type, swrcp, gravel, width, LOGWARN), + SW_MISSING + ); // --- 2) swp <= 0: water content cannot be zero or negative (any SWRC) swrc_type = 0; EXPECT_DEATH_IF_SUPPORTED( - SWRC_SWPtoSWC(-1., swrc_type, swrcp, gravel, width), + SWRC_SWPtoSWC(-1., swrc_type, swrcp, gravel, width, LOGFATAL), "@ generic.c LogError" ); + EXPECT_EQ( + SWRC_SWPtoSWC(-1., swrc_type, swrcp, gravel, width, LOGWARN), + SW_MISSING + ); + EXPECT_DEATH_IF_SUPPORTED( - SWRC_SWPtoSWC(0., swrc_type, swrcp, gravel, width), + SWRC_SWPtoSWC(0., swrc_type, swrcp, gravel, width, LOGFATAL), "@ generic.c LogError" ); + EXPECT_EQ( + SWRC_SWPtoSWC(0., swrc_type, swrcp, gravel, width, LOGWARN), + SW_MISSING + ); } } From 5ee0687bbe8919cae6836063b3795bbd9b29210b Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 2 Mar 2022 08:06:11 -0500 Subject: [PATCH 023/326] Saturated SWC consistent with selected SWRC - previously, SOILWAT2 used the soil water retention curve by Campbell 1974 including an estimate of theta_sat (= saturated volumetric water content) by pedotransfer functions by Cosby et al. 1984 -- yet for purposes of determining saturated conditions (outside of SWRC) utilized a different estimate of theta_sat, the one estimated by pedotransfer functions by Saxton et al. 2006 - otherwise, SOILWAT2 now uses consistently theta_sat as estimated by the selected PDF (or provided as input via `swrc_params.in` depending on the selected SWRC) -> new `SWRC_PDF_swcBulk_saturated()` delivers the correct `theta_sat` (converted to bulk soil) depending on the selected `SWRC` (and in case of `pdf_name = "Cosby1984AndOthers"` reproduces previous (legacy) behavior) -> `PDF_Saxton2006()` now returns `theta_sat` (instead of `theta_sat` converted to bulk soil) --- SW_Site.c | 113 ++++++++++++++++++++++++++++++++++++++++++------------ SW_Site.h | 14 ++++++- 2 files changed, 101 insertions(+), 26 deletions(-) diff --git a/SW_Site.c b/SW_Site.c index 21aaf10a0..81a2ac620 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -292,7 +292,7 @@ unsigned int encode_str2pdf(char *pdf_name) { @brief Estimate parameters of selected soil water retention curve (SWRC) using selected pedotransfer function (PDF) - See `pdf2str` for implemented PDFs. + See #pdf2str for implemented PDFs. @param[in] pdf_type Identification number of selected PDF @param[out] *swrcp Vector of SWRC parameters to be estimated @@ -382,6 +382,76 @@ void SWRC_PDF_Cosby1984_for_Campbell1974( } +/** + @brief Saturated soil water content + + See #pdf2str for implemented PDFs. + 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 + `pdf_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) PDF instead; + `pdf_name` of "Cosby1984" will return saturated soil water content estimated + by Cosby et al. 1984 (\cite Cosby1984) PDF. + + The arguments `pdf_type`, `sand`, and `clay` are utilized only if + `pdf_name` is "Cosby1984AndOthers" (see #pdf2str). + + @param[in] swrc_type Identification number of selected SWRC + @param[out] *swrcp Vector of SWRC parameters to be estimated + @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] pdf_type Identification number of selected PDF + @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 [cm] +*/ +double SWRC_PDF_swcBulk_saturated( + unsigned int swrc_type, + double *swrcp, + double gravel, + double width, + unsigned int pdf_type, + double sand, + double clay +) { + double theta_sat = SW_MISSING; + + switch (swrc_type) { + case 0: // Campbell1974 + if (pdf_type == 1) { + // Cosby1984AndOthers + PDF_Saxton2006(&theta_sat, sand, clay); + } else { + // Cosby1984 + theta_sat = swrcp[1]; + } + break; + + case 1: // vanGenuchten1980 + theta_sat = swrcp[1]; + break; + + default: + LogError( + logfp, + LOGFATAL, + "`SWRC_PDF_swcBulk_saturated()`: SWRC (type %d) is not implemented.", + swrc_type + ); + break; + } + + // Convert from [cm/cm] to [cm] + return theta_sat * width * (1. - gravel); +} + /** @brief Check whether selected PDF and SWRC are compatible @@ -601,23 +671,16 @@ Bool SWRC_check_parameters_for_vanGenuchten1980(double *swrcp) { @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] width Soil layer width [cm] - @param[out] *swc_sat Saturated water content [cm] to be estimated + @param[out] *theta_sat Estimated saturated volumetric water content [cm/cm] */ void PDF_Saxton2006( - double *swc_sat, - double sand, double clay, double gravel, double width + double *theta_sat, + double sand, + double clay ) { - /* 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. - */ - double OM = 0., - theta_S, theta_33, theta_33t, theta_S33, theta_S33t; + theta_33, theta_33t, theta_S33, theta_S33t; /* Eq. 2: 33 kPa moisture */ theta_33t = @@ -646,22 +709,21 @@ void PDF_Saxton2006( /* 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, "PDF_Saxton2006(): invalid value of " "theta(saturated, [cm / cm]) = %f (must be within 0-1)\n", - theta_S + *theta_sat ); } - *swc_sat = width * (1. - gravel) * theta_S; // currently, unused and defunct code: @@ -1577,13 +1639,16 @@ void SW_SIT_init_run(void) { SW_SWRC_SWPtoSWC(100., lyr) ); - /* Estimate additional properties */ - PDF_Saxton2006( - &(lyr->swcBulk_saturated), - lyr->fractionWeightMatric_sand, - lyr->fractionWeightMatric_clay, + + /* Extract or estimate additional properties */ + lyr->swcBulk_saturated = SWRC_PDF_swcBulk_saturated( + lyr->swrc_type, + lyr->swrcp, lyr->fractionVolBulk_gravel, - lyr->width + lyr->width, + lyr->pdf_type, + lyr->fractionWeightMatric_sand, + lyr->fractionWeightMatric_clay ); diff --git a/SW_Site.h b/SW_Site.h index e697cc328..9c03d900f 100644 --- a/SW_Site.h +++ b/SW_Site.h @@ -226,9 +226,19 @@ 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); +double SWRC_PDF_swcBulk_saturated( + unsigned int swrc_type, + double *swrcp, + double gravel, + double width, + unsigned int pdf_type, + double sand, + double clay +); void PDF_Saxton2006( - double *swc_sat, - double sand, double clay, double gravel, double width + double *theta_sat, + double sand, + double clay ); RealD calculate_soilBulkDensity(RealD matricDensity, RealD fractionGravel); From dd7b9f263f30ba29ca911dcff9d1a53bda3bf5d8 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 2 Mar 2022 09:08:25 -0500 Subject: [PATCH 024/326] Make unit tests for SWPtoSWC and SWCtoSWP safer - expectations of SWRC output to be `SW_MISSING` used `EXPECT_EQ()` which tests for exact equality --> `SW_MISSING`, however, is a floating point number --> use `EXPECT_DOULBE_EQ()` instead - this improves commit 0f6ac5d6757cfb3a284163fa486cf2e26851b4ec --- test/test_SW_SoilWater.cc | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/test/test_SW_SoilWater.cc b/test/test_SW_SoilWater.cc index 141130da1..9baaff6dc 100644 --- a/test/test_SW_SoilWater.cc +++ b/test/test_SW_SoilWater.cc @@ -245,7 +245,7 @@ namespace{ SWRC_SWCtoSWP(1., swrc_type, swrcp, gravel, width, LOGFATAL), "@ generic.c LogError" ); - EXPECT_EQ( + EXPECT_DOUBLE_EQ( SWRC_SWCtoSWP(1., swrc_type, swrcp, gravel, width, LOGWARN), SW_MISSING ); @@ -257,7 +257,7 @@ namespace{ SWRC_SWCtoSWP(SW_MISSING, swrc_type, swrcp, gravel, width, LOGFATAL), "@ generic.c LogError" ); - EXPECT_EQ( + EXPECT_DOUBLE_EQ( SWRC_SWCtoSWP(SW_MISSING, swrc_type, swrcp, gravel, width, LOGWARN), SW_MISSING ); @@ -266,7 +266,7 @@ namespace{ SWRC_SWCtoSWP(-1., swrc_type, swrcp, gravel, width, LOGFATAL), "@ generic.c LogError" ); - EXPECT_EQ( + EXPECT_DOUBLE_EQ( SWRC_SWCtoSWP(-1., swrc_type, swrcp, gravel, width, LOGWARN), SW_MISSING ); @@ -275,7 +275,7 @@ namespace{ SWRC_SWCtoSWP(0., swrc_type, swrcp, gravel, width, LOGFATAL), "@ generic.c LogError" ); - EXPECT_EQ( + EXPECT_DOUBLE_EQ( SWRC_SWCtoSWP(0., swrc_type, swrcp, gravel, width, LOGWARN), SW_MISSING ); @@ -284,7 +284,7 @@ namespace{ SWRC_SWCtoSWP(1., swrc_type, swrcp, 1., width, LOGFATAL), "@ generic.c LogError" ); - EXPECT_EQ( + EXPECT_DOUBLE_EQ( SWRC_SWCtoSWP(1., swrc_type, swrcp, 1., width, LOGWARN), SW_MISSING ); @@ -293,7 +293,7 @@ namespace{ SWRC_SWCtoSWP(1., swrc_type, swrcp, gravel, 0., LOGFATAL), "@ generic.c LogError" ); - EXPECT_EQ( + EXPECT_DOUBLE_EQ( SWRC_SWCtoSWP(1., swrc_type, swrcp, gravel, 0., LOGWARN), SW_MISSING ); @@ -311,7 +311,7 @@ namespace{ SWRC_SWCtoSWP(5., swrc_type, swrcp, gravel, width, LOGFATAL), "@ generic.c LogError" ); - EXPECT_EQ( + EXPECT_DOUBLE_EQ( SWRC_SWCtoSWP(5., swrc_type, swrcp, gravel, width, LOGWARN), SW_MISSING ); @@ -330,7 +330,7 @@ namespace{ SWRC_SWCtoSWP(0.99 * swrcp[0], swrc_type, swrcp, gravel, width, LOGFATAL), "@ generic.c LogError" ); - EXPECT_EQ( + EXPECT_DOUBLE_EQ( SWRC_SWCtoSWP(0.99 * swrcp[0], swrc_type, swrcp, gravel, width, LOGWARN), SW_MISSING ); @@ -357,7 +357,7 @@ namespace{ SWRC_SWPtoSWC(0., swrc_type, swrcp, gravel, width, LOGFATAL), "@ generic.c LogError" ); - EXPECT_EQ( + EXPECT_DOUBLE_EQ( SWRC_SWPtoSWC(0., swrc_type, swrcp, gravel, width, LOGWARN), SW_MISSING ); @@ -368,7 +368,7 @@ namespace{ SWRC_SWPtoSWC(-1., swrc_type, swrcp, gravel, width, LOGFATAL), "@ generic.c LogError" ); - EXPECT_EQ( + EXPECT_DOUBLE_EQ( SWRC_SWPtoSWC(-1., swrc_type, swrcp, gravel, width, LOGWARN), SW_MISSING ); @@ -377,7 +377,7 @@ namespace{ SWRC_SWPtoSWC(0., swrc_type, swrcp, gravel, width, LOGFATAL), "@ generic.c LogError" ); - EXPECT_EQ( + EXPECT_DOUBLE_EQ( SWRC_SWPtoSWC(0., swrc_type, swrcp, gravel, width, LOGWARN), SW_MISSING ); From 803e671e7388dd399273847726d866cf62aa2652 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 2 Mar 2022 09:18:17 -0500 Subject: [PATCH 025/326] Rename `SW_VWCBulkRes()` to `PDF_RawlsBrakensiek1985()` - rename to make underlying PDF explicit in name of function, similar to `PDF_Saxton2006()` - `PDF_RawlsBrakensiek1985()` now returns `theta_min` instead of `theta_min * (1 - gravel)` --> `gravel` is no longer required as argument --> make interface similar to that of `PDF_Saxton2006()` --> update unit tests --> update function documentation --> new limits for argument `porosity` which is required to be >= 10% (based on the shape of the equation) and < 100% --- SW_Site.c | 69 ++++++++++++++++++++++++++++++++++++--- SW_Site.h | 7 ++++ SW_SoilWater.c | 51 ----------------------------- SW_SoilWater.h | 1 - test/test_SW_Site.cc | 54 ++++++++++++++++++++++++++++++ test/test_SW_SoilWater.cc | 47 -------------------------- 6 files changed, 126 insertions(+), 103 deletions(-) diff --git a/SW_Site.c b/SW_Site.c index 81a2ac620..322ced615 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -783,6 +783,64 @@ void PDF_Saxton2006( } + + +/** + @brief Rawls and Brakensiek 1985 PDFs \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 [cm/cm] +*/ +void PDF_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, + "`PDF_RawlsBrakensiek1985()`: sand, clay, or porosity out of valid range." + ); + + *theta_min = SW_MISSING; + } +} + + + /** @brief Estimate soil density of the whole soil (bulk). @@ -1659,9 +1717,9 @@ void SW_SIT_init_run(void) { if (LT(_SWCMinVal, 0.0)) { /* input: estimate mininum SWC */ - /* residual SWC of Rawls & Brakensiek (1985) */ - swcmin_help1 = SW_VWCBulkRes( - lyr->fractionVolBulk_gravel, + /* residual VWC of Rawls & Brakensiek (1985) */ + PDF_RawlsBrakensiek1985( + &swcmin_help1, lyr->fractionWeightMatric_sand, lyr->fractionWeightMatric_clay, lyr->swcBulk_saturated / ((1. - lyr->fractionVolBulk_gravel) * lyr->width) @@ -1683,7 +1741,10 @@ void SW_SIT_init_run(void) { lyr->swcBulk_min = swcmin_help2; } else { - lyr->swcBulk_min = fmax(swcmin_help1, swcmin_help2); + lyr->swcBulk_min = fmax( + swcmin_help1 * (1. - lyr->fractionVolBulk_gravel), + swcmin_help2 + ); } } else if (GE(_SWCMinVal, 1.0)) { diff --git a/SW_Site.h b/SW_Site.h index 9c03d900f..7cd8bc0af 100644 --- a/SW_Site.h +++ b/SW_Site.h @@ -240,6 +240,13 @@ void PDF_Saxton2006( double sand, double clay ); +void PDF_RawlsBrakensiek1985( + double *theta_min, + double sand, + double clay, + double porosity +); + RealD calculate_soilBulkDensity(RealD matricDensity, RealD fractionGravel); LyrIndex nlayers_bsevap(void); diff --git a/SW_SoilWater.c b/SW_SoilWater.c index f59920aa6..390bcdcfc 100644 --- a/SW_SoilWater.c +++ b/SW_SoilWater.c @@ -1393,57 +1393,6 @@ double SWRC_SWPtoSWC_vanGenuchten1980( } -/** -@brief Calculates 'Brooks-Corey' residual volumetric soil water. - -Equations based on: Rawls WJ, Brakensiek DL (1985) Prediction of soil water properties - for hydrological modeling, based on @cite ASCE1985 - -@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. - -@returns Residual volumetric soil water (cm/cm) -**/ - -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) - ----------------------*/ - - 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; - - } 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.)); - } -} - /** @brief This routine sets the known memory refs in this module so they can be checked for leaks, etc. diff --git a/SW_SoilWater.h b/SW_SoilWater.h index bac7e3131..480184418 100644 --- a/SW_SoilWater.h +++ b/SW_SoilWater.h @@ -157,7 +157,6 @@ 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_VWCBulkRes(RealD fractionGravel, RealD sand, RealD clay, RealD porosity); void get_dSWAbulk(int i); double SW_SWRC_SWCtoSWP(double swcBulk, SW_LAYER_INFO *lyr); diff --git a/test/test_SW_Site.cc b/test/test_SW_Site.cc index c3c7390c1..eca493525 100644 --- a/test/test_SW_Site.cc +++ b/test/test_SW_Site.cc @@ -263,6 +263,60 @@ namespace { } + // Test 'PDF_RawlsBrakensiek1985' + TEST(SiteTest, PDFRawlsBrakensiek1985) { + //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[ + PDF_RawlsBrakensiek1985(&theta_min, 0., clay, porosity); + EXPECT_DOUBLE_EQ(theta_min, SW_MISSING); + + PDF_RawlsBrakensiek1985(&theta_min, 0.75, clay, porosity); + EXPECT_DOUBLE_EQ(theta_min, SW_MISSING); + + PDF_RawlsBrakensiek1985(&theta_min, sand, 0., porosity); + EXPECT_DOUBLE_EQ(theta_min, SW_MISSING); + + PDF_RawlsBrakensiek1985(&theta_min, sand, 0.65, porosity); + EXPECT_DOUBLE_EQ(theta_min, SW_MISSING); + + PDF_RawlsBrakensiek1985(&theta_min, sand, clay, 0.); + EXPECT_DOUBLE_EQ(theta_min, SW_MISSING); + + PDF_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); + + PDF_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 + PDF_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; diff --git a/test/test_SW_SoilWater.cc b/test/test_SW_SoilWater.cc index 9baaff6dc..12ddbec9a 100644 --- a/test/test_SW_SoilWater.cc +++ b/test/test_SW_SoilWater.cc @@ -20,53 +20,6 @@ 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, SWCadjustSnow){ // setup mock variables From ec3271b73771786b04dfe81945798bc26cc4aa66 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 2 Mar 2022 11:52:34 -0500 Subject: [PATCH 026/326] Minimum/residual SWC consistent with selected SWRC - previously, SOILWAT2 used the soil water retention curve by Campbell 1974 -- yet, minimum soil moisture conditions were based on user input and/or realistic minimum values and/or on an estimate of theta_min by Rawls & Brakensiek 1985 - SOILWAT2 now uses a theta_min that is consistent with the selected SWRC, i.e., estimated by the selected PDF (or provided as input via `swrc_params.in` depending on the selected SWRC) -> new `SWRC_PDF_swcBulk_minimum()` delivers the correct `theta_min` (converted to bulk soil) depending on the selected `SWRC` (and in case of `pdf_name = "Cosby1984AndOthers"` reproduces previous (legacy) behavior) -> new `legacy_theta_min()` reproduces previous behavior (code taken from `SW_SIT_init_run()`) -> new `lower_limit_of_theta_min()` returns a "realistic" lower estimate of `theta_min` based on selected SWRC at a fixed tension --- SW_Site.c | 273 +++++++++++++++++++++++++++++++++++++---------- SW_Site.h | 11 ++ doc/SOILWAT2.bib | 31 ++++-- 3 files changed, 247 insertions(+), 68 deletions(-) diff --git a/SW_Site.c b/SW_Site.c index 322ced615..f14336472 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -225,6 +225,107 @@ static Bool SW_check_soil_properties(SW_LAYER_INFO *lyr) { +/** Lower realistic limit for minimum `theta` + Notes: + - 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 Legacy minimum/residual volumetric water content + + This function returns minimum soil water content determined + by user input via #_SWCMinVal and/or + by the pedotransfer function of Rawls & Brakensiek 1985 \cite rawls1985WmitE + (independent of the selected SWRC), and/or + by an estimate of a realistic lower limit. + + This reproduces legacy behavior of SOILWAT2 prior to v7.0.0. + + @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 + + @return Estimated minimum volumetric water content of the matric soil [cm / cm] +*/ +static double legacy_theta_min( + double ui_sm_min, + double gravel, + double width, + double sand, + double clay, + double swcBulk_sat, + unsigned int swrc_type, + double *swrcp +) { + double vwc_min = SW_MISSING, vwcmin1, vwcmin2; + + if (LT(ui_sm_min, 0.0)) { + /* input: do estimate minimum theta */ + + /* residual theta estimated with Rawls & Brakensiek (1985) PDF*/ + PDF_RawlsBrakensiek1985( + &vwcmin1, + sand, + clay, + swcBulk_sat / ((1. - gravel) * width) + ); + + /* Lower realistic limit for theta_min + - used in case `PDF_RawlsBrakensiek1985` doesn't work or + produces unrealistic small values + */ + vwcmin2 = lower_limit_of_theta_min(swrc_type, swrcp, gravel, width); + + // if `PDF_RawlsBrakensiek1985()` returns SW_MISSING then use `swcmin_help2` + if (missing(vwcmin1)) { + vwc_min = vwcmin2; + + } else { + vwc_min = fmax(vwcmin1, vwcmin2); + } + + } else if (GE(_SWCMinVal, 1.0)) { + /* input: fixed (matric) SWP value; unit(_SWCMinVal) == -bar */ + vwc_min = SWRC_SWPtoSWC( + _SWCMinVal, + swrc_type, + swrcp, + gravel, + width, + LOGFATAL + ) / ((1. - gravel) * width); + + } else { + /* input: fixed matric VWC; unit(_SWCMinVal) == cm/cm */ + vwc_min = _SWCMinVal / width; + } + + return vwc_min; +} + + + /* =================================================== */ /* Global Function Definitions */ /* --------------------------------------------------- */ @@ -402,7 +503,7 @@ void SWRC_PDF_Cosby1984_for_Campbell1974( `pdf_name` is "Cosby1984AndOthers" (see #pdf2str). @param[in] swrc_type Identification number of selected SWRC - @param[out] *swrcp Vector of SWRC parameters to be estimated + @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] @@ -410,7 +511,7 @@ void SWRC_PDF_Cosby1984_for_Campbell1974( @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 [cm] + @return Estimated saturated water content of the bulk soil [cm] */ double SWRC_PDF_swcBulk_saturated( unsigned int swrc_type, @@ -448,10 +549,106 @@ double SWRC_PDF_swcBulk_saturated( break; } - // Convert from [cm/cm] to [cm] + // Convert from matric [cm/cm] to bulk [cm] return theta_sat * width * (1. - gravel); } + +/** + @brief Minimum/residual soil water content + + See #pdf2str for implemented PDFs. + See #swrc2str for implemented SWRCs. + + Minimum/residual 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 + `pdf_name` of "Cosby1984AndOthers" will reproduce `SOILWAT2` legacy mode + (`SOILWAT2` prior to v7.0.0) and return minimum soil water content determined + by the user #_SWCMinVal and/or by Rawls & Brakensiek 1985 PDF, + see `legacy_theta_min()`; + `pdf_name` of "Cosby1984" will return zero as minimum soil water content. + + The arguments `_SWCMinVal`, `sand`, `clay`, and `swcBulk_sat` + are utilized only if `pdf_name` is "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] pdf_type Identification number of selected PDF + @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 Estimated minimum water content of the bulk soil [cm] +*/ +double SWRC_PDF_swcBulk_minimum( + unsigned int swrc_type, + double *swrcp, + double gravel, + double width, + unsigned int pdf_type, + double ui_sm_min, + double sand, + double clay, + double swcBulk_sat +) { + double theta_min = SW_MISSING; + + switch (swrc_type) { + case 0: // Campbell1974 + if (pdf_type == 1) { + // Cosby1984AndOthers + theta_min = legacy_theta_min( + ui_sm_min, + gravel, + width, + sand, + clay, + swcBulk_sat, + swrc_type, + swrcp + ); + + } else if (pdf_type == 2) { + // Cosby1984 + theta_min = 0.; // TODO: should this be slightly larger than 0? e.g., -30 MPa? + + } else { + LogError( + logfp, + LOGFATAL, + "`SWRC_PDF_swcBulk_minimum()`: SWRC (type %d) is not implemented.", + swrc_type + ); + } + break; + + case 1: // vanGenuchten1980 + theta_min = swrcp[0]; + break; + + default: + LogError( + logfp, + LOGFATAL, + "`SWRC_PDF_swcBulk_minimum()`: SWRC (type %d) is not implemented.", + swrc_type + ); + break; + } + + // Convert from matric [cm/cm] to bulk [cm] + return theta_min * width * (1. - gravel); +} + + + /** @brief Check whether selected PDF and SWRC are compatible @@ -671,7 +868,8 @@ Bool SWRC_check_parameters_for_vanGenuchten1980(double *swrcp) { @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 [cm/cm] + @param[out] *theta_sat Estimated saturated volumetric water content + of the matric soil [cm/cm] */ void PDF_Saxton2006( double *theta_sat, @@ -784,7 +982,6 @@ void PDF_Saxton2006( - /** @brief Rawls and Brakensiek 1985 PDFs \cite rawls1985WmitE to estimate residual soil water content for the Brooks-Corey SWRC @@ -797,7 +994,8 @@ void PDF_Saxton2006( @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 [cm/cm] + @param[out] *theta_min Estimated residual volumetric water content + of the matric soil [cm/cm] */ void PDF_RawlsBrakensiek1985( double *theta_min, @@ -1592,8 +1790,7 @@ void SW_SIT_init_run(void) { LyrIndex s, r, curregion; int k, flagswpcrit = 0; RealD - evsum = 0., trsum_veg[NVEGTYPES] = {0.}, - swcmin_help1, swcmin_help2, tmp; + evsum = 0., trsum_veg[NVEGTYPES] = {0.}, tmp; #ifdef SWDEBUG int debug = 0; @@ -1709,55 +1906,17 @@ void SW_SIT_init_run(void) { lyr->fractionWeightMatric_clay ); - - - - - /* Compute swc wet and dry limits and init value */ - if (LT(_SWCMinVal, 0.0)) { - /* input: estimate mininum SWC */ - - /* residual VWC of Rawls & Brakensiek (1985) */ - PDF_RawlsBrakensiek1985( - &swcmin_help1, - 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_SWRC_SWPtoSWC(300., lyr) / lyr->width; - - // 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 * (1. - lyr->fractionVolBulk_gravel), - swcmin_help2 - ); - } - - } else if (GE(_SWCMinVal, 1.0)) { - /* input: fixed SWP value as minimum SWC; unit(_SWCMinVal) == -bar */ - lyr->swcBulk_min = SW_SWRC_SWPtoSWC(_SWCMinVal, lyr) / lyr->width; - - } else { - /* input: fixed VWC value as minimum SWC; unit(_SWCMinVal) == cm/cm */ - lyr->swcBulk_min = _SWCMinVal; - } - - /* Convert VWC to SWC */ - lyr->swcBulk_min *= lyr->width; + lyr->swcBulk_min = SWRC_PDF_swcBulk_minimum( + lyr->swrc_type, + lyr->swrcp, + lyr->fractionVolBulk_gravel, + lyr->width, + lyr->pdf_type, + _SWCMinVal, + lyr->fractionWeightMatric_sand, + lyr->fractionWeightMatric_clay, + lyr->swcBulk_saturated + ); /* Calculate wet limit of SWC for what inputs defined as wet */ diff --git a/SW_Site.h b/SW_Site.h index 7cd8bc0af..02e7bd194 100644 --- a/SW_Site.h +++ b/SW_Site.h @@ -235,6 +235,17 @@ double SWRC_PDF_swcBulk_saturated( double sand, double clay ); +double SWRC_PDF_swcBulk_minimum( + unsigned int swrc_type, + double *swrcp, + double gravel, + double width, + unsigned int pdf_type, + double ui_sm_min, + double sand, + double clay, + double swcBulk_sat +); void PDF_Saxton2006( double *theta_sat, double sand, diff --git a/doc/SOILWAT2.bib b/doc/SOILWAT2.bib index 8051f3823..9d9760ea7 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, @@ -364,3 +354,22 @@ @article{zhang2017JoH 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} +} From b9c6cc38c15dc2db8d46a5bf1a0737e5aa543fb7 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 2 Mar 2022 15:01:27 -0500 Subject: [PATCH 027/326] More informative error messages for `SWRC_SWCtoSWP_XXX()` --- SW_SoilWater.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/SW_SoilWater.c b/SW_SoilWater.c index 390bcdcfc..7be47bfd0 100644 --- a/SW_SoilWater.c +++ b/SW_SoilWater.c @@ -1149,13 +1149,14 @@ double SWRC_SWCtoSWP_Campbell1974( // calculate (theta / theta_s) ^ b tmp = powe(theta / swrcp[1], swrcp[2]); - if (!isfinite(tmp) || ZRO(tmp)) { + if (!isfinite(tmp) || LE(tmp, 0.)) { LogError( logfp, errmode, - "SWRC_SWCtoSWP_Campbell1974(): " - "invalid value of (theta / theta(saturated)) ^ b = %f (must be != 0)\n", - tmp + "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; @@ -1204,9 +1205,10 @@ double SWRC_SWCtoSWP_vanGenuchten1980( LogError( logfp, errmode, - "SWRC_SWCtoSWP_vanGenuchten1980(): " - "invalid value of (theta - theta(residual)) = %f (must be > 0)\n", - tmp + "SWRC_SWCtoSWP_vanGenuchten1980(): invalid value of\n" + "\t(theta - theta(residual)) = %f - %f =\n" + "\t= %f (must be > 0)\n", + theta, swrcp[0], tmp ); return SW_MISSING; From 2db03e92945f2628245db6e422aaa71378cd4910 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 2 Mar 2022 16:42:34 -0500 Subject: [PATCH 028/326] Updated estimate of `theta_min` if Campbell1974/Cosby1984 - previous commit set theta_min to 0 if Campbell1974/Cosby1984; this, however, leads to phi -> Infinity (and code breaks before that) -> we need `theta_min` > 0 so that floating point arithmetic does not break and for phi staying in a realistic range options: - fixed realistic value based on a phi_min by `lower_limit_of_theta_min()` - fixed (unrealistic) value based on floating point delta `powe(D_DELTA, 1. / swrcp[2]) * swrcp[1]` - use previous behavior, i.e., `legacy_theta_min()` --> `legacy_theta_min()` seems to be the best alternative: (i) realistic range of values, (ii) possibly sensitive to soil properties, and (iii) allows user input (e.g., for experiments) --- SW_Site.c | 42 ++++++++++++++++++++++---------------- testing/Input/siteparam.in | 6 ++++-- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/SW_Site.c b/SW_Site.c index f14336472..effa122b5 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -251,7 +251,7 @@ static double lower_limit_of_theta_min( by user input via #_SWCMinVal and/or by the pedotransfer function of Rawls & Brakensiek 1985 \cite rawls1985WmitE (independent of the selected SWRC), and/or - by an estimate of a realistic lower limit. + by an estimate of a realistic lower limit (see `lower_limit_of_theta_min()`). This reproduces legacy behavior of SOILWAT2 prior to v7.0.0. @@ -297,7 +297,7 @@ static double legacy_theta_min( */ vwcmin2 = lower_limit_of_theta_min(swrc_type, swrcp, gravel, width); - // if `PDF_RawlsBrakensiek1985()` returns SW_MISSING then use `swcmin_help2` + // if `PDF_RawlsBrakensiek1985()` returns SW_MISSING then use `vwcmin2` if (missing(vwcmin1)) { vwc_min = vwcmin2; @@ -563,15 +563,15 @@ double SWRC_PDF_swcBulk_saturated( Minimum/residual 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 - `pdf_name` of "Cosby1984AndOthers" will reproduce `SOILWAT2` legacy mode - (`SOILWAT2` prior to v7.0.0) and return minimum soil water content determined - by the user #_SWCMinVal and/or by Rawls & Brakensiek 1985 PDF, - see `legacy_theta_min()`; - `pdf_name` of "Cosby1984" will return zero as minimum soil water content. + If `swrc_name` is "Campbell1974", then + `theta_min` should be zero which would, however, lead to infinite + soil water potentials. Instead, `theta_min` is determined + - by user input via #_SWCMinVal and + - by the pedotransfer function of Rawls & Brakensiek 1985 \cite rawls1985WmitE + - by an estimate of a realistic lower limit - The arguments `_SWCMinVal`, `sand`, `clay`, and `swcBulk_sat` - are utilized only if `pdf_name` is "Cosby1984AndOthers". + Thus, the arguments `_SWCMinVal`, `sand`, `clay`, and `swcBulk_sat` + are utilized only if `swrc_name` is "Campbell1974". @param[in] swrc_type Identification number of selected SWRC @param[in] *swrcp Vector of SWRC parameters @@ -601,9 +601,10 @@ double SWRC_PDF_swcBulk_minimum( double theta_min = SW_MISSING; switch (swrc_type) { - case 0: // Campbell1974 - if (pdf_type == 1) { - // Cosby1984AndOthers + case 0: // Campbell1974: does not define a `theta_min` + if (pdf_type == 1 || pdf_type == 2) { + // Cosby1984AndOthers/Cosby1984: we cannot set theta_min = 0 because phi -> infinity + // -> `legacy_theta_min()` theta_min = legacy_theta_min( ui_sm_min, gravel, @@ -615,16 +616,21 @@ double SWRC_PDF_swcBulk_minimum( swrcp ); - } else if (pdf_type == 2) { - // Cosby1984 - theta_min = 0.; // TODO: should this be slightly larger than 0? e.g., -30 MPa? + // Possible alternatives to `legacy_theta_min()` + /* theta_min defined via realistic phi_min */ + // theta_min = lower_limit_of_theta_min(swrc_type, swrcp, gravel, width); + + /* theta_min so that `SWRC_SWCtoSWP_Campbell1974()` does just not explode but SWP > 1e13 bar */ + // theta_min = powe(D_DELTA, 1. / swrcp[2]) * swrcp[1]; + } else { LogError( logfp, LOGFATAL, - "`SWRC_PDF_swcBulk_minimum()`: SWRC (type %d) is not implemented.", - swrc_type + "`SWRC_PDF_swcBulk_minimum()`: " + "PDF (type %d) is not implemented for SWRC (type %d).", + pdf_type, swrc_type ); } break; diff --git a/testing/Input/siteparam.in b/testing/Input/siteparam.in index c60318928..be6e2a06f 100644 --- a/testing/Input/siteparam.in +++ b/testing/Input/siteparam.in @@ -91,9 +91,11 @@ RCP85 # input file ("swrc_params.in") instead of estimated from PDF # - swrc_name = "Campbell1974": (Campbell 1974) # * pdf_name = "Cosby1984AndOthers": PDFs by Cosby et al. 1984 -# (but Saxton et al. 2006 for `swc_sat` and Rawls et al. 1985 for `swc_min`) +# (but `swc_sat` by Saxton et al. 2006, +# `swc_min` by user input, realistic SWP limit, or Rawls et al. 1985) # * pdf_name = "Cosby1984": PDFs by Cosby et al. 1984 -# (including `swc_sat` and `swc_min` = 0) +# (including `swc_sat`; `swc_min` = 0 is not realistic, thus, +# `swc_min` by user input, realistic SWP limit, or Rawls et al. 1985) # - swrc_name = "vanGenuchten1980": van Genuchten-Mualem (van Genuchten 1980) # * pdf_name = "Rosetta3": not implemented in C (but see rSOILWAT2) # - swrc_name = "BrooksCorey1964": (Brooks and Corey 1964) From 6c73f5631fa33f4114fa55f5b839c43d13966e49 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Thu, 3 Mar 2022 11:35:55 -0500 Subject: [PATCH 029/326] Additional "testing" SWRC parameter file for vanGenuchten1980 values - values estimated using rSOILWAT2 which calls "Rosetta3" --- testing/Input/swrc_params_vanGenuchten1980.in | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 testing/Input/swrc_params_vanGenuchten1980.in diff --git a/testing/Input/swrc_params_vanGenuchten1980.in b/testing/Input/swrc_params_vanGenuchten1980.in new file mode 100644 index 000000000..21f9ddb35 --- /dev/null +++ b/testing/Input/swrc_params_vanGenuchten1980.in @@ -0,0 +1,28 @@ +#------ 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" +# * param1 = "saturation" soil water matric potential [-bar] +# * param2 = saturated volumetric water content for the matric component [cm/cm] +# * param3 = b, slope of the linear log-log retention curve [-] + +# 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 [-] + +# param1 param2 param3 param4 param5 param6 +0.07564425 0.3925437 0.010035788 1.412233 0 0 +0.10061329 0.4011315 0.009425738 1.352274 0 0 +0.12060752 0.4278807 0.010424896 1.287923 0 0 +0.12336711 0.4393192 0.010807529 1.274654 0 0 +0.12461498 0.4444546 0.011155912 1.267327 0 0 +0.12480807 0.4426857 0.011408809 1.264873 0 0 +0.10129327 0.3878319 0.014241212 1.311722 0 0 +0.10129327 0.3878319 0.014241212 1.311722 0 0 From f06dae5c847914c49351e5a6740bf23aac6b715a Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Thu, 3 Mar 2022 11:51:35 -0500 Subject: [PATCH 030/326] New water balance unit tests using vanGenuchten1980 SWRC --> these tests currently fail: ``` [ RUN ] WaterBalanceTest.WithSWRCvanGenuchten1980 WB (1980-113): aet=1.509554, pet=0.396699 WB (1980-115): aet=1.075805, pet=0.383362 WB (1981-101): aet=2.136892, pet=0.327267 ... test/test_WaterBalance.cc:216: Failure Expected equality of these values: 0 SW_Soilwat.wbError[i] Which is: 204 Water balance error in test 0: AET <= PET ``` --- test/test_WaterBalance.cc | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/test_WaterBalance.cc b/test/test_WaterBalance.cc index 2b4533818..4bb62cfd9 100644 --- a/test/test_WaterBalance.cc +++ b/test/test_WaterBalance.cc @@ -189,4 +189,37 @@ namespace { } + TEST(WaterBalanceTest, WithSWRCvanGenuchten1980) { + int i; + + // Set SWRC and PDF (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_pdf_name, (char *) "NoPDF"); + SW_Site.site_pdf_type = encode_str2pdf(SW_Site.site_pdf_name); + + 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(); + } + + } // namespace From 4927910d9509fa728f052db253a7e18362e36387 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 4 Mar 2022 15:37:17 -0500 Subject: [PATCH 031/326] Clarify Campbell `swrcp[0]` parameter description - `swrcp[0]` is the suction at air-entry in units of cm of water (Campbell 1974) --- SW_Site.c | 4 ++-- testing/Input/swrc_params.in | 2 +- testing/Input/swrc_params_vanGenuchten1980.in | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SW_Site.c b/SW_Site.c index effa122b5..0d5761a6a 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -738,7 +738,7 @@ Bool SWRC_check_parameters(unsigned int swrc_type, double *swrcp) { for implementation of Campbell's 1974 SWRC (\cite Campbell1974). Campbell's 1974 SWRC uses three parameters: - - `swrcp[0]` (`psisMatric`): saturated soil water matric potential [-bar] + - `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 [-] @@ -756,7 +756,7 @@ Bool SWRC_check_parameters_for_Campbell1974(double *swrcp) { logfp, LOGWARN, "SWRC_check_parameters_for_Campbell1974(): invalid value of " - "psi(saturated, matric, [-bar]) = %f (must > 0)\n", + "psi(saturated, matric, [cm]) = %f (must > 0)\n", swrcp[1] ); } diff --git a/testing/Input/swrc_params.in b/testing/Input/swrc_params.in index 867487821..b5e845ffa 100644 --- a/testing/Input/swrc_params.in +++ b/testing/Input/swrc_params.in @@ -7,7 +7,7 @@ # - unused columns are ignored (if selected SWRC uses fewer than 6 parameters) # swrc = "Campbell1974" (default values below) -# * param1 = "saturation" soil water matric potential [-bar] +# * 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 [-] diff --git a/testing/Input/swrc_params_vanGenuchten1980.in b/testing/Input/swrc_params_vanGenuchten1980.in index 21f9ddb35..f766aca61 100644 --- a/testing/Input/swrc_params_vanGenuchten1980.in +++ b/testing/Input/swrc_params_vanGenuchten1980.in @@ -7,7 +7,7 @@ # - unused columns are ignored (if selected SWRC uses fewer than 6 parameters) # swrc = "Campbell1974" -# * param1 = "saturation" soil water matric potential [-bar] +# * 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 [-] From 7131934d549c9b5086bcf01e40b3fce1fb625a82 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 4 Mar 2022 17:03:36 -0500 Subject: [PATCH 032/326] Improve unit tests "TranslateBetweenSWCandSWP" - improved error messages - new unit test for saturated conditions - testing SWP->SWC conversion to meet bounds for a large range of SWP values - new unit tests to check that conversions are the inverse of each other over possible range of SWP values - death tests: loop over all implemented SWRCs; clean up unnecessary tests --- test/test_SW_SoilWater.cc | 236 +++++++++++++++++++++----------------- 1 file changed, 128 insertions(+), 108 deletions(-) diff --git a/test/test_SW_SoilWater.cc b/test/test_SW_SoilWater.cc index 12ddbec9a..d7fba7a8f 100644 --- a/test/test_SW_SoilWater.cc +++ b/test/test_SW_SoilWater.cc @@ -1,8 +1,11 @@ #include "gtest/gtest.h" #include +#include #include #include -#include +#include +#include +#include #include #include "../generic.h" #include "../filefuncs.h" @@ -70,16 +73,27 @@ namespace{ // Test the 'SW_SoilWater' functions 'SWRC_SWCtoSWP' and `SWRC_SWPtoSWC` TEST(SWSoilWaterTest, TranslateBetweenSWCandSWP) { // set up mock variables + unsigned int swrc_type, pdf_type, k; + const int em = LOGFATAL; RealD phi, - swcBulk, swp, swc_fc, swc_wp, + swcBulk, swc_sat, swc_fc, swc_wp, + swp, swrcp[SWRC_PARAM_NMAX], sand = 0.33, clay = 0.33, gravel = 0.2, - width = 10.; - unsigned int swrc_type, pdf_type; - const int em = LOGFATAL; + width = 10., + // SWP values in [0, Inf[ + swpsb[12] = { + 0., 0.001, 0.01, 0.026, 0.027, 0.33, 15., 30., 100., 300., 1000., 10000. + }, + // SWP values in [fc, Inf[ + swpsi[7] = {0.33, 15., 30., 100., 300., 1000., 10000.}; + + std::ostringstream msg; + + // Loop over SWRCs for (swrc_type = 0; swrc_type < N_SWRCs; swrc_type++) { @@ -128,33 +142,61 @@ namespace{ //------ Tests SWC -> SWP + msg.str(""); + msg << "SWRC/PDF = " << swrc_type << "/" << pdf_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 'pdf2str' + //msg << "SWRC/PDF = " << swrc2str[swrc_type] << "/" << pdf2str[pdf_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 - swp = 1. / 3.; - swc_fc = SWRC_SWPtoSWC(swp, swrc_type, swrcp, gravel, width, em); - phi = SWRC_SWCtoSWP(swc_fc + 0.1, swrc_type, swrcp, gravel, width, em); - EXPECT_LT(phi, swp); + 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 - phi = SWRC_SWCtoSWP(swc_fc, swrc_type, swrcp, gravel, width, em); - EXPECT_NEAR(phi, 1. / 3., tol9); + 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 - swc_wp = SWRC_SWPtoSWC(15., swrc_type, swrcp, gravel, width, em); swcBulk = (swc_wp + swc_fc) / 2.; phi = SWRC_SWCtoSWP(swcBulk, swrc_type, swrcp, gravel, width, em); - EXPECT_GT(phi, 1. / 3.); - EXPECT_LT(phi, 15.); + EXPECT_GT(phi, 1. / 3.) << msg.str(); + EXPECT_LT(phi, 15.) << msg.str(); // if swc = wilting point, then we expect phi == 15 bar - phi = SWRC_SWCtoSWP(swc_wp, swrc_type, swrcp, gravel, width, em); - EXPECT_NEAR(phi, 15., tol9); + 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); - phi = SWRC_SWCtoSWP(swcBulk, swrc_type, swrcp, gravel, width, em); - EXPECT_GT(phi, 15.); + EXPECT_GT( + SWRC_SWCtoSWP(swcBulk, swrc_type, swrcp, gravel, width, em), + 15. + ) << msg.str(); + //------ Tests SWP -> SWC @@ -162,18 +204,40 @@ namespace{ 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 - swcBulk = SWRC_SWPtoSWC(15., swrc_type, swrcp, gravel, width, em); - EXPECT_GE(swcBulk, 0.); - EXPECT_LE(swcBulk, width * (1. - gravel)); + 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"; + } } } @@ -204,73 +268,37 @@ namespace{ ); - // --- 2) swc < 0: water content cannot be missing, zero or negative - swrc_type = 0; // any SWRC - EXPECT_DEATH_IF_SUPPORTED( - SWRC_SWCtoSWP(SW_MISSING, swrc_type, swrcp, gravel, width, LOGFATAL), - "@ generic.c LogError" - ); - EXPECT_DOUBLE_EQ( - SWRC_SWCtoSWP(SW_MISSING, swrc_type, swrcp, gravel, width, LOGWARN), - SW_MISSING - ); - - EXPECT_DEATH_IF_SUPPORTED( - SWRC_SWCtoSWP(-1., swrc_type, swrcp, gravel, width, LOGFATAL), - "@ generic.c LogError" - ); - EXPECT_DOUBLE_EQ( - SWRC_SWCtoSWP(-1., swrc_type, swrcp, gravel, width, LOGWARN), - SW_MISSING - ); - - EXPECT_DEATH_IF_SUPPORTED( - SWRC_SWCtoSWP(0., swrc_type, swrcp, gravel, width, LOGFATAL), - "@ generic.c LogError" - ); - EXPECT_DOUBLE_EQ( - SWRC_SWCtoSWP(0., swrc_type, swrcp, gravel, width, LOGWARN), - SW_MISSING - ); - - EXPECT_DEATH_IF_SUPPORTED( - SWRC_SWCtoSWP(1., swrc_type, swrcp, 1., width, LOGFATAL), - "@ generic.c LogError" - ); - 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), - "@ generic.c LogError" - ); - EXPECT_DOUBLE_EQ( - SWRC_SWCtoSWP(1., swrc_type, swrcp, gravel, 0., LOGWARN), - SW_MISSING - ); - - - // --- 3) if theta_sat == 0 (specific to Campbell1974) - // note: this case is normally prevented due to checks of inputs - swrc_type = encode_str2swrc((char *) "Campbell1974"); - memset(swrcp, 0., SWRC_PARAM_NMAX * sizeof(swrcp[0])); - swrcp[0] = 24.2159; - swrcp[2] = 10.3860; + // --- 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), + "@ generic.c LogError" + ); + EXPECT_DOUBLE_EQ( + SWRC_SWCtoSWP(-1., swrc_type, swrcp, gravel, width, LOGWARN), + SW_MISSING + ); - swrcp[1] = 0.; // instead of 0.4436 - EXPECT_DEATH_IF_SUPPORTED( - SWRC_SWCtoSWP(5., swrc_type, swrcp, gravel, width, LOGFATAL), - "@ generic.c LogError" - ); - EXPECT_DOUBLE_EQ( - SWRC_SWCtoSWP(5., swrc_type, swrcp, gravel, width, LOGWARN), - SW_MISSING - ); + EXPECT_DEATH_IF_SUPPORTED( + SWRC_SWCtoSWP(1., swrc_type, swrcp, 1., width, LOGFATAL), + "@ generic.c LogError" + ); + 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), + "@ generic.c LogError" + ); + EXPECT_DOUBLE_EQ( + SWRC_SWCtoSWP(1., swrc_type, swrcp, gravel, 0., LOGWARN), + SW_MISSING + ); + } - // --- 4) if (theta - theta_res) <= 0 (specific to vanGenuchten1980) + // --- *) 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])); @@ -307,32 +335,24 @@ namespace{ //--- 1) Unimplemented SWRC swrc_type = N_SWRCs + 1; EXPECT_DEATH_IF_SUPPORTED( - SWRC_SWPtoSWC(0., swrc_type, swrcp, gravel, width, LOGFATAL), - "@ generic.c LogError" - ); - EXPECT_DOUBLE_EQ( - SWRC_SWPtoSWC(0., swrc_type, swrcp, gravel, width, LOGWARN), - SW_MISSING - ); - - // --- 2) swp <= 0: water content cannot be zero or negative (any SWRC) - swrc_type = 0; - EXPECT_DEATH_IF_SUPPORTED( - SWRC_SWPtoSWC(-1., swrc_type, swrcp, gravel, width, LOGFATAL), + SWRC_SWPtoSWC(15., swrc_type, swrcp, gravel, width, LOGFATAL), "@ generic.c LogError" ); EXPECT_DOUBLE_EQ( - SWRC_SWPtoSWC(-1., swrc_type, swrcp, gravel, width, LOGWARN), + SWRC_SWPtoSWC(15., swrc_type, swrcp, gravel, width, LOGWARN), SW_MISSING ); - EXPECT_DEATH_IF_SUPPORTED( - SWRC_SWPtoSWC(0., swrc_type, swrcp, gravel, width, LOGFATAL), - "@ generic.c LogError" - ); - EXPECT_DOUBLE_EQ( - SWRC_SWPtoSWC(0., 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), + "@ generic.c LogError" + ); + EXPECT_DOUBLE_EQ( + SWRC_SWPtoSWC(-1., swrc_type, swrcp, gravel, width, LOGWARN), + SW_MISSING + ); + } } } From 993c8cad05175994427d35f790631d5a26733718 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 4 Mar 2022 17:06:57 -0500 Subject: [PATCH 033/326] Clarify and improve SWRC SWP<->SWC behavior in edge cases - document valid range for each SWRC and function - document range over which pairs of SWRC functions are each other's inverse function - improve logic - now, SWP = 0 bar is returned for saturated content - make sure that function fails if soil moisture is at 0 or minimum level -> this commit causes tiny deviations in the output of the "testing" example (related to days with very wet conditions) --- SW_SoilWater.c | 138 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 101 insertions(+), 37 deletions(-) diff --git a/SW_SoilWater.c b/SW_SoilWater.c index 7be47bfd0..a0befb25c 100644 --- a/SW_SoilWater.c +++ b/SW_SoilWater.c @@ -1073,7 +1073,7 @@ double SWRC_SWCtoSWP( ) { double res = SW_MISSING; - if (missing(swcBulk) || LE(swcBulk, 0.) || EQ(gravel, 1.) || LE(width, 0.)) { + if (LE(swcBulk, 0.) || EQ(gravel, 1.) || LE(width, 0.)) { LogError( logfp, errmode, @@ -1122,6 +1122,17 @@ double SWRC_SWCtoSWP( @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) @@ -1146,23 +1157,32 @@ double SWRC_SWCtoSWP_Campbell1974( // convert bulk SWC [cm] to theta = matric VWC [cm / cm] theta = swcBulk / (width * (1. - gravel)); - // calculate (theta / theta_s) ^ b - tmp = powe(theta / swrcp[1], swrcp[2]); + 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.; - if (!isfinite(tmp) || 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 - ); + } else { + // calculate (theta / theta_s) ^ b + tmp = powe(theta / swrcp[1], swrcp[2]); - return SW_MISSING; - } + 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 + ); - res = swrcp[0] / tmp; + return SW_MISSING; + } + + res = swrcp[0] / tmp; + } // convert [cm of H20; SOILWAT2 legacy value] to [bar] return res / 1024.; @@ -1175,6 +1195,11 @@ double SWRC_SWCtoSWP_Campbell1974( 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) @@ -1198,31 +1223,50 @@ double SWRC_SWCtoSWP_vanGenuchten1980( // convert bulk SWC [cm] to theta = matric VWC [cm / cm] theta = swcBulk / (width * (1. - gravel)); - // calculate inverse of normalized theta - tmp = theta - swrcp[0]; + // 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 H20 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; - if (!isfinite(tmp) || LE(tmp, 0.)) { + } else { + // 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 { + // theta is <= theta_min LogError( logfp, errmode, "SWRC_SWCtoSWP_vanGenuchten1980(): invalid value of\n" - "\t(theta - theta(residual)) = %f - %f =\n" - "\t= %f (must be > 0)\n", - theta, swrcp[0], tmp + "\ttheta = %f (must be > theta_min = %f)\n", + theta, swrcp[0] ); - return SW_MISSING; + res = SW_MISSING; } - tmp = (swrcp[1] - swrcp[0]) / tmp; - - - // calculate tension [cm of H20] - tmp = powe(tmp, 1. / (1. - 1. / swrcp[3])); // tmp values are in 0-1 - res = pow(-1. + tmp, 1. / swrcp[3]) / swrcp[2]; // use `pow()` because x < 0 - - // convert [cm of H20 at 4 C; value from `soilDB::KSSL_VG_model()`] to [bar] - return res / 1019.716; + return res; } @@ -1287,11 +1331,11 @@ double SWRC_SWPtoSWC( ) { double res = SW_MISSING; - if (LE(swpMatric, 0.)) { + if (LT(swpMatric, 0.)) { LogError( logfp, errmode, - "SWRC_SWPtoSWC(): invalid SWP = %.4f (must be > 0)\n", + "SWRC_SWPtoSWC(): invalid SWP = %.4f (must be >= 0)\n", swpMatric ); @@ -1330,6 +1374,16 @@ double SWRC_SWPtoSWC( @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) @@ -1349,8 +1403,14 @@ double SWRC_SWPtoSWC_Campbell1974( // convert SWP [-bar] to phi [cm of H20; SOILWAT2 legacy value] phi = swpMatric * 1024.; - // calculate matric theta [cm / cm]; assuming `swpMatric` > 0 - res = swrcp[1] * powe(swrcp[0] / phi, 1. / swrcp[2]); + 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; @@ -1363,6 +1423,11 @@ double SWRC_SWPtoSWC_Campbell1974( 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) @@ -1383,11 +1448,10 @@ double SWRC_SWPtoSWC_vanGenuchten1980( // value from `soilDB::KSSL_VG_model()`] phi = swpMatric * 1019.716; - // assume that `swpMatric` > 0 tmp = powe(swrcp[2] * phi, swrcp[3]); tmp = powe(1. + tmp, 1. - 1. / swrcp[3]); - // calculate matric theta [cm / cm] + // 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] From f2f4e00b512d4befbb712423be9ddb5c4e95b73b Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 4 Mar 2022 17:19:21 -0500 Subject: [PATCH 034/326] Fix `remove_from_soil()`: avoid division by zero - previously, the code was dividing by zero if the selected SWRC returned SWP = 0 bar for SWC = saturated - prior to v7.0.0, this never occurred -> now, code catches SWP = 0 bar and treats it as if at field capacity for division (here, calculating weights) -> this fixes unit tests introduced with commit f06dae5c847914c49351e5a6740bf23aac6b715a "New water balance unit tests using vanGenuchten1980 SWRC" --- SW_Flow_lib.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/SW_Flow_lib.c b/SW_Flow_lib.c index 8c0047db9..c6b76aa4a 100644 --- a/SW_Flow_lib.c +++ b/SW_Flow_lib.c @@ -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; ST_RGR_VALUES *st = &stValues; for (i = 0; i < nlyrs; i++) { - swpfrac[i] = coeff[i] / SW_SWRC_SWCtoSWP(swc[i], SW_Site.lyr[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]; } From 0db12d3da3de2849bf9a7e212fb4768d02bef583 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 9 Mar 2022 16:27:16 -0500 Subject: [PATCH 035/326] Fix `SWRC_PDF_swcBulk_minimum()` for "Campbell1974" - "NoPDF" - `SWRC_PDF_swcBulk_minimum()` returns now with a value even for "Campbell1974" and "NoPDF" (previously, an error was produced) --- SW_Site.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SW_Site.c b/SW_Site.c index 0d5761a6a..923fd1c5d 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -602,7 +602,7 @@ double SWRC_PDF_swcBulk_minimum( switch (swrc_type) { case 0: // Campbell1974: does not define a `theta_min` - if (pdf_type == 1 || pdf_type == 2) { + if (pdf_type == 0 || pdf_type == 1 || pdf_type == 2) { // Cosby1984AndOthers/Cosby1984: we cannot set theta_min = 0 because phi -> infinity // -> `legacy_theta_min()` theta_min = legacy_theta_min( From a3faa4d089ca3cec9729b06c987f93269334fa10 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Thu, 9 Jun 2022 08:07:29 -0400 Subject: [PATCH 036/326] Use realistic instead of SWRC-theoretical lower limit of soil moisture for simulations Floating-point arithmetic cannot calculate well with infinite values; however, SWRCs (at least Campbell's 1974 and van Genuchten's 1980 SWRCs) go to infinite tension at `theta_min`. Also, SOILWAT2's implementation of unsaturated percolation is currently not tightly linked to SWRC-specific hydraulic conductivity, i.e., soil moisture can be reduced to exactly `theta_min` which then would lead to a crash. Thus, we use a realistic lower limit of soil moisture which is determined by `SW_swcBulk_minimum()` -- instead of the SWRC-theoretical one --- SW_Site.c | 163 +++++++++++++++++++++++++----------------------------- SW_Site.h | 2 +- 2 files changed, 76 insertions(+), 89 deletions(-) diff --git a/SW_Site.c b/SW_Site.c index 923fd1c5d..a4be69e78 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -225,7 +225,7 @@ static Bool SW_check_soil_properties(SW_LAYER_INFO *lyr) { -/** Lower realistic limit for minimum `theta` +/** A realistic lower limit for minimum `theta` Notes: - currently, -30 MPa (based on limited test runs across western US including hot deserts) @@ -245,15 +245,18 @@ static double lower_limit_of_theta_min( } /** - @brief Legacy minimum/residual volumetric water content - - This function returns minimum soil water content determined - by user input via #_SWCMinVal and/or - by the pedotransfer function of Rawls & Brakensiek 1985 \cite rawls1985WmitE - (independent of the selected SWRC), and/or - by an estimate of a realistic lower limit (see `lower_limit_of_theta_min()`). - - This reproduces legacy behavior of SOILWAT2 prior to v7.0.0. + @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 (`pdf` equal to "Cosby1984AndOthers") is used + (reproducing behavior prior to v7.0.0), then calculated as + maximum of `lower_limit_of_theta_min()` and `PDF_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 @@ -265,10 +268,11 @@ static double lower_limit_of_theta_min( @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 Estimated minimum volumetric water content of the matric soil [cm / cm] + @return Minimum volumetric water content of the matric soil [cm / cm] */ -static double legacy_theta_min( +static double ui_theta_min( double ui_sm_min, double gravel, double width, @@ -276,37 +280,34 @@ static double legacy_theta_min( double clay, double swcBulk_sat, unsigned int swrc_type, - double *swrcp + double *swrcp, + Bool legacy_mode ) { - double vwc_min = SW_MISSING, vwcmin1, vwcmin2; + double vwc_min = SW_MISSING, tmp_vwcmin; if (LT(ui_sm_min, 0.0)) { - /* input: do estimate minimum theta */ - - /* residual theta estimated with Rawls & Brakensiek (1985) PDF*/ - PDF_RawlsBrakensiek1985( - &vwcmin1, - sand, - clay, - swcBulk_sat / ((1. - gravel) * width) - ); - - /* Lower realistic limit for theta_min - - used in case `PDF_RawlsBrakensiek1985` doesn't work or - produces unrealistic small values - */ - vwcmin2 = lower_limit_of_theta_min(swrc_type, swrcp, gravel, width); - - // if `PDF_RawlsBrakensiek1985()` returns SW_MISSING then use `vwcmin2` - if (missing(vwcmin1)) { - vwc_min = vwcmin2; + /* 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) PDF */ + PDF_RawlsBrakensiek1985( + &tmp_vwcmin, + sand, + clay, + swcBulk_sat / ((1. - gravel) * width) + ); - } else { - vwc_min = fmax(vwcmin1, vwcmin2); + /* if `PDF_RawlsBrakensiek1985()` was successful, then take max */ + if (!missing(tmp_vwcmin)) { + vwc_min = fmax(vwc_min, tmp_vwcmin); + } } } else if (GE(_SWCMinVal, 1.0)) { - /* input: fixed (matric) SWP value; unit(_SWCMinVal) == -bar */ + /* user input: fixed (matric) SWP value; unit(_SWCMinVal) == -bar */ vwc_min = SWRC_SWPtoSWC( _SWCMinVal, swrc_type, @@ -317,7 +318,7 @@ static double legacy_theta_min( ) / ((1. - gravel) * width); } else { - /* input: fixed matric VWC; unit(_SWCMinVal) == cm/cm */ + /* user input: fixed matric VWC; unit(_SWCMinVal) == cm/cm */ vwc_min = _SWCMinVal / width; } @@ -555,23 +556,20 @@ double SWRC_PDF_swcBulk_saturated( /** - @brief Minimum/residual soil water content + @brief Lower limit of simulated soil water content See #pdf2str for implemented PDFs. See #swrc2str for implemented SWRCs. - Minimum/residual volumetric water content is usually estimated as one of the - SWRC parameters; this is what this function returns. + 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. - If `swrc_name` is "Campbell1974", then - `theta_min` should be zero which would, however, lead to infinite - soil water potentials. Instead, `theta_min` is determined - - by user input via #_SWCMinVal and - - by the pedotransfer function of Rawls & Brakensiek 1985 \cite rawls1985WmitE - - by an estimate of a realistic lower limit + The lower limit is determined via `ui_theta_min()` using user input + and is strictly larger than the theoretical SWRC value. - Thus, the arguments `_SWCMinVal`, `sand`, `clay`, and `swcBulk_sat` - are utilized only if `swrc_name` is "Campbell1974". + The arguments `sand`, `clay`, and `swcBulk_sat` + are utilized only in legacy mode, i.e., `pdf` equal to "Cosby1984AndOthers". @param[in] swrc_type Identification number of selected SWRC @param[in] *swrcp Vector of SWRC parameters @@ -585,9 +583,9 @@ double SWRC_PDF_swcBulk_saturated( @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 Estimated minimum water content of the bulk soil [cm] + @return Minimum water content of the bulk soil [cm] */ -double SWRC_PDF_swcBulk_minimum( +double SW_swcBulk_minimum( unsigned int swrc_type, double *swrcp, double gravel, @@ -598,59 +596,48 @@ double SWRC_PDF_swcBulk_minimum( double clay, double swcBulk_sat ) { - double theta_min = SW_MISSING; + double theta_min_sim, theta_min_theoretical = SW_MISSING; + /* `theta_min` based on theoretical SWRC (but phi -> infinity) */ switch (swrc_type) { - case 0: // Campbell1974: does not define a `theta_min` - if (pdf_type == 0 || pdf_type == 1 || pdf_type == 2) { - // Cosby1984AndOthers/Cosby1984: we cannot set theta_min = 0 because phi -> infinity - // -> `legacy_theta_min()` - theta_min = legacy_theta_min( - ui_sm_min, - gravel, - width, - sand, - clay, - swcBulk_sat, - swrc_type, - swrcp - ); - - // Possible alternatives to `legacy_theta_min()` - /* theta_min defined via realistic phi_min */ - // theta_min = lower_limit_of_theta_min(swrc_type, swrcp, gravel, width); - - /* theta_min so that `SWRC_SWCtoSWP_Campbell1974()` does just not explode but SWP > 1e13 bar */ - // theta_min = powe(D_DELTA, 1. / swrcp[2]) * swrcp[1]; - - - } else { - LogError( - logfp, - LOGFATAL, - "`SWRC_PDF_swcBulk_minimum()`: " - "PDF (type %d) is not implemented for SWRC (type %d).", - pdf_type, swrc_type - ); - } + case 0: // Campbell1974 + theta_min_theoretical = 0.; break; case 1: // vanGenuchten1980 - theta_min = swrcp[0]; + theta_min_theoretical = swrcp[0]; break; default: LogError( logfp, LOGFATAL, - "`SWRC_PDF_swcBulk_minimum()`: SWRC (type %d) is not implemented.", + "`SW_swcBulk_minimum()`: SWRC (type %d) is not implemented.", swrc_type ); break; } - // Convert from matric [cm/cm] to bulk [cm] - return theta_min * width * (1. - gravel); + /* `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, + // `pdf_type == 1` (Cosby1984AndOthers) doesn't work for unit test: + // error: "no known conversion from 'bool' to 'Bool'" + pdf_type == 1 ? 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); } @@ -1912,7 +1899,7 @@ void SW_SIT_init_run(void) { lyr->fractionWeightMatric_clay ); - lyr->swcBulk_min = SWRC_PDF_swcBulk_minimum( + lyr->swcBulk_min = SW_swcBulk_minimum( lyr->swrc_type, lyr->swrcp, lyr->fractionVolBulk_gravel, diff --git a/SW_Site.h b/SW_Site.h index 02e7bd194..f175bc948 100644 --- a/SW_Site.h +++ b/SW_Site.h @@ -235,7 +235,7 @@ double SWRC_PDF_swcBulk_saturated( double sand, double clay ); -double SWRC_PDF_swcBulk_minimum( +double SW_swcBulk_minimum( unsigned int swrc_type, double *swrcp, double gravel, From a9bccec06506d8f452c9fd7cb0d2780bb6cdeb20 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Thu, 9 Jun 2022 09:04:29 -0400 Subject: [PATCH 037/326] Rename function returning saturated soil moisture content - the new name reflects that this is primarily focused on the value used for SOILWAT2 simulations - even though this value is in most cased copied from parameters of the selected SWRC, legacy mode for "Cosby1984AndOthers" used an alternative estimate --- SW_Site.c | 6 +++--- SW_Site.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SW_Site.c b/SW_Site.c index a4be69e78..ec5c3178e 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -514,7 +514,7 @@ void SWRC_PDF_Cosby1984_for_Campbell1974( @return Estimated saturated water content of the bulk soil [cm] */ -double SWRC_PDF_swcBulk_saturated( +double SW_swcBulk_saturated( unsigned int swrc_type, double *swrcp, double gravel, @@ -544,7 +544,7 @@ double SWRC_PDF_swcBulk_saturated( LogError( logfp, LOGFATAL, - "`SWRC_PDF_swcBulk_saturated()`: SWRC (type %d) is not implemented.", + "`SW_swcBulk_saturated()`: SWRC (type %d) is not implemented.", swrc_type ); break; @@ -1889,7 +1889,7 @@ void SW_SIT_init_run(void) { /* Extract or estimate additional properties */ - lyr->swcBulk_saturated = SWRC_PDF_swcBulk_saturated( + lyr->swcBulk_saturated = SW_swcBulk_saturated( lyr->swrc_type, lyr->swrcp, lyr->fractionVolBulk_gravel, diff --git a/SW_Site.h b/SW_Site.h index f175bc948..8b139bf3c 100644 --- a/SW_Site.h +++ b/SW_Site.h @@ -226,7 +226,7 @@ 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); -double SWRC_PDF_swcBulk_saturated( +double SW_swcBulk_saturated( unsigned int swrc_type, double *swrcp, double gravel, From c8bb45875eb7c93ced215cb1d442d85933b34c23 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Thu, 9 Jun 2022 09:05:19 -0400 Subject: [PATCH 038/326] Expanded documentation on use and maintenance of SWRC/PDF functionality --- SW_Site.c | 27 ++++++----- SW_Site.h | 96 ++++++++++++++++++++++++++++++-------- testing/Input/siteparam.in | 5 +- 3 files changed, 94 insertions(+), 34 deletions(-) diff --git a/SW_Site.c b/SW_Site.c index ec5c3178e..f925c4831 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -92,11 +92,10 @@ RealD /** Character representation of implemented Soil Water Retention Curves (SWRC) -*/ -/* MAINTENANCE: - - Values must exactly match those provided in `siteparam.in`. - - If entries are added/removed/change, then update `check_SWRC_vs_PDF()`, - `SWRC_check_parameters()`, `SWRC_SWCtoSWP()`, and `SWRC_SWPtoSWC()`. + + @note Code maintenance: + - Values must exactly match those provided in `siteparam.in`. + - See details in section #swrc_pdf */ char const *swrc2str[N_SWRCs] = { "Campbell1974", @@ -104,12 +103,11 @@ char const *swrc2str[N_SWRCs] = { }; /** Character representation of implemented Pedotransfer Functions (PDF) -*/ -/* MAINTENANCE: - - Values must exactly match those provided in `siteparam.in`. - - The first value must be "NoPDF". - - If entries are added/removed/change, then update `check_SWRC_vs_PDF()` and - `SWRC_PDF_estimate_parameters()`. + + @note Code maintenance: + - Values must exactly match those provided in `siteparam.in`. + - The first value must be "NoPDF". + - See details in section #swrc_pdf */ char const *pdf2str[N_PDFs] = { "NoPDF", @@ -335,6 +333,8 @@ static double ui_theta_min( /** @brief Translate a SWRC name into a SWRC type number + See #swrc2str and `siteparam.in`. + @param[in] *swrc_name Name of a SWRC @return Internal identification number of selected SWRC @@ -363,6 +363,8 @@ unsigned int encode_str2swrc(char *swrc_name) { /** @brief Translate a PDF name into a PDF type number + See #pdf2str and `siteparam.in`. + @param[in] *pdf_name Name of a PDF @return Internal identification number of selected PDF @@ -645,6 +647,9 @@ double SW_swcBulk_minimum( /** @brief Check whether selected PDF and SWRC are compatible + See #pdf2str for implemented PDFs. + See #swrc2str for implemented SWRCs. + @param[in] *swrc_name Name of selected SWRC @param[in] *pdf_name Name of selected PDF @param[in] isSW2 Logical; TRUE if scope of PDF implementation is "SOILWAT2". diff --git a/SW_Site.h b/SW_Site.h index 8b139bf3c..5eab41dbc 100644 --- a/SW_Site.h +++ b/SW_Site.h @@ -56,26 +56,84 @@ extern "C" { /** - @defgroup swrc_pdf Overview over Soil Water Retention Curves (SWRCs) -- Pedotransfer functions (PDFs) - - __Implement a new SWRC "XXX" and corresponding PDF "YYY"__ - - - update number of `N_SWRCs` and `N_PDFs` - - add new names to `swrc2str[]` and `pdf2str[]` - - implement new functions - - `SWRC_check_parameters_for_XXX()` to validate parameter values - - `SWRC_PDF_YYY_for_XXX()` to estimate parameter values (if implemented) - - `SWRC_SWCtoSWP_XXX()` to translate moisture content to potential - - `SWRC_SWPtoSWC_XXX()` to translate water potential to content - - update "wrapper" functions to call new XXX/YYY-specific functions - - `check_SWRC_vs_PDF()` - - `SWRC_PDF_estimate_parameters()` (if PDF is implemented) - - `SWRC_check_parameters()` - - `SWRC_SWCtoSWP()` - - `SWRC_SWPtoSWC()` - - update `siteparam.in` and `swrc_param.in` - - update and add new unit tests that utilize new XXX/YYY functions + @defgroup swrc_pdf Soil Water Retention Curves + + __Soil Water Retention Curves (SWRCs) -- Pedotransfer functions (PDFs)__ + + __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 PDFs 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 PDF (see input file `siteparam.in`) or, + alternatively ("NoPDF"), provide adequate SWRC parameter values + (see input file `swrc_params.in`). + Please note that `rSOILWAT2` may provide additional PDF functionality. + + + __Approach__ + + -# User selections of SWRC and PDF are read in from input file `siteparam.in` + by `SW_SIT_read()` and, if "NoPDF", SWRC parameters are read from + input file `swrc_params.in` by `SW_SWRC_read()`. + + -# `SW_SIT_init_run()` + - calls `check_SWRC_vs_PDF()` to check that selected SWRC and PDF are + compatible + - calls `SWRC_PDF_estimate_parameters()` to estimate SWRC parameter values + from soil properties based on selected PDF (unless "NoPDF") + - 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/PDF + implementations and are used by SOILWAT2 simulation code. + Thus, most of SOILWAT2 is "unaware" about the selected SWRC/PDF + and how to interpret SWRC parameters. Instead, these "wrapper" functions + know how to call the appropriate SWRC and/or PDF specific functions + which implement SWRC and/or PDF specific details. + + + __Steps to implement a new SWRC "XXX" and corresponding PDF "YYY"__ + + -# Update #N_SWRCs and #N_PDFs + + -# Add new names to #swrc2str and #pdf2str + + -# 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_PDF_YYY_for_XXX()` to estimate parameter values (if implemented), + e.g., `SWRC_PDF_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_PDF()` + - `SWRC_PDF_estimate_parameters()` (if PDF 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 2 /**< Number of implemented SWRCs */ #define N_PDFs 4 /**< Number of implemented PDFs */ diff --git a/testing/Input/siteparam.in b/testing/Input/siteparam.in index be6e2a06f..95ed8e7b3 100644 --- a/testing/Input/siteparam.in +++ b/testing/Input/siteparam.in @@ -87,15 +87,12 @@ RCP85 #--- Soil water retention curve (SWRC) ------ # # Implemented options (`swrc_name`/`pdf_name`, see `swrc2str[]`/`pdf2str[]`): -# - pdf_name = "NoPDF": parameters for any SWRC obtained from -# input file ("swrc_params.in") instead of estimated from PDF +# - pdf_name = "NoPDF": SWRC parameters from input file "swrc_params.in" # - swrc_name = "Campbell1974": (Campbell 1974) # * pdf_name = "Cosby1984AndOthers": PDFs by Cosby et al. 1984 # (but `swc_sat` by Saxton et al. 2006, # `swc_min` by user input, realistic SWP limit, or Rawls et al. 1985) # * pdf_name = "Cosby1984": PDFs by Cosby et al. 1984 -# (including `swc_sat`; `swc_min` = 0 is not realistic, thus, -# `swc_min` by user input, realistic SWP limit, or Rawls et al. 1985) # - swrc_name = "vanGenuchten1980": van Genuchten-Mualem (van Genuchten 1980) # * pdf_name = "Rosetta3": not implemented in C (but see rSOILWAT2) # - swrc_name = "BrooksCorey1964": (Brooks and Corey 1964) From 8be1d98e4a022ba25edcf0a50c1e6745cc3597e6 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 10 Jun 2022 15:06:12 -0400 Subject: [PATCH 039/326] Accommodating (future) PDFs with bulk density as predictor - `SWRC_PDF_estimate_parameters()` gains parameter `bdensity` --- SW_Site.c | 18 +++++++++++++++--- SW_Site.h | 8 ++++++-- test/test_SW_Site.cc | 25 ++++++++++++++++++++----- test/test_SW_SoilWater.cc | 4 +++- 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/SW_Site.c b/SW_Site.c index f925c4831..b7754ed11 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -404,11 +404,18 @@ unsigned int encode_str2pdf(char *pdf_name) { @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 PDF + */ void SWRC_PDF_estimate_parameters( unsigned int pdf_type, double *swrcp, - double sand, double clay, double gravel + double sand, + double clay, + double gravel, + double bdensity ) { if (pdf_type == 0) { @@ -443,9 +450,13 @@ void SWRC_PDF_estimate_parameters( } /**********************************/ - /* TODO: remove once other PDFs are implemented that utilize gravel */ + /* TODO: remove once PDFs are implemented that utilize gravel */ /* avoiding `error: unused parameter 'gravel' [-Werror=unused-parameter]` */ if (gravel < 0.) {}; + + /* TODO: remove once PDFs are implemented that utilize bdensity */ + /* avoiding `error: unused parameter 'gravel' [-Werror=unused-parameter]` */ + if (bdensity < 0.) {}; /**********************************/ } @@ -1859,7 +1870,8 @@ void SW_SIT_init_run(void) { lyr->swrcp, lyr->fractionWeightMatric_sand, lyr->fractionWeightMatric_clay, - lyr->fractionVolBulk_gravel + lyr->fractionVolBulk_gravel, + lyr->soilBulk_density ); } diff --git a/SW_Site.h b/SW_Site.h index 5eab41dbc..b3d1756df 100644 --- a/SW_Site.h +++ b/SW_Site.h @@ -271,11 +271,15 @@ unsigned int encode_str2pdf(char *pdf_name); void SWRC_PDF_estimate_parameters( unsigned int pdf_type, double *swrcp, - double sand, double clay, double gravel + double sand, + double clay, + double gravel, + double bdensity ); void SWRC_PDF_Cosby1984_for_Campbell1974( double *swrcp, - double sand, double clay + double sand, + double clay ); diff --git a/test/test_SW_Site.cc b/test/test_SW_Site.cc index eca493525..71c302bcb 100644 --- a/test/test_SW_Site.cc +++ b/test/test_SW_Site.cc @@ -63,7 +63,8 @@ namespace { swrcp[SWRC_PARAM_NMAX], sand = 0.33, clay = 0.33, - gravel = 0.1; + gravel = 0.1, + bdensity = 1.4; unsigned int swrc_type, k; @@ -75,7 +76,10 @@ namespace { SWRC_PDF_estimate_parameters( encode_str2pdf((char *) ns_pdfca2C1974[k]), swrcp, - sand, clay, gravel + sand, + clay, + gravel, + bdensity ); EXPECT_TRUE((bool) SWRC_check_parameters(swrc_type, swrcp)); } @@ -85,7 +89,10 @@ namespace { SWRC_PDF_estimate_parameters( encode_str2pdf((char *) ns_pdfc2vG1980[k]), swrcp, - sand, clay, gravel + sand, + clay, + gravel, + bdensity ); EXPECT_TRUE((bool) SWRC_check_parameters(swrc_type, swrcp)); } @@ -99,7 +106,8 @@ namespace { swrcp[SWRC_PARAM_NMAX], sand = 0.33, clay = 0.33, - gravel = 0.1; + gravel = 0.1, + bdensity = 1.4; unsigned int pdf_type; @@ -107,7 +115,14 @@ namespace { pdf_type = N_PDFs + 1; EXPECT_DEATH_IF_SUPPORTED( - SWRC_PDF_estimate_parameters(pdf_type, swrcp, sand, clay, gravel), + SWRC_PDF_estimate_parameters( + pdf_type, + swrcp, + sand, + clay, + gravel, + bdensity + ), "@ generic.c LogError" ); } diff --git a/test/test_SW_SoilWater.cc b/test/test_SW_SoilWater.cc index d7fba7a8f..bb490230e 100644 --- a/test/test_SW_SoilWater.cc +++ b/test/test_SW_SoilWater.cc @@ -83,6 +83,7 @@ namespace{ sand = 0.33, clay = 0.33, gravel = 0.2, + bdensity = 1.4, width = 10., // SWP values in [0, Inf[ swpsb[12] = { @@ -119,7 +120,8 @@ namespace{ swrcp, sand, clay, - gravel + gravel, + bdensity ); } else { From 5653f7ffea715b7f82c501ff3a3d8ff97ea14800 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 10 Jun 2022 17:46:04 -0400 Subject: [PATCH 040/326] Added saturated hydraulic conductivity as parameter to `swrcp` - updated `SWRC_PDF_Cosby1984_for_Campbell1974()` now also estimates `K_sat` - updated `SWRC_check_parameters_for_Campbell1974()` now also checks `K_sat` - updated `SWRC_check_parameters_for_vanGenuchten1980()` now also checks `K_sat` --- SW_Site.c | 130 +++++++++++++++++- test/test_SW_Site.cc | 2 + test/test_SW_SoilWater.cc | 2 + testing/Input/swrc_params.in | 20 +-- testing/Input/swrc_params_vanGenuchten1980.in | 19 +-- 5 files changed, 152 insertions(+), 21 deletions(-) diff --git a/SW_Site.c b/SW_Site.c index b7754ed11..7b9bbe492 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -465,7 +465,7 @@ void SWRC_PDF_estimate_parameters( @brief Estimate Campbell's 1974 SWRC parameters using Cosby et al. 1984 multivariate PDF - Estimation of three SWRC parameter values `swrcp` + Estimation of four SWRC parameter values `swrcp` based on sand, clay, and (silt). Parameters are explained in `SWRC_check_parameters_for_Campbell1974()`. @@ -486,6 +486,8 @@ void SWRC_PDF_Cosby1984_for_Campbell1974( 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 */ @@ -493,7 +495,11 @@ void SWRC_PDF_Cosby1984_for_Campbell1974( /* 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); } @@ -740,11 +746,12 @@ Bool SWRC_check_parameters(unsigned int swrc_type, double *swrcp) { See `SWRC_SWCtoSWP_Campbell1974()` and `SWRC_SWPtoSWC_Campbell1974()` for implementation of Campbell's 1974 SWRC (\cite Campbell1974). - Campbell's 1974 SWRC uses three parameters: + Campbell's 1974 SWRC uses four parameters: - `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]` @param[in] *swrcp Vector of SWRC parameters @@ -786,6 +793,17 @@ Bool SWRC_check_parameters_for_Campbell1974(double *swrcp) { ); } + 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; } @@ -795,13 +813,14 @@ Bool SWRC_check_parameters_for_Campbell1974(double *swrcp) { See `SWRC_SWCtoSWP_vanGenuchten1980()` and `SWRC_SWPtoSWC_vanGenuchten1980()` for implementation of van Genuchten's 1980 SWRC (\cite vanGenuchten1980). - van Genuchten's 1980 SWRC uses four parameters: + van Genuchten's 1980 SWRC uses five parameters: - `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 @@ -867,6 +886,111 @@ Bool SWRC_check_parameters_for_vanGenuchten1980(double *swrcp) { ); } + if (LE(swrcp[4], 0.0)) { + res = swFALSE; + LogError( + logfp, + LOGWARN, + "SWRC_check_parameters_for_vanGenuchten1980(): invalid value of " + "K_sat = %f (must be > 0)\n", + swrcp[4] + ); + } + + return res; +} + + +/** + @brief Check FXW SWRC parameters + + See `SWRC_SWCtoSWP_FXW()` and `SWRC_SWPtoSWC_FXW()` + for implementation of FXW's SWRC + (\cite fredlund1994CGJa, \cite wang2018wrr, \rudiyanto2020JoH). + + FXW SWRC uses four parameters: + - `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 [-] + + @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_vanGenuchten1980(): invalid value of " + "theta(residual, matric, [cm/cm]) = %f (must within 0-1)\n", + swrcp[0] + ); + } + + 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 within 0-1)\n", + swrcp[1] + ); + } + + 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(swrcp[3], 1.0)) { + res = swFALSE; + LogError( + logfp, + LOGWARN, + "SWRC_check_parameters_for_vanGenuchten1980(): invalid value of " + "n = %f (must be > 1)\n", + swrcp[3] + ); + } + + if (LE(swrcp[4], 0.0)) { + res = swFALSE; + LogError( + logfp, + LOGWARN, + "SWRC_check_parameters_for_vanGenuchten1980(): invalid value of " + "K_sat = %f (must be > 0)\n", + swrcp[4] + ); + } + return res; } diff --git a/test/test_SW_Site.cc b/test/test_SW_Site.cc index 71c302bcb..8110f0247 100644 --- a/test/test_SW_Site.cc +++ b/test/test_SW_Site.cc @@ -212,6 +212,7 @@ namespace { 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) @@ -243,6 +244,7 @@ namespace { 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)); diff --git a/test/test_SW_SoilWater.cc b/test/test_SW_SoilWater.cc index bb490230e..5dcd98ba5 100644 --- a/test/test_SW_SoilWater.cc +++ b/test/test_SW_SoilWater.cc @@ -136,6 +136,7 @@ namespace{ swrcp[1] = 0.4213539; swrcp[2] = 0.007735474; swrcp[3] = 1.344678; + swrcp[4] = 7.78506; } else { FAIL() << "No SWRC parameters available for " << swrc2str[swrc_type]; @@ -308,6 +309,7 @@ namespace{ 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), diff --git a/testing/Input/swrc_params.in b/testing/Input/swrc_params.in index b5e845ffa..8c3059b78 100644 --- a/testing/Input/swrc_params.in +++ b/testing/Input/swrc_params.in @@ -10,19 +10,21 @@ # * 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] -# param1 param2 param3 param4 param5 param6 -18.6080 0.42703 5.3020 0.0000 0.0000 0.0000 -20.4644 0.43290 7.0500 0.0000 0.0000 0.0000 -22.8402 0.44013 9.4320 0.0000 0.0000 0.0000 -24.0381 0.44291 10.0690 0.0000 0.0000 0.0000 -24.2159 0.44359 10.3860 0.0000 0.0000 0.0000 -23.3507 0.44217 10.3830 0.0000 0.0000 0.0000 -12.3880 0.41370 7.3250 0.0000 0.0000 0.0000 -12.3880 0.41370 7.3250 0.0000 0.0000 0.0000 +# 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/testing/Input/swrc_params_vanGenuchten1980.in b/testing/Input/swrc_params_vanGenuchten1980.in index f766aca61..671d3125a 100644 --- a/testing/Input/swrc_params_vanGenuchten1980.in +++ b/testing/Input/swrc_params_vanGenuchten1980.in @@ -16,13 +16,14 @@ # * 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 0 0 -0.10061329 0.4011315 0.009425738 1.352274 0 0 -0.12060752 0.4278807 0.010424896 1.287923 0 0 -0.12336711 0.4393192 0.010807529 1.274654 0 0 -0.12461498 0.4444546 0.011155912 1.267327 0 0 -0.12480807 0.4426857 0.011408809 1.264873 0 0 -0.10129327 0.3878319 0.014241212 1.311722 0 0 -0.10129327 0.3878319 0.014241212 1.311722 0 0 +# 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 From 0dff98cac2c9b028cc1033ec5485a03b5412e00d Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Mon, 27 Jun 2022 17:22:08 -0400 Subject: [PATCH 041/326] New SWRC: FXW (Fredlund-Xing-Wang) - N_SWRCs and N_PDFs increased each by one - `swrc2str[]` and `pdf2str[]` updated with "FXW" and "neuroFX2021" (PDF will be implemented in rSOILWAT2) - new `SWRC_check_parameters_for_FXW()` to validate parameter values of FXW - new `SWRC_SWCtoSWP_FXW()` to translate moisture content to water potential * There is no inverse of FWX --> implemented bracketing algorithm "interpolate, truncate, project" (ITP) to estimate phi from theta with new `itp_FXW_for_phi()` - new `SWRC_SWPtoSWC_FXW()` to translate water potential to moisture content * new bare-bones version `FXW_phi_to_theta()` - updated "wrapper" functions to incorporate FXW: `check_SWRC_vs_PDF()`, `SWRC_check_parameters()`, `SW_swcBulk_minimum()`, `SW_swcBulk_saturated()`, `SWRC_SWCtoSWP()` where theta = 0 is no allowed, `SWRC_SWPtoSWC()` - `swE` macro defining Euler's constant - used by FXW - `FXW_h0` and `FXW_hr` macros defining FXW constants - expanded unit tests to incorporate FXW --- SW_Defines.h | 8 + SW_Site.c | 105 +++-- SW_Site.h | 13 +- SW_SoilWater.c | 364 +++++++++++++++++- SW_SoilWater.h | 13 + doc/SOILWAT2.bib | 66 ++++ test/test_SW_Site.cc | 121 +++++- test/test_SW_SoilWater.cc | 24 +- test/test_WaterBalance.cc | 33 ++ testing/Input/siteparam.in | 12 +- testing/Input/swrc_params.in | 9 + testing/Input/swrc_params_FXW.in | 26 ++ testing/Input/swrc_params_vanGenuchten1980.in | 5 - 13 files changed, 741 insertions(+), 58 deletions(-) create mode 100644 testing/Input/swrc_params_FXW.in diff --git a/SW_Defines.h b/SW_Defines.h index 0675080f9..2380eba6d 100644 --- a/SW_Defines.h +++ b/SW_Defines.h @@ -60,6 +60,14 @@ extern "C" { #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 but may not be defined for any implementation that conforms to the C standard */ diff --git a/SW_Site.c b/SW_Site.c index 172fe715c..d1307a82e 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -99,7 +99,8 @@ RealD */ char const *swrc2str[N_SWRCs] = { "Campbell1974", - "vanGenuchten1980" + "vanGenuchten1980", + "FXW" }; /** Character representation of implemented Pedotransfer Functions (PDF) @@ -113,7 +114,8 @@ char const *pdf2str[N_PDFs] = { "NoPDF", "Cosby1984AndOthers", "Cosby1984", - "Rosetta3" + "Rosetta3", + "neuroFX2021" }; @@ -547,10 +549,9 @@ double SW_swcBulk_saturated( switch (swrc_type) { case 0: // Campbell1974 if (pdf_type == 1) { - // Cosby1984AndOthers + // Cosby1984AndOthers (backwards compatible) PDF_Saxton2006(&theta_sat, sand, clay); } else { - // Cosby1984 theta_sat = swrcp[1]; } break; @@ -559,6 +560,10 @@ double SW_swcBulk_saturated( theta_sat = swrcp[1]; break; + case 2: // FXW + theta_sat = swrcp[0]; + break; + default: LogError( logfp, @@ -617,16 +622,20 @@ double SW_swcBulk_minimum( ) { double theta_min_sim, theta_min_theoretical = SW_MISSING; - /* `theta_min` based on theoretical SWRC (but phi -> infinity) */ + /* `theta_min` based on theoretical SWRC */ switch (swrc_type) { - case 0: // Campbell1974 + case 0: // Campbell1974: phi = infinity at theta_min theta_min_theoretical = 0.; break; - case 1: // vanGenuchten1980 + case 1: // vanGenuchten1980: phi = infinity at theta_min theta_min_theoretical = swrcp[0]; break; + case 2: // FXW: phi = 6.3 x 10^6 cm at theta_min + theta_min_theoretical = 0.; + break; + default: LogError( logfp, @@ -697,6 +706,14 @@ Bool check_SWRC_vs_PDF(char *swrc_name, char *pdf_name, Bool isSW2) { ) { res = swTRUE; } + else if ( + !isSW2 && + Str_CompareI(swrc_name, (char *) "FXW") == 0 && + // "neuroFX2021" PDF is not implemented in SOILWAT2 (but in rSOILWAT2) + Str_CompareI(pdf_name, (char *) "neuroFX2021") == 0 + ) { + res = swTRUE; + } } return res; @@ -725,6 +742,10 @@ Bool SWRC_check_parameters(unsigned int swrc_type, double *swrcp) { res = SWRC_check_parameters_for_vanGenuchten1980(swrcp); break; + case 2: + res = SWRC_check_parameters_for_FXW(swrcp); + break; + default: LogError( logfp, @@ -906,15 +927,27 @@ Bool SWRC_check_parameters_for_vanGenuchten1980(double *swrcp) { See `SWRC_SWCtoSWP_FXW()` and `SWRC_SWPtoSWC_FXW()` for implementation of FXW's SWRC - (\cite fredlund1994CGJa, \cite wang2018wrr, \rudiyanto2020JoH). + (\cite fredlund1994CGJa, \cite wang2018wrr). - FXW SWRC uses four parameters: - - `swrcp[0]` (`theta_r`): residual volumetric water content - of the matric component [cm/cm] - - `swrcp[1]` (`theta_s`): saturated volumetric water content + "FXWJD" is a synonym for the FXW SWRC (\cite wang2018wrr). + + 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[2]` (`alpha`): related to the inverse of air entry suction [cm-1] - - `swrcp[3]` (`n`): measure of the pore-size distribution [-] + - `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 wang2022wrr. @param[in] *swrcp Vector of SWRC parameters @@ -928,66 +961,64 @@ Bool SWRC_check_parameters_for_FXW(double *swrcp) { LogError( logfp, LOGWARN, - "SWRC_check_parameters_for_vanGenuchten1980(): invalid value of " - "theta(residual, matric, [cm/cm]) = %f (must within 0-1)\n", + "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) || GT(swrcp[1], 1.)) { + if (LE(swrcp[1], 0.0)) { res = swFALSE; LogError( logfp, LOGWARN, - "SWRC_check_parameters_for_vanGenuchten1980(): invalid value of " - "theta(saturated, matric, [cm/cm]) = %f (must within 0-1)\n", + "SWRC_check_parameters_for_FXW(): invalid value of " + "alpha([1 / cm]) = %f (must be > 0)\n", swrcp[1] ); } - if (LE(swrcp[1], swrcp[0])) { + if (LE(swrcp[2], 1.0) || GT(swrcp[2], 10.)) { 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] + "SWRC_check_parameters_for_FXW(): invalid value of " + "n = %f (must be within 1-10)\n", + swrcp[2] ); } - if (LE(swrcp[2], 0.0)) { + if (LE(swrcp[3], 0.0) || GT(swrcp[3], 1.5)) { res = swFALSE; LogError( logfp, LOGWARN, - "SWRC_check_parameters_for_vanGenuchten1980(): invalid value of " - "alpha([1 / cm]) = %f (must > 0)\n", - swrcp[2] + "SWRC_check_parameters_for_FXW(): invalid value of " + "m = %f (must be within 0-1.5)\n", + swrcp[3] ); } - if (LE(swrcp[3], 1.0)) { + if (LE(swrcp[4], 0.0)) { res = swFALSE; LogError( logfp, LOGWARN, - "SWRC_check_parameters_for_vanGenuchten1980(): invalid value of " - "n = %f (must be > 1)\n", - swrcp[3] + "SWRC_check_parameters_for_FXW(): invalid value of " + "K_sat = %f (must be > 0)\n", + swrcp[4] ); } - if (LE(swrcp[4], 0.0)) { + if (LE(swrcp[5], 0.)) { res = swFALSE; LogError( logfp, LOGWARN, - "SWRC_check_parameters_for_vanGenuchten1980(): invalid value of " - "K_sat = %f (must be > 0)\n", - swrcp[4] + "SWRC_check_parameters_for_FXW(): invalid value of " + "L = %f (must be > 0)\n", + swrcp[5] ); } diff --git a/SW_Site.h b/SW_Site.h index d77741296..20eddca0b 100644 --- a/SW_Site.h +++ b/SW_Site.h @@ -135,10 +135,18 @@ extern "C" { */ #define SWRC_PARAM_NMAX 6 /**< Maximal number of SWRC parameters implemented */ -#define N_SWRCs 2 /**< Number of implemented SWRCs */ -#define N_PDFs 4 /**< Number of implemented PDFs */ +#define N_SWRCs 3 /**< Number of implemented SWRCs */ +#define N_PDFs 5 /**< Number of implemented PDFs */ +#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; typedef struct { @@ -287,6 +295,7 @@ Bool check_SWRC_vs_PDF(char *swrc_name, char *pdf_name, Bool isSW2); 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, diff --git a/SW_SoilWater.c b/SW_SoilWater.c index d8f32b6c5..189481b3a 100644 --- a/SW_SoilWater.c +++ b/SW_SoilWater.c @@ -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 */ /* --------------------------------------------------- */ @@ -1072,11 +1303,11 @@ double SWRC_SWCtoSWP( ) { double res = SW_MISSING; - if (LE(swcBulk, 0.) || EQ(gravel, 1.) || LE(width, 0.)) { + if (LT(swcBulk, 0.) || EQ(gravel, 1.) || LE(width, 0.)) { LogError( logfp, errmode, - "SWRC_SWCtoSWP(): invalid SWC = %.4f (must be > 0)\n", + "SWRC_SWCtoSWP(): invalid SWC = %.4f (must be >= 0)\n", swcBulk ); @@ -1098,6 +1329,13 @@ double SWRC_SWCtoSWP( ); break; + case 2: + res = SWRC_SWCtoSWP_FXW( + swcBulk, swrcp, gravel, width, + errmode + ); + break; + default: LogError( logfp, @@ -1269,6 +1507,83 @@ double SWRC_SWCtoSWP_vanGenuchten1980( } + +/** + @brief Convert soil water content to soil water potential 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] 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; +} + + /** @brief Convert soil water potential to soil water content using specified soil water retention curve (SWRC) @@ -1350,6 +1665,10 @@ double SWRC_SWPtoSWC( res = SWRC_SWPtoSWC_vanGenuchten1980(swpMatric, swrcp, gravel, width); break; + case 2: + res = SWRC_SWPtoSWC_FXW(swpMatric, swrcp, gravel, width); + break; + default: LogError( logfp, @@ -1458,6 +1777,47 @@ double SWRC_SWPtoSWC_vanGenuchten1980( } +/** + @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. diff --git a/SW_SoilWater.h b/SW_SoilWater.h index babe58c14..dc40a94e3 100644 --- a/SW_SoilWater.h +++ b/SW_SoilWater.h @@ -186,6 +186,13 @@ double SWRC_SWCtoSWP_vanGenuchten1980( 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( @@ -208,6 +215,12 @@ double SWRC_SWPtoSWC_vanGenuchten1980( double gravel, double width ); +double SWRC_SWPtoSWC_FXW( + double swpMatric, + double *swrcp, + double gravel, + double width +); #ifdef SWDEBUG void SW_WaterBalance_Checks(void); diff --git a/doc/SOILWAT2.bib b/doc/SOILWAT2.bib index 9d9760ea7..4699258c8 100644 --- a/doc/SOILWAT2.bib +++ b/doc/SOILWAT2.bib @@ -373,3 +373,69 @@ @incollection{rawls1985WmitE 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} +} diff --git a/test/test_SW_Site.cc b/test/test_SW_Site.cc index 8110f0247..91683094d 100644 --- a/test/test_SW_Site.cc +++ b/test/test_SW_Site.cc @@ -54,7 +54,15 @@ namespace { "vanGenuchten1980" // PDFs implemented in C }; - + const char *ns_pdfa2FXW[] = { + "FXW" + // all PDFs + "neuroFX2021" + }; + const char *ns_pdfc2FXW[] = { + "FXW" + // PDFs implemented in C + }; // Test pedotransfer functions TEST(SiteTest, PDFs) { @@ -96,6 +104,19 @@ namespace { ); EXPECT_TRUE((bool) SWRC_check_parameters(swrc_type, swrcp)); } + + swrc_type = encode_str2swrc((char *) ns_pdfc2FXW[0]); + for (k = 1; k < length(ns_pdfc2FXW); k++) { + SWRC_PDF_estimate_parameters( + encode_str2pdf((char *) ns_pdfc2FXW[k]), + swrcp, + sand, + clay, + gravel, + bdensity + ); + EXPECT_TRUE((bool) SWRC_check_parameters(swrc_type, swrcp)); + } } @@ -156,6 +177,14 @@ namespace { swTRUE ) ); + + EXPECT_FALSE( + (bool) check_SWRC_vs_PDF( + (char *) ns_pdfa2FXW[0], + (char *) ns_pdfca2C1974[k], + swTRUE + ) + ); } for (k = 1; k < length(ns_pdfa2vG1980); k++) { @@ -174,7 +203,43 @@ namespace { swFALSE ) ); + + EXPECT_FALSE( + (bool) check_SWRC_vs_PDF( + (char *) ns_pdfa2FXW[0], + (char *) ns_pdfa2vG1980[k], + swFALSE + ) + ); } + + + for (k = 1; k < length(ns_pdfa2FXW); k++) { + EXPECT_TRUE( + (bool) check_SWRC_vs_PDF( + (char *) ns_pdfa2FXW[0], + (char *) ns_pdfa2FXW[k], + swFALSE + ) + ); + + EXPECT_FALSE( + (bool) check_SWRC_vs_PDF( + (char *) ns_pdfca2C1974[0], + (char *) ns_pdfa2FXW[k], + swFALSE + ) + ); + + EXPECT_FALSE( + (bool) check_SWRC_vs_PDF( + (char *) ns_pdfa2vG1980[0], + (char *) ns_pdfa2FXW[k], + swFALSE + ) + ); + } + } @@ -277,6 +342,60 @@ namespace { 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; } diff --git a/test/test_SW_SoilWater.cc b/test/test_SW_SoilWater.cc index 5dcd98ba5..2429bd253 100644 --- a/test/test_SW_SoilWater.cc +++ b/test/test_SW_SoilWater.cc @@ -85,12 +85,12 @@ namespace{ gravel = 0.2, bdensity = 1.4, width = 10., - // SWP values in [0, Inf[ + // 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., 10000. + 0., 0.001, 0.01, 0.026, 0.027, 0.33, 15., 30., 100., 300., 1000., 6178. }, - // SWP values in [fc, Inf[ - swpsi[7] = {0.33, 15., 30., 100., 300., 1000., 10000.}; + // 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; @@ -101,7 +101,7 @@ namespace{ memset(swrcp, 0., SWRC_PARAM_NMAX * sizeof(swrcp[0])); // Find a suitable PDF to generate `SWRCp` - // (start `k2` at 1 because 0 codes to "NoPDF") + // (start `pdf_type` at 1 because 0 codes to "NoPDF") for ( pdf_type = 1; pdf_type < N_PDFs && !check_SWRC_vs_PDF( @@ -112,6 +112,7 @@ namespace{ pdf_type++ ) {} + // Obtain SWRCp if (pdf_type < N_PDFs) { // PDF implemented in C: estimate parameters @@ -138,6 +139,19 @@ namespace{ 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]; } diff --git a/test/test_WaterBalance.cc b/test/test_WaterBalance.cc index 4bb62cfd9..ca11123da 100644 --- a/test/test_WaterBalance.cc +++ b/test/test_WaterBalance.cc @@ -222,4 +222,37 @@ namespace { } + + TEST(WaterBalanceTest, WithSWRCFXW) { + int i; + + // Set SWRC and PDF (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_pdf_name, (char *) "NoPDF"); + SW_Site.site_pdf_type = encode_str2pdf(SW_Site.site_pdf_name); + + 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(); + } + } // namespace diff --git a/testing/Input/siteparam.in b/testing/Input/siteparam.in index 95ed8e7b3..2ac096f7a 100644 --- a/testing/Input/siteparam.in +++ b/testing/Input/siteparam.in @@ -95,12 +95,12 @@ RCP85 # * pdf_name = "Cosby1984": PDFs by Cosby et al. 1984 # - swrc_name = "vanGenuchten1980": van Genuchten-Mualem (van Genuchten 1980) # * pdf_name = "Rosetta3": not implemented in C (but see rSOILWAT2) -# - swrc_name = "BrooksCorey1964": (Brooks and Corey 1964) -# * pdf_name: currently, no implemented PDF (but see rSOILWAT2) -# - swrc_name = "Brunswick": (Weber et al. 2019) -# * pdf_name: currently, no implemented PDF (but see rSOILWAT2) -# - swrc_name = "FXW": (Fredlund and Xing 1994, Wang et al. 2018, Rudiyanto et al. 2020) -# * pdf_name: currently, no implemented PDF (but see rSOILWAT2) +## - swrc_name = "BrooksCorey1964": (Brooks and Corey 1964) +## * pdf_name: currently, no implemented PDF +## - swrc_name = "Brunswick": (Weber et al. 2019) +## * pdf_name: currently, no implemented PDF +# - swrc_name = "FXW": (Fredlund and Xing 1994, Wang et al. 2018) +# * pdf_name: "neuroFX2021": not implemented PDF in C (but see rSOILWAT2) # # Note: option "Campbell1974"/"Cosby1984AndOthers" was hard-coded < v7.0.0 # diff --git a/testing/Input/swrc_params.in b/testing/Input/swrc_params.in index 8c3059b78..d9003860b 100644 --- a/testing/Input/swrc_params.in +++ b/testing/Input/swrc_params.in @@ -19,6 +19,15 @@ # * 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 diff --git a/testing/Input/swrc_params_FXW.in b/testing/Input/swrc_params_FXW.in new file mode 100644 index 000000000..8d916e148 --- /dev/null +++ b/testing/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" +# * 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/testing/Input/swrc_params_vanGenuchten1980.in b/testing/Input/swrc_params_vanGenuchten1980.in index 671d3125a..d31396bcc 100644 --- a/testing/Input/swrc_params_vanGenuchten1980.in +++ b/testing/Input/swrc_params_vanGenuchten1980.in @@ -6,11 +6,6 @@ # selected SWRC (see `siteparam.in`) # - unused columns are ignored (if selected SWRC uses fewer than 6 parameters) -# swrc = "Campbell1974" -# * 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 [-] - # 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] From 0c7162bd387edc6f2dd1498657d07bc9a5bbb4e9 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Mon, 27 Jun 2022 17:24:17 -0400 Subject: [PATCH 042/326] Fix/improve documentation/comments of SWRC related code/functions --- SW_Site.c | 14 +++++++------- SW_SoilWater.c | 20 ++++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/SW_Site.c b/SW_Site.c index d1307a82e..3fcbbd09f 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -226,8 +226,8 @@ static Bool SW_check_soil_properties(SW_LAYER_INFO *lyr) { /** A realistic lower limit for minimum `theta` - Notes: - - currently, -30 MPa + +@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) @@ -767,7 +767,7 @@ Bool SWRC_check_parameters(unsigned int swrc_type, double *swrcp) { See `SWRC_SWCtoSWP_Campbell1974()` and `SWRC_SWPtoSWC_Campbell1974()` for implementation of Campbell's 1974 SWRC (\cite Campbell1974). - Campbell's 1974 SWRC uses four parameters: + 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] @@ -798,7 +798,7 @@ Bool SWRC_check_parameters_for_Campbell1974(double *swrcp) { logfp, LOGWARN, "SWRC_check_parameters_for_Campbell1974(): invalid value of " - "theta(saturated, matric, [cm/cm]) = %f (must within 0-1)\n", + "theta(saturated, matric, [cm/cm]) = %f (must be within 0-1)\n", swrcp[1] ); } @@ -834,7 +834,7 @@ Bool SWRC_check_parameters_for_Campbell1974(double *swrcp) { See `SWRC_SWCtoSWP_vanGenuchten1980()` and `SWRC_SWPtoSWC_vanGenuchten1980()` for implementation of van Genuchten's 1980 SWRC (\cite vanGenuchten1980). - van Genuchten's 1980 SWRC uses five parameters: + "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 @@ -856,7 +856,7 @@ Bool SWRC_check_parameters_for_vanGenuchten1980(double *swrcp) { logfp, LOGWARN, "SWRC_check_parameters_for_vanGenuchten1980(): invalid value of " - "theta(residual, matric, [cm/cm]) = %f (must within 0-1)\n", + "theta(residual, matric, [cm/cm]) = %f (must be within 0-1)\n", swrcp[0] ); } @@ -867,7 +867,7 @@ Bool SWRC_check_parameters_for_vanGenuchten1980(double *swrcp) { logfp, LOGWARN, "SWRC_check_parameters_for_vanGenuchten1980(): invalid value of " - "theta(saturated, matric, [cm/cm]) = %f (must within 0-1)\n", + "theta(saturated, matric, [cm/cm]) = %f (must be within 0-1)\n", swrcp[1] ); } diff --git a/SW_SoilWater.c b/SW_SoilWater.c index 189481b3a..13961653f 100644 --- a/SW_SoilWater.c +++ b/SW_SoilWater.c @@ -1257,7 +1257,7 @@ RealD SW_SnowDepth(RealD SWE, RealD snowdensity) { 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, @@ -1292,7 +1292,7 @@ RealD SW_SWRC_SWCtoSWP(RealD swcBulk, SW_LAYER_INFO *lyr) { other applications may want to warn only (`LOGWARN`) and return. @return Soil water potential [-bar] -**/ +*/ double SWRC_SWCtoSWP( double swcBulk, unsigned int swrc_type, @@ -1380,7 +1380,7 @@ double SWRC_SWCtoSWP( other applications may want to warn only (`LOGWARN`) and return. @return Soil water potential [-bar] -**/ +*/ double SWRC_SWCtoSWP_Campbell1974( double swcBulk, double *swrcp, @@ -1447,7 +1447,7 @@ double SWRC_SWCtoSWP_Campbell1974( other applications may want to warn only (`LOGWARN`) and return. @return Soil water potential [-bar] -**/ +*/ double SWRC_SWCtoSWP_vanGenuchten1980( double swcBulk, double *swrcp, @@ -1470,7 +1470,7 @@ double SWRC_SWCtoSWP_vanGenuchten1980( 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 H20 at 4 C; value from `soilDB::KSSL_VG_model()`] to [bar] + // convert [cm of H2O at 4 C; value from `soilDB::KSSL_VG_model()`] to [bar] res /= 1019.716; } else if (EQ(theta, swrcp[1])) { @@ -1598,7 +1598,7 @@ double SWRC_SWCtoSWP_FXW( 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, @@ -1634,7 +1634,7 @@ RealD SW_SWRC_SWPtoSWC(RealD swpMatric, SW_LAYER_INFO *lyr) { 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, @@ -1709,7 +1709,7 @@ double SWRC_SWPtoSWC( @param[in] width Soil layer width [cm] @return Soil water content in the layer [cm] -**/ +*/ double SWRC_SWPtoSWC_Campbell1974( double swpMatric, double *swrcp, @@ -1753,7 +1753,7 @@ double SWRC_SWPtoSWC_Campbell1974( @param[in] width Soil layer width [cm] @return Soil water content in the layer [cm] -**/ +*/ double SWRC_SWPtoSWC_vanGenuchten1980( double swpMatric, double *swrcp, @@ -1762,7 +1762,7 @@ double SWRC_SWPtoSWC_vanGenuchten1980( ) { double phi, tmp, res; - // convert SWP [-bar] to phi [cm of H20 at 4 C; + // convert SWP [-bar] to phi [cm of H2O at 4 C; // value from `soilDB::KSSL_VG_model()`] phi = swpMatric * 1019.716; From f4d0425d7c40bb45cc9393c3e5dca127c7376e5d Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Wed, 29 Jun 2022 13:31:34 -0400 Subject: [PATCH 043/326] Made `allHist` a global to hold all weather data - Added `allHist` double pointer to SW_WEATHER - Adjusted `_read_weather_hist` to store weather in year it is called with --- SW_Weather.c | 11 +++++------ SW_Weather.h | 1 + 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 989824485..cf796decf 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -470,7 +470,7 @@ void SW_WTH_read(void) { @return `swTRUE`/`swFALSE` if historical daily meteorological inputs are successfully/unsuccessfully read in. */ -Bool _read_weather_hist(TimeInt year) { +Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather) { /* =================================================== */ /* Read the historical (measured) weather files. * Format is @@ -486,7 +486,6 @@ Bool _read_weather_hist(TimeInt year) { * */ - SW_WEATHER_HIST *wh = &SW_Weather.hist; FILE *f; int x, lineno = 0, doy; // TimeInt mon, j, k = 0; @@ -520,10 +519,10 @@ Bool _read_weather_hist(TimeInt year) { /* --- 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; + yearWeather->temp_max[doy] = tmpmax; + yearWeather->temp_min[doy] = tmpmin; + yearWeather->temp_avg[doy] = (tmpmax + tmpmin) / 2.0; + yearWeather->ppt[doy] = ppt; // Calculate annual average temperature based on historical input, i.e., // the `temp_year_avg` calculated here is prospective and unsuitable when diff --git a/SW_Weather.h b/SW_Weather.h index f689b3921..fc9f144cd 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -85,6 +85,7 @@ typedef struct { *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_HIST **allHist; SW_WEATHER_2DAYS now; } SW_WEATHER; From 4bb5eb934219e6492eea181966a315cff2f3190b Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Wed, 29 Jun 2022 13:47:41 -0400 Subject: [PATCH 044/326] Weather data is now read in all at once - Created function `SW_WTH_read` that allocates allHist and calls `readAllWeather` - Created function `readAllWeather` which: * Reads in all weather history at once * Handles the case of missing weather (moved from and deleted _todays_weth) * Scales data depending on month (moved from `SW_WTH_new_day`) - Deleted `SW_WTH_new_year` as it is no longer needed --- SW_Control.c | 10 +- SW_Weather.c | 258 +++++++++++++++++++++++++++------------------------ SW_Weather.h | 5 +- 3 files changed, 145 insertions(+), 128 deletions(-) diff --git a/SW_Control.c b/SW_Control.c index a3a77b344..20836c9e4 100644 --- a/SW_Control.c +++ b/SW_Control.c @@ -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 @@ -250,9 +249,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(); @@ -267,6 +266,11 @@ void SW_CTL_read_inputs_from_disk(void) { #endif } + SW_WTH_read(); + #ifdef SWDEBUG + if (debug) swprintf(" > 'weather read'"); + #endif + SW_VPD_read(); #ifdef SWDEBUG if (debug) swprintf(" > 'veg'"); diff --git a/SW_Weather.c b/SW_Weather.c index cf796decf..38b4af419 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -103,58 +103,109 @@ static void _update_yesterday(void) { wn->rain[Yesterday] = wn->rain[Today]; } +/** + @brief Reads in all weather data through all years and stores them in global SW_Weather's `allHist` + + @param[out] allHist 2D array holding all weather data gathered + @param[in] startYear Start year of the simulation + @param[in] endYear End year of the simulation + + @note Function requires SW_MKV_today, SW_Weather.scale_temp_max and SW_Weather.scale_temp_min + + */ -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.; - } - } - } - +void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, int endYear) { + + int yearIndex, year, day, numYears = endYear - startYear + 1, yearDays, prevYearDays, + monthDays, month, currentMonDays; + + double yesterdayPPT = 0., yesterdayMin = 0., yesterdayMax = 0.; + + Bool weth_found = swFALSE, no_missing = swTRUE; + + for(yearIndex = 0; yearIndex < numYears; yearIndex++) { + year = yearIndex + startYear; + yearDays = isleapyear(year) ? 366 : 365; + monthDays = 31; + month = 0; + currentMonDays = 0; + + if(!SW_Weather.use_weathergenerator_only) { + #ifdef RSOILWAT + weth_found = onSet_WTH_DATA_YEAR(year, allHist[yearIndex]); + #else + weth_found = _read_weather_hist(year, allHist[yearIndex]); + #endif + } + for(day = 0; day < yearDays; day++) { + monthDays++; + if(currentMonDays == monthDays - 1) { + month++; + monthDays = (month == Feb) ? (isleapyear(year) ? 29 : 28) : Time_days_in_month(month); + } + /* --------------------------------------------------- */ + /* 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. + */ + if (!weth_found) { + if(SW_Weather.use_weathergenerator) { + // no weather input file for current year ==> use 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 { + // TODO: Make error message + } + + } else { + // weather input file for current year available + + no_missing = (Bool) (!missing(allHist[yearIndex]->temp_max[day]) && + !missing(allHist[yearIndex]->temp_min[day]) && + !missing(allHist[yearIndex]->ppt[day])); + if(!no_missing) { + // some of today's values are missing + + if (SW_Weather.use_weathergenerator) { + // if weather generator is turned on then use it for all values + allHist[yearIndex]->ppt[day] = yesterdayPPT; + SW_MKV_today(day, &allHist[yearIndex]->temp_max[day], + &allHist[yearIndex]->temp_min[day], &allHist[yearIndex]->ppt[day]); + + } else { + // impute missing values with 0 for precipitation and + // with LOCF for temperature (i.e., last-observation-carried-forward) + allHist[yearIndex]->temp_max[day] = (!missing(allHist[yearIndex]->temp_max[day])) ? + allHist[yearIndex]->temp_max[day] : yesterdayMax; + + allHist[yearIndex]->temp_min[day] = (!missing(allHist[yearIndex]->temp_min[day])) ? + allHist[yearIndex]->temp_min[day] : yesterdayMin; + + allHist[yearIndex]->ppt[day] = (!missing(allHist[yearIndex]->ppt[day])) ? + allHist[yearIndex]->ppt[day] : 0.; + } + } + } + /* scale the weather according to monthly factors */ + allHist[yearIndex]->temp_max[day] += SW_Weather.scale_temp_max[month]; + allHist[yearIndex]->temp_min[day] += SW_Weather.scale_temp_min[month]; + + allHist[yearIndex]->ppt[day] *= SW_Weather.scale_precip[month]; + + yesterdayPPT = allHist[yearIndex]->ppt[day];; + yesterdayMax = allHist[yearIndex]->temp_max[day]; + yesterdayMin = allHist[yearIndex]->temp_min[day]; + } + + prevYearDays = yearDays; + + } } @@ -250,56 +301,6 @@ void SW_WTH_init_run(void) { 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'. */ @@ -325,10 +326,9 @@ void SW_WTH_new_day(void) { * 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; + SW_WEATHER *w = &SW_Weather; + SW_WEATHER_2DAYS *wn = &SW_Weather.now; + TimeInt day = SW_Model.doy - 1, year = SW_Model.year - SW_Model.startyr; #ifdef STEPWAT /* @@ -338,32 +338,29 @@ void SW_WTH_new_day(void) { */ #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]; + /* get the daily weather from allHist */ + wn->temp_max[Today] = w->allHist[year]->temp_max[day]; + wn->temp_min[Today] = w->allHist[year]->temp_min[day]; + wn->ppt[Today] = w->allHist[year]->ppt[day]; - wn->temp_avg[Today] = (wn->temp_max[Today] + wn->temp_min[Today]) / 2.; + wn->temp_avg[Today] = (SW_Weather.now.temp_max[Today] + SW_Weather.now.temp_min[Today]) / 2.; - ppt *= w->scale_precip[month]; + w->snow = w->snowmelt = w->snowloss = 0.; + w->snowRunoff = w->surfaceRunoff = w->surfaceRunon = w->soil_inf = 0.; - 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); - } + 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); + } else { + wn->rain[Today] = wn->ppt[Today]; + } } /** @brief Reads in file for SW_Weather. */ -void SW_WTH_read(void) { +void SW_WTH_setup(void) { /* =================================================== */ SW_WEATHER *w = &SW_Weather; const int nitems = 17; @@ -454,6 +451,21 @@ void SW_WTH_read(void) { /* else we assume weather files match model run years */ } +void SW_WTH_read(void) { + + int numYears = SW_Model.endyr - SW_Model.startyr + 1, year; + + SW_Weather.allHist = (SW_WEATHER_HIST **)malloc(sizeof(SW_WEATHER_HIST *) * numYears); + + for(year = 0; year < numYears; year++) { + + SW_Weather.allHist[year] = (SW_WEATHER_HIST *)malloc(sizeof(SW_WEATHER_HIST)); + } + + readAllWeather(SW_Weather.allHist, SW_Model.startyr, SW_Model.endyr); + +} + /** @brief Read the historical (observed) weather file for a simulation year. diff --git a/SW_Weather.h b/SW_Weather.h index fc9f144cd..5a4f88b5e 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -101,14 +101,15 @@ extern SW_WEATHER SW_Weather; /* =================================================== */ /* Global Function Declarations */ /* --------------------------------------------------- */ +void SW_WTH_setup(void); void SW_WTH_read(void); -Bool _read_weather_hist(TimeInt year); +Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather); +void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, int endYear); 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); From 9f96ed66b5e6f5303004bcde04eeb82de8dd498c Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Wed, 29 Jun 2022 13:49:23 -0400 Subject: [PATCH 045/326] Added deallocation of allHist - Created function that is called by `SW_WTH_deconstruct` to deallocated allHist --- SW_Weather.c | 19 +++++++++++++++++++ SW_Weather.h | 1 + 2 files changed, 20 insertions(+) diff --git a/SW_Weather.c b/SW_Weather.c index 38b4af419..af02d9b5e 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -261,6 +261,7 @@ void SW_WTH_construct(void) { void SW_WTH_deconstruct(void) { OutPeriod pd; + int numYears = SW_Model.endyr - SW_Model.startyr; // De-allocate output structures: ForEachOutPeriod(pd) @@ -279,6 +280,24 @@ void SW_WTH_deconstruct(void) if (SW_Weather.use_weathergenerator) { SW_MKV_deconstruct(); } + deallocateAllHistory(numYears); +} + +/** + @brief Helper function to SW_WTH_deconstruct to deallocate allHist array. + + @param numYears Number of years the simulation spans to deallocate + */ + +void deallocateAllHistory(int numYears) { + int year; + + for(year = 0; year < numYears; year++) { + free(SW_Weather.allHist[year]); + } + + free(SW_Weather.allHist); + } /** diff --git a/SW_Weather.h b/SW_Weather.h index 5a4f88b5e..011c83fa8 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -105,6 +105,7 @@ void SW_WTH_setup(void); void SW_WTH_read(void); Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather); void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, int endYear); +void deallocateAllHistory(int numYears); void _clear_hist_weather(void); void SW_WTH_init_run(void); void SW_WTH_construct(void); From 1dfe9285258ba53b881a1680b11f36d9cfdc5d2d Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Wed, 29 Jun 2022 13:51:57 -0400 Subject: [PATCH 046/326] Moved weth_found to `readAllWeather` - Deleted static version of weth_found and is now in `readAllWeather` --- SW_Weather.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index af02d9b5e..9718bdabb 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -72,14 +72,6 @@ SW_WEATHER SW_Weather; 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 */ From ab6bbe51080af3e588d8c8420063e247de4c8015 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Wed, 29 Jun 2022 13:53:21 -0400 Subject: [PATCH 047/326] Unit tests for new weather related functions --- test/test_SW_Weather.cc | 44 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 test/test_SW_Weather.cc diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc new file mode 100644 index 000000000..e1d33b9c6 --- /dev/null +++ b/test/test_SW_Weather.cc @@ -0,0 +1,44 @@ +#include "gtest/gtest.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../SW_Weather.h" +#include "sw_testhelpers.h" + +namespace { + + TEST(ReadAllWeatherTest, DefaultValues) { + + // Testing to fill allHist from `SW_Weather` + readAllWeather(SW_Weather.allHist, 1980, 2010); + + 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); + + } + + TEST(WeatherReadTest, Initialization) { + + SW_WTH_read(); + + EXPECT_FLOAT_EQ(SW_Weather.allHist[0]->temp_max[0], -.52); + + } + + +} From e2cc61ce101580251ed46186f17671e3e7a627fe Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Wed, 29 Jun 2022 23:26:48 -0400 Subject: [PATCH 048/326] Added chunk of documentation to `readAllWeather()` - Added most of documentation from old `SW_WTH_new_year()` * Describing how SOILWAT handles missing weather data --- SW_Weather.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/SW_Weather.c b/SW_Weather.c index 9718bdabb..5cb3b73f0 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -98,6 +98,20 @@ static void _update_yesterday(void) { /** @brief Reads in all weather data through all years and stores them in global SW_Weather's `allHist` + 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 + @param[out] allHist 2D array holding all weather data gathered @param[in] startYear Start year of the simulation @param[in] endYear End year of the simulation From f0dbb1edfcb5b2346e9a25433b479aad7913f05f Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Wed, 29 Jun 2022 23:28:44 -0400 Subject: [PATCH 049/326] Fixed syntax mistake and temp_avg calc location in `SW_Weather` --- SW_Weather.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 5cb3b73f0..389db7601 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -201,10 +201,12 @@ void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, int endYear) { /* scale the weather according to monthly factors */ allHist[yearIndex]->temp_max[day] += SW_Weather.scale_temp_max[month]; allHist[yearIndex]->temp_min[day] += SW_Weather.scale_temp_min[month]; + allHist[yearIndex]->temp_avg[day] += (SW_Weather.now.temp_max[Today] + + SW_Weather.now.temp_min[Today]) / 2.; allHist[yearIndex]->ppt[day] *= SW_Weather.scale_precip[month]; - yesterdayPPT = allHist[yearIndex]->ppt[day];; + yesterdayPPT = allHist[yearIndex]->ppt[day]; yesterdayMax = allHist[yearIndex]->temp_max[day]; yesterdayMin = allHist[yearIndex]->temp_min[day]; } @@ -368,7 +370,7 @@ void SW_WTH_new_day(void) { wn->temp_min[Today] = w->allHist[year]->temp_min[day]; wn->ppt[Today] = w->allHist[year]->ppt[day]; - wn->temp_avg[Today] = (SW_Weather.now.temp_max[Today] + SW_Weather.now.temp_min[Today]) / 2.; + wn->temp_avg[Today] = w->allHist[year]->temp_avg[day]; w->snow = w->snowmelt = w->snowloss = 0.; w->snowRunoff = w->surfaceRunoff = w->surfaceRunon = w->soil_inf = 0.; From 31033cb2039e6a70133a42cf339af68921a6cad0 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 30 Jun 2022 10:43:47 -0400 Subject: [PATCH 050/326] Removed unused "prevYearDays" from `readAllWeather()` --- SW_Weather.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 389db7601..4128963bf 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -122,7 +122,7 @@ static void _update_yesterday(void) { void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, int endYear) { - int yearIndex, year, day, numYears = endYear - startYear + 1, yearDays, prevYearDays, + int yearIndex, year, day, numYears = endYear - startYear + 1, yearDays, monthDays, month, currentMonDays; double yesterdayPPT = 0., yesterdayMin = 0., yesterdayMax = 0.; @@ -211,8 +211,6 @@ void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, int endYear) { yesterdayMin = allHist[yearIndex]->temp_min[day]; } - prevYearDays = yearDays; - } } From 5508038dc3f3b5ee153153a80cb659a5b5e72091 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 1 Jul 2022 15:20:10 -0400 Subject: [PATCH 051/326] Added new global "n_years" in `SW_Weather` - Instead of calculating number of years multiple times, it is calculated once - n_years is initialized to 0 --- SW_Weather.c | 19 ++++++++++--------- SW_Weather.h | 1 + 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 4128963bf..dffb933c9 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -120,16 +120,16 @@ static void _update_yesterday(void) { */ -void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, int endYear) { +void readAllWeather(SW_WEATHER_HIST **allHist, int startYear) { - int yearIndex, year, day, numYears = endYear - startYear + 1, yearDays, - monthDays, month, currentMonDays; + int day, yearDays, monthDays, month, currentMonDays, year; + unsigned int yearIndex; double yesterdayPPT = 0., yesterdayMin = 0., yesterdayMax = 0.; Bool weth_found = swFALSE, no_missing = swTRUE; - for(yearIndex = 0; yearIndex < numYears; yearIndex++) { + for(yearIndex = 0; yearIndex < SW_Weather.n_years; yearIndex++) { year = yearIndex + startYear; yearDays = isleapyear(year) ? 366 : 365; monthDays = 31; @@ -259,6 +259,7 @@ void SW_WTH_construct(void) { sizeof(SW_WEATHER_OUTPUTS), "SW_WTH_construct()"); } } + SW_Weather.n_years = 0; } /** @@ -267,7 +268,6 @@ void SW_WTH_construct(void) { void SW_WTH_deconstruct(void) { OutPeriod pd; - int numYears = SW_Model.endyr - SW_Model.startyr; // De-allocate output structures: ForEachOutPeriod(pd) @@ -478,16 +478,17 @@ void SW_WTH_setup(void) { void SW_WTH_read(void) { - int numYears = SW_Model.endyr - SW_Model.startyr + 1, year; + SW_Weather.n_years = SW_Model.endyr - SW_Model.startyr + 1; + unsigned int year; - SW_Weather.allHist = (SW_WEATHER_HIST **)malloc(sizeof(SW_WEATHER_HIST *) * numYears); + SW_Weather.allHist = (SW_WEATHER_HIST **)malloc(sizeof(SW_WEATHER_HIST *) * SW_Weather.n_years); - for(year = 0; year < numYears; year++) { + for(year = 0; year < SW_Weather.n_years; year++) { SW_Weather.allHist[year] = (SW_WEATHER_HIST *)malloc(sizeof(SW_WEATHER_HIST)); } - readAllWeather(SW_Weather.allHist, SW_Model.startyr, SW_Model.endyr); + readAllWeather(SW_Weather.allHist, SW_Model.startyr); } diff --git a/SW_Weather.h b/SW_Weather.h index 011c83fa8..f9cd6ebce 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -68,6 +68,7 @@ typedef struct { // swFALSE: fail if any weather input is missing (values/files) use_snow; RealD pct_snowdrift, pct_snowRunoff; + unsigned int n_years; SW_TIMES yr; RealD scale_precip[MAX_MONTHS], From 4bec9ad84aab1e3d5e825346e354d1f8b70d9deb Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 1 Jul 2022 15:22:30 -0400 Subject: [PATCH 052/326] Fixed calculations in `readAllWeather` - Fixed how the months days are incremented - Fixed "temp_avg" calculations --- SW_Weather.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index dffb933c9..7e2400b60 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -144,11 +144,12 @@ void readAllWeather(SW_WEATHER_HIST **allHist, int startYear) { #endif } for(day = 0; day < yearDays; day++) { - monthDays++; - if(currentMonDays == monthDays - 1) { + if(currentMonDays == monthDays) { month++; monthDays = (month == Feb) ? (isleapyear(year) ? 29 : 28) : Time_days_in_month(month); + currentMonDays = 0; } + currentMonDays++; /* --------------------------------------------------- */ /* If use_weathergenerator = swFALSE and no weather file found, we won't * get this far because the new_year() will fail, so if @@ -201,8 +202,8 @@ void readAllWeather(SW_WEATHER_HIST **allHist, int startYear) { /* scale the weather according to monthly factors */ allHist[yearIndex]->temp_max[day] += SW_Weather.scale_temp_max[month]; allHist[yearIndex]->temp_min[day] += SW_Weather.scale_temp_min[month]; - allHist[yearIndex]->temp_avg[day] += (SW_Weather.now.temp_max[Today] + - SW_Weather.now.temp_min[Today]) / 2.; + allHist[yearIndex]->temp_avg[day] = (SW_Weather.scale_temp_max[day] + + SW_Weather.scale_temp_min[day]) / 2.; allHist[yearIndex]->ppt[day] *= SW_Weather.scale_precip[month]; From 8114724ecdd856ceaf9bd4aaaa54e3a6f86e47fe Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 1 Jul 2022 15:24:24 -0400 Subject: [PATCH 053/326] Removed numYears from `dellocateAllHistory` - Removed numYears from `deallocateAllHistory` header - Added `isnull` to prevent empty lists from attempting to free itself --- SW_Weather.c | 17 ++++++++++------- SW_Weather.h | 4 ++-- test/test_SW_Weather.cc | 2 +- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 7e2400b60..1a2ad9c47 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -287,7 +287,7 @@ void SW_WTH_deconstruct(void) if (SW_Weather.use_weathergenerator) { SW_MKV_deconstruct(); } - deallocateAllHistory(numYears); + deallocateAllHistory(); } /** @@ -296,15 +296,18 @@ void SW_WTH_deconstruct(void) @param numYears Number of years the simulation spans to deallocate */ -void deallocateAllHistory(int numYears) { - int year; +void deallocateAllHistory(void) { + unsigned int year; - for(year = 0; year < numYears; year++) { - free(SW_Weather.allHist[year]); + if(!isnull(SW_Weather.allHist)) { + for(year = 0; year < SW_Weather.n_years; year++) { + free(SW_Weather.allHist[year]); + } + + free(SW_Weather.allHist); + } - free(SW_Weather.allHist); - } /** diff --git a/SW_Weather.h b/SW_Weather.h index f9cd6ebce..d820ec321 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -105,8 +105,8 @@ extern SW_WEATHER SW_Weather; void SW_WTH_setup(void); void SW_WTH_read(void); Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather); -void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, int endYear); -void deallocateAllHistory(int numYears); +void readAllWeather(SW_WEATHER_HIST **allHist, int startYear); +void deallocateAllHistory(void); void _clear_hist_weather(void); void SW_WTH_init_run(void); void SW_WTH_construct(void); diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index e1d33b9c6..3e7295414 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -23,7 +23,7 @@ namespace { TEST(ReadAllWeatherTest, DefaultValues) { // Testing to fill allHist from `SW_Weather` - readAllWeather(SW_Weather.allHist, 1980, 2010); + readAllWeather(SW_Weather.allHist, 1980); EXPECT_NEAR(SW_Weather.allHist[0]->temp_max[0], -0.520000, tol6); EXPECT_NEAR(SW_Weather.allHist[0]->temp_avg[0], -8.095000, tol6); From 8a938a9e2b2fa5eb6da5f28a1b8ef31c3dc32295 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 1 Jul 2022 22:12:13 -0400 Subject: [PATCH 054/326] Fixed error causing bug by fixing variable `temp_avg` - Swapped SW_Weather.scale_temp_max/min for allHist[yearIndex]->temp_max/min --- SW_Weather.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 1a2ad9c47..402cc73af 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -202,8 +202,8 @@ void readAllWeather(SW_WEATHER_HIST **allHist, int startYear) { /* scale the weather according to monthly factors */ allHist[yearIndex]->temp_max[day] += SW_Weather.scale_temp_max[month]; allHist[yearIndex]->temp_min[day] += SW_Weather.scale_temp_min[month]; - allHist[yearIndex]->temp_avg[day] = (SW_Weather.scale_temp_max[day] + - SW_Weather.scale_temp_min[day]) / 2.; + allHist[yearIndex]->temp_avg[day] = (allHist[yearIndex]->temp_max[day] + + allHist[yearIndex]->temp_min[day]) / 2.; allHist[yearIndex]->ppt[day] *= SW_Weather.scale_precip[month]; From 392a5d674f1c074836a9e9b03c8897ce7f97fbab Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 1 Jul 2022 22:24:07 -0400 Subject: [PATCH 055/326] Added STEPWAT years to `SW_Weather.c` - When running STEPWAT, n_years in `SW_Weather` will be the correct value --- SW_Weather.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/SW_Weather.c b/SW_Weather.c index 402cc73af..1e5ae7b77 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -56,6 +56,10 @@ #include "../rSW_Weather.h" #endif +#ifdef STEPWAT + #include "../ST_defines.h" +#endif + /* =================================================== */ @@ -64,7 +68,9 @@ SW_WEATHER SW_Weather; - +#ifdef STEPWAT +extern GlobalType SuperGlobals; +#endif /* =================================================== */ /* Local Variables */ @@ -482,7 +488,11 @@ void SW_WTH_setup(void) { void SW_WTH_read(void) { + #ifdef STEPWAT + SW_Weather.n_years = SuperGlobals.runModelYears; + #else SW_Weather.n_years = SW_Model.endyr - SW_Model.startyr + 1; + #endif unsigned int year; SW_Weather.allHist = (SW_WEATHER_HIST **)malloc(sizeof(SW_WEATHER_HIST *) * SW_Weather.n_years); From 987f76ee9b2fa45ce55fd99ed0fc7307cddb32af Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 5 Jul 2022 12:31:36 -0400 Subject: [PATCH 056/326] Updated what is included when STEPWAT is defined - Swapped `ST_defines.h` for `SW_globals.h` - Removed extern `SuperGlobals` to prevent duplicates --- SW_Weather.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 1e5ae7b77..ad1fad128 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -57,7 +57,7 @@ #endif #ifdef STEPWAT - #include "../ST_defines.h" + #include "../ST_globals.h" // externs `SuperGlobals #endif @@ -68,10 +68,6 @@ SW_WEATHER SW_Weather; -#ifdef STEPWAT -extern GlobalType SuperGlobals; -#endif - /* =================================================== */ /* Local Variables */ /* --------------------------------------------------- */ From a64f2772f2be0bfe445bd348f38a444b2d54351e Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 5 Jul 2022 12:35:04 -0400 Subject: [PATCH 057/326] Removed global usage of `n_years` in `readAllWeather()` - Updated header of `readAllWeather()` to accept `n_years` - Replaced instance of using `SW_Weather.n_years` with `n_years` from header - Updated unit test to include `SW_Weather.n_years` in call to `readAllWeather()` --- SW_Weather.c | 6 +++--- SW_Weather.h | 2 +- test/test_SW_Weather.cc | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index ad1fad128..04bb3205b 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -122,7 +122,7 @@ static void _update_yesterday(void) { */ -void readAllWeather(SW_WEATHER_HIST **allHist, int startYear) { +void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, unsigned int n_years) { int day, yearDays, monthDays, month, currentMonDays, year; unsigned int yearIndex; @@ -131,7 +131,7 @@ void readAllWeather(SW_WEATHER_HIST **allHist, int startYear) { Bool weth_found = swFALSE, no_missing = swTRUE; - for(yearIndex = 0; yearIndex < SW_Weather.n_years; yearIndex++) { + for(yearIndex = 0; yearIndex < n_years; yearIndex++) { year = yearIndex + startYear; yearDays = isleapyear(year) ? 366 : 365; monthDays = 31; @@ -498,7 +498,7 @@ void SW_WTH_read(void) { SW_Weather.allHist[year] = (SW_WEATHER_HIST *)malloc(sizeof(SW_WEATHER_HIST)); } - readAllWeather(SW_Weather.allHist, SW_Model.startyr); + readAllWeather(SW_Weather.allHist, SW_Model.startyr, SW_Weather.n_years); } diff --git a/SW_Weather.h b/SW_Weather.h index d820ec321..1bc80b62b 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -105,7 +105,7 @@ extern SW_WEATHER SW_Weather; void SW_WTH_setup(void); void SW_WTH_read(void); Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather); -void readAllWeather(SW_WEATHER_HIST **allHist, int startYear); +void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, unsigned int n_years); void deallocateAllHistory(void); void _clear_hist_weather(void); void SW_WTH_init_run(void); diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 3e7295414..44ad77426 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -23,7 +23,7 @@ namespace { TEST(ReadAllWeatherTest, DefaultValues) { // Testing to fill allHist from `SW_Weather` - readAllWeather(SW_Weather.allHist, 1980); + readAllWeather(SW_Weather.allHist, 1980, SW_Weather.n_years); EXPECT_NEAR(SW_Weather.allHist[0]->temp_max[0], -0.520000, tol6); EXPECT_NEAR(SW_Weather.allHist[0]->temp_avg[0], -8.095000, tol6); From 9c3529f6287d83393375ba799fcf0677ba131991 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Wed, 6 Jul 2022 13:38:29 -0400 Subject: [PATCH 058/326] Fixed memory leak originating in `test_SW_Weather.cc` - Added call to `deallocateAllHistory()` to fix memory leak --- test/test_SW_Weather.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 44ad77426..7f4be7eb1 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -30,6 +30,7 @@ namespace { EXPECT_NEAR(SW_Weather.allHist[0]->temp_min[0], -15.670000, tol6); EXPECT_NEAR(SW_Weather.allHist[0]->ppt[0], .220000, tol6); + deallocateAllHistory(); } TEST(WeatherReadTest, Initialization) { From 6de989392b2fcd64762402fc3ff3b58adcd0819a Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Wed, 6 Jul 2022 13:45:09 -0400 Subject: [PATCH 059/326] Fixed headers/documentation in `SW_Weather.c` - Swapped `endYear` for `n_years` in documentation for `readAllWeather` - Deleted previously removed `numYears` parameter documentation - Added `yearWeather` documentation to `_read_weather_hist()` - Added descriptions to `_read_weather_hist()` parameters in documentation --- SW_Weather.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 04bb3205b..63999065b 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -116,7 +116,7 @@ static void _update_yesterday(void) { @param[out] allHist 2D array holding all weather data gathered @param[in] startYear Start year of the simulation - @param[in] endYear End year of the simulation + @param[in] n_years Number of years in simulation @note Function requires SW_MKV_today, SW_Weather.scale_temp_max and SW_Weather.scale_temp_min @@ -294,8 +294,6 @@ void SW_WTH_deconstruct(void) /** @brief Helper function to SW_WTH_deconstruct to deallocate allHist array. - - @param numYears Number of years the simulation spans to deallocate */ void deallocateAllHistory(void) { @@ -513,7 +511,8 @@ void SW_WTH_read(void) { @note Used by rSOILWAT2 - @param year + @param year Current year within the simulation + @param yearWeather Current year's weather array that is to be filled by function @return `swTRUE`/`swFALSE` if historical daily meteorological inputs are successfully/unsuccessfully read in. From 73cf098650ca9562aeed3ad9dd82ebc79081d4ac Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Thu, 7 Jul 2022 08:48:33 -0400 Subject: [PATCH 060/326] Update weather-related "WaterBalance" tests for new `readAllWeather()` - weather-related "WaterBalance" tests use non-default weather inputs * "WithWeatherGeneratorOnly" utilizes the weather generator for all values * "WithWeatherGeneratorForSomeMissingValues" utilizes available inputs from non-default source "Input/data_weather_missing" and uses the weather generator for missing values - updates related to `readAllWeather()` require that these tests prepare their weather data before running the simulation loop -- otherwise, they are using the "default" weather values previously obtained by `Reset_SOILWAT2_after_UnitTest()` and they would actually no longer perform the intended checks --- test/test_WaterBalance.cc | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/test/test_WaterBalance.cc b/test/test_WaterBalance.cc index 2b4533818..258bf4de6 100644 --- a/test/test_WaterBalance.cc +++ b/test/test_WaterBalance.cc @@ -112,13 +112,19 @@ namespace { 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; + 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 + deallocateAllHistory(); + SW_WTH_read(); + // Run the simulation SW_CTL_main(); @@ -146,6 +152,10 @@ namespace { // Point to partial weather data strcpy(SW_Weather.name_prefix, "Input/data_weather_missing/weath"); + // Prepare weather data + deallocateAllHistory(); + SW_WTH_read(); + // Run the simulation SW_CTL_main(); From fe14db10025ecc0f6efa59b40bcd8c06f34b89b4 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 7 Jul 2022 14:16:39 -0400 Subject: [PATCH 061/326] Changed name of `deallocateAllHistory()` to be specific --- SW_Weather.c | 4 ++-- SW_Weather.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 63999065b..89424ebb1 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -289,14 +289,14 @@ void SW_WTH_deconstruct(void) if (SW_Weather.use_weathergenerator) { SW_MKV_deconstruct(); } - deallocateAllHistory(); + deallocateAllWeather(); } /** @brief Helper function to SW_WTH_deconstruct to deallocate allHist array. */ -void deallocateAllHistory(void) { +void deallocateAllWeather(void) { unsigned int year; if(!isnull(SW_Weather.allHist)) { diff --git a/SW_Weather.h b/SW_Weather.h index 1bc80b62b..bc0768594 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -106,7 +106,7 @@ void SW_WTH_setup(void); void SW_WTH_read(void); Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather); void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, unsigned int n_years); -void deallocateAllHistory(void); +void deallocateAllWeather(void); void _clear_hist_weather(void); void SW_WTH_init_run(void); void SW_WTH_construct(void); From 2cad1c1026a3b48637df6e3540eafd3b2d383334 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 7 Jul 2022 14:24:18 -0400 Subject: [PATCH 062/326] `deallocateAllWeather()` is now called from `SW_WTH_read()` - Added deallocation call before allocation process occurs * NOTE: In SOILWAT2 runs alone, this does not change anything * Eliminates the chance of forgetting to call `deallocateAllWeather()` in tests --- SW_Weather.c | 2 ++ test/test_SW_Weather.cc | 1 - test/test_WaterBalance.cc | 2 -- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 89424ebb1..1916dbfe4 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -489,6 +489,8 @@ void SW_WTH_read(void) { #endif unsigned int year; + deallocateAllWeather(); + SW_Weather.allHist = (SW_WEATHER_HIST **)malloc(sizeof(SW_WEATHER_HIST *) * SW_Weather.n_years); for(year = 0; year < SW_Weather.n_years; year++) { diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 7f4be7eb1..44ad77426 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -30,7 +30,6 @@ namespace { EXPECT_NEAR(SW_Weather.allHist[0]->temp_min[0], -15.670000, tol6); EXPECT_NEAR(SW_Weather.allHist[0]->ppt[0], .220000, tol6); - deallocateAllHistory(); } TEST(WeatherReadTest, Initialization) { diff --git a/test/test_WaterBalance.cc b/test/test_WaterBalance.cc index 258bf4de6..8e7746a23 100644 --- a/test/test_WaterBalance.cc +++ b/test/test_WaterBalance.cc @@ -122,7 +122,6 @@ namespace { strcpy(SW_Weather.name_prefix, "Input/data_weather_nonexisting/weath"); // Prepare weather data - deallocateAllHistory(); SW_WTH_read(); // Run the simulation @@ -153,7 +152,6 @@ namespace { strcpy(SW_Weather.name_prefix, "Input/data_weather_missing/weath"); // Prepare weather data - deallocateAllHistory(); SW_WTH_read(); // Run the simulation From 1e352a4c44c4e99215970b9abd7ce6367243232b Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 7 Jul 2022 14:25:44 -0400 Subject: [PATCH 063/326] Made error message within `readAllWeather()` --- SW_Weather.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/SW_Weather.c b/SW_Weather.c index 1916dbfe4..3f32985ef 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -169,7 +169,9 @@ void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, unsigned int n_yea SW_MKV_today(day, &allHist[yearIndex]->temp_max[day], &allHist[yearIndex]->temp_min[day], &allHist[yearIndex]->ppt[day]); } else { - // TODO: Make error message + LogError(logfp, LOGWARN, "Weather was not found and user specification " + "for using weather generator is off. Cannot generate " + "weather for day %d of year %d\n", day, year); } } else { From 3466a4ae1539c03e33b95ed53b24551a81514720 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 7 Jul 2022 22:56:37 -0400 Subject: [PATCH 064/326] Created three new unit tests for `readAllWeather()` - Created unit tests for the following conditions of `readAllWeather()`: * Use of weather generator to fill in missing days * Use of weather generator to fill in missing years * Only use weather generator to fill weather data --- test/test_SW_Weather.cc | 70 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 44ad77426..9b63331a1 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -17,6 +17,8 @@ #include "../SW_Weather.h" #include "sw_testhelpers.h" +#include "../SW_Markov.h" +#include "../SW_Model.h" namespace { @@ -32,6 +34,74 @@ namespace { } + TEST(ReadAllWeatherTest, SomeMissingValuesDays) { + + SW_Weather.use_weathergenerator = swTRUE; + SW_MKV_setup(); + strcpy(SW_Weather.name_prefix, "Input/data_weather_missing/weath"); + + SW_WTH_read(); + + EXPECT_TRUE(!missing(SW_Weather.allHist[0]->temp_max[0])); + EXPECT_TRUE(!missing(SW_Weather.allHist[0]->temp_max[1])); + EXPECT_TRUE(!missing(SW_Weather.allHist[0]->temp_max[2])); + EXPECT_TRUE(!missing(SW_Weather.allHist[0]->temp_max[3])); + EXPECT_TRUE(!missing(SW_Weather.allHist[0]->temp_max[4])); + EXPECT_TRUE(!missing(SW_Weather.allHist[0]->temp_max[365])); + + Reset_SOILWAT2_after_UnitTest(); + + } + + TEST(ReadAllWeatherTest, SomeMissingValuesYears) { + + int year, day; + + SW_Weather.use_weathergenerator = swTRUE; + strcpy(SW_Weather.name_prefix, "Input/data_weather_missing/weath"); + SW_MKV_setup(); + + readAllWeather(SW_Weather.allHist, 1981, 2); + + SW_Model.startyr = 1981; + SW_Weather.n_years = 2; + + for(year = 0; year < 2; year++) { + for(day = 0; day < 365; day++) { + EXPECT_TRUE(!missing(SW_Weather.allHist[year]->temp_max[day])); + } + } + + SW_Model.startyr = 1980; + SW_Weather.n_years = 31; + + Reset_SOILWAT2_after_UnitTest(); + + } + + TEST(ReadAllWeatherTest, WeatherGeneratorOnly) { + + int year, day; + + SW_Weather.use_weathergenerator = swTRUE; + SW_Weather.use_weathergenerator_only = swTRUE; + + SW_MKV_setup(); + + strcpy(SW_Weather.name_prefix, "Input/data_weather_nonexisting/weath"); + + SW_WTH_read(); + + 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(WeatherReadTest, Initialization) { SW_WTH_read(); From 1b96402836ac6e0e621cce132cbb0e741fdff2de Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 8 Jul 2022 22:54:07 -0400 Subject: [PATCH 065/326] Modified `_clear_hist_weather()` to fill global `allHist` - `_clear_hist_weather()` now fills `allHist` instead of `SW_Weather.hist` - Now takes in `yearWeather` (current year's weather data) to fill with `MISSING` --- SW_Weather.c | 7 +++---- SW_Weather.h | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 3f32985ef..b2f8ea604 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -229,13 +229,12 @@ void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, unsigned int n_yea @brief Clears weather history. @note Used by rSOILWAT2 */ -void _clear_hist_weather(void) { +void _clear_hist_weather(SW_WEATHER_HIST *yearWeather) { /* --------------------------------------------------- */ - 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; + yearWeather->ppt[d] = yearWeather->temp_max[d] = yearWeather->temp_min[d] = SW_MISSING; } @@ -547,7 +546,7 @@ Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather) { sprintf(fname, "%s.%4d", SW_Weather.name_prefix, year); - _clear_hist_weather(); // clear values before returning + _clear_hist_weather(yearWeather); // clear values before returning if (NULL == (f = fopen(fname, "r"))) return swFALSE; diff --git a/SW_Weather.h b/SW_Weather.h index bc0768594..8a50be8be 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -107,7 +107,7 @@ void SW_WTH_read(void); Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather); void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, unsigned int n_years); void deallocateAllWeather(void); -void _clear_hist_weather(void); +void _clear_hist_weather(SW_WEATHER_HIST *yearWeather); void SW_WTH_init_run(void); void SW_WTH_construct(void); void SW_WTH_deconstruct(void); From 521f2443389c793681ccba0dec6153f9bd1db16c Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 8 Jul 2022 22:59:13 -0400 Subject: [PATCH 066/326] Set `SW_Weather.allHist` to NULL in `deallocateAllWeather()` - Eliminates chance of false "is not null" result when running tests --- SW_Weather.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SW_Weather.c b/SW_Weather.c index b2f8ea604..4078eb3c6 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -306,7 +306,7 @@ void deallocateAllWeather(void) { } free(SW_Weather.allHist); - + SW_Weather.allHist = NULL; } } From ab4fb949a6ba022f7f7308ba5db6715b9eeaf9b9 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 8 Jul 2022 23:00:50 -0400 Subject: [PATCH 067/326] Added comments for existing weather tests --- test/test_SW_Weather.cc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 9b63331a1..06dbdcc2e 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -27,6 +27,8 @@ namespace { // Testing to fill allHist from `SW_Weather` readAllWeather(SW_Weather.allHist, 1980, SW_Weather.n_years); + // 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); @@ -38,10 +40,14 @@ namespace { SW_Weather.use_weathergenerator = swTRUE; SW_MKV_setup(); + + // Change directory to get input files with some missing data strcpy(SW_Weather.name_prefix, "Input/data_weather_missing/weath"); SW_WTH_read(); + // With the use of 1980's missing values, test a few days of the year + // to make sure they are filled using the weather generator EXPECT_TRUE(!missing(SW_Weather.allHist[0]->temp_max[0])); EXPECT_TRUE(!missing(SW_Weather.allHist[0]->temp_max[1])); EXPECT_TRUE(!missing(SW_Weather.allHist[0]->temp_max[2])); @@ -66,6 +72,7 @@ namespace { SW_Model.startyr = 1981; SW_Weather.n_years = 2; + // 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])); @@ -88,10 +95,12 @@ namespace { 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(); + // 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])); From a6415cb5e7c6d7692bf027234089d2d6034a94c5 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 8 Jul 2022 23:05:40 -0400 Subject: [PATCH 068/326] New unit test in `test_SW_Weather.cc` and modified existing test - Added new test to check for missing yearly input - "SomeMissingValuesYears" test no longer directly calls `readAllWeather()` --- test/test_SW_Weather.cc | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 06dbdcc2e..abc8823d2 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -63,14 +63,18 @@ namespace { int year, day; + deallocateAllWeather(); + SW_Weather.use_weathergenerator = swTRUE; + + // Change directory to get input files with some missing data strcpy(SW_Weather.name_prefix, "Input/data_weather_missing/weath"); SW_MKV_setup(); - readAllWeather(SW_Weather.allHist, 1981, 2); - SW_Model.startyr = 1981; - SW_Weather.n_years = 2; + SW_Model.endyr = 1982; + + SW_WTH_read(); // Check everyday's value and test if it's `MISSING` for(year = 0; year < 2; year++) { @@ -79,9 +83,6 @@ namespace { } } - SW_Model.startyr = 1980; - SW_Weather.n_years = 31; - Reset_SOILWAT2_after_UnitTest(); } @@ -111,6 +112,32 @@ namespace { } + TEST(ReadAllWeatherTest, CheckMissingForMissingYear) { + + int day; + + deallocateAllWeather(); + + // Change directory to get input files with some missing data + strcpy(SW_Weather.name_prefix, "Input/data_weather_nonexisting/weath"); + + SW_Weather.use_weathergenerator = swFALSE; + SW_Weather.use_weathergenerator_only = swFALSE; + + SW_Model.startyr = 1981; + SW_Model.endyr = 1981; + + SW_WTH_read(); + + // Check everyday's value and test if it's `MISSING` + for(day = 0; day < 365; day++) { + EXPECT_TRUE(missing(SW_Weather.allHist[0]->temp_max[day])); + } + + Reset_SOILWAT2_after_UnitTest(); + + } + TEST(WeatherReadTest, Initialization) { SW_WTH_read(); From fb9f389073e346eb155887e8463ae0feca0c9b3f Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sat, 9 Jul 2022 14:59:05 -0400 Subject: [PATCH 069/326] Moved new `generic.c` functions - Added two functions from `feature_weather_before_sim` branch --- generic.c | 37 +++++++++++++++++++++++++++++++++++++ generic.h | 2 ++ 2 files changed, 39 insertions(+) diff --git a/generic.c b/generic.c index 459303d1b..51e86df4d 100644 --- a/generic.c +++ b/generic.c @@ -421,3 +421,40 @@ 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. + */ +double mean(double values[], int length) { + int index; + double total = 0.0; + for(index = 0; index < length; index++) { + total += values[index]; + } + return total / length; +} + +/** + @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. + + */ +double standardDeviation(double inputArray[], int length) { + + int index; + double arrayMean = mean(inputArray, length), total = 0.0; + + for(index = 0; index < length; index++) { + + total += (inputArray[index] - arrayMean) * (inputArray[index] - arrayMean); + } + + return sqrt(total / (length - 1)); + +} diff --git a/generic.h b/generic.h index 6bc33f0b2..417afd31c 100644 --- a/generic.h +++ b/generic.h @@ -242,6 +242,8 @@ 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 From 6037238af5ad73199482c541185a5d88b3f9e9f0 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sat, 9 Jul 2022 15:21:48 -0400 Subject: [PATCH 070/326] New vegetation user input flag - Added input in file `veg.in` to specify method of vegetation estimation - Changed file reading to accommodate new user input --- SW_VegProd.c | 68 +++++++++++++++++++++++++------------------- SW_VegProd.h | 3 +- testing/Input/veg.in | 5 +++- 3 files changed, 45 insertions(+), 31 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index 3277ac29e..9584a69ea 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -84,8 +84,8 @@ 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 + 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); @@ -94,8 +94,18 @@ void SW_VPD_read(void) { while (GetALine(f, inbuf)) { if (lineno++ < line_help) { switch (lineno) { + case 1: + x = sscanf(inbuf, "%d", &veg_method); + if(x != 1) { + sprintf(errstr, "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 1: + 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) { @@ -110,7 +120,7 @@ void SW_VPD_read(void) { break; /* albedo */ - case 2: + 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) { @@ -125,7 +135,7 @@ void SW_VPD_read(void) { break; /* canopy height */ - case 3: + 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) { @@ -137,7 +147,7 @@ void SW_VPD_read(void) { v->veg[k].cnpy.xinflec = help_veg[k]; } break; - case 4: + 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) { @@ -149,7 +159,7 @@ void SW_VPD_read(void) { v->veg[k].cnpy.yinflec = help_veg[k]; } break; - case 5: + 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) { @@ -161,7 +171,7 @@ void SW_VPD_read(void) { v->veg[k].cnpy.range = help_veg[k]; } break; - case 6: + 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) { @@ -173,7 +183,7 @@ void SW_VPD_read(void) { v->veg[k].cnpy.slope = help_veg[k]; } break; - case 7: + 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) { @@ -187,7 +197,7 @@ void SW_VPD_read(void) { break; /* vegetation interception parameters */ - case 8: + 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) { @@ -199,7 +209,7 @@ void SW_VPD_read(void) { v->veg[k].veg_kSmax = help_veg[k]; } break; - case 9: + 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) { @@ -213,7 +223,7 @@ void SW_VPD_read(void) { break; /* litter interception parameters */ - case 10: + 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) { @@ -227,7 +237,7 @@ void SW_VPD_read(void) { break; /* parameter for partitioning of bare-soil evaporation and transpiration */ - case 11: + 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) { @@ -241,7 +251,7 @@ void SW_VPD_read(void) { break; /* Parameter for scaling and limiting bare soil evaporation rate */ - case 12: + 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) { @@ -255,7 +265,7 @@ void SW_VPD_read(void) { break; /* shade effects */ - case 13: + 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) { @@ -267,7 +277,7 @@ void SW_VPD_read(void) { v->veg[k].shade_scale = help_veg[k]; } break; - case 14: + 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) { @@ -279,7 +289,7 @@ void SW_VPD_read(void) { v->veg[k].shade_deadmax = help_veg[k]; } break; - case 15: + 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) { @@ -291,7 +301,7 @@ void SW_VPD_read(void) { v->veg[k].tr_shade_effects.xinflec = help_veg[k]; } break; - case 16: + 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) { @@ -303,7 +313,7 @@ void SW_VPD_read(void) { v->veg[k].tr_shade_effects.yinflec = help_veg[k]; } break; - case 17: + 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) { @@ -315,7 +325,7 @@ void SW_VPD_read(void) { v->veg[k].tr_shade_effects.range = help_veg[k]; } break; - case 18: + 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) { @@ -329,7 +339,7 @@ void SW_VPD_read(void) { break; /* Hydraulic redistribution */ - case 19: + 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) { @@ -341,7 +351,7 @@ void SW_VPD_read(void) { v->veg[k].flagHydraulicRedistribution = (Bool) EQ(help_veg[k], 1.); } break; - case 20: + 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) { @@ -353,7 +363,7 @@ void SW_VPD_read(void) { v->veg[k].maxCondroot = help_veg[k]; } break; - case 21: + 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) { @@ -365,7 +375,7 @@ void SW_VPD_read(void) { v->veg[k].swpMatric50 = help_veg[k]; } break; - case 22: + 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) { @@ -379,7 +389,7 @@ void SW_VPD_read(void) { break; /* Critical soil water potential */ - case 23: + 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) { @@ -396,7 +406,7 @@ void SW_VPD_read(void) { /* CO2 Biomass Power Equation */ // Coefficient 1 - case 24: + 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) { @@ -409,7 +419,7 @@ void SW_VPD_read(void) { } break; // Coefficient 2 - case 25: + 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) { @@ -424,7 +434,7 @@ void SW_VPD_read(void) { /* CO2 WUE Power Equation */ // Coefficient 1 - case 26: + 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) { @@ -437,7 +447,7 @@ void SW_VPD_read(void) { } break; // Coefficient 2 - case 27: + 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) { diff --git a/SW_VegProd.h b/SW_VegProd.h index 1641530ca..77a4d217a 100644 --- a/SW_VegProd.h +++ b/SW_VegProd.h @@ -229,7 +229,8 @@ typedef struct { /** Flag that determines whether vegetation-type specific soil water availability should be calculated; user input from file `Input/outsetup.in` */ - use_SWA; + use_SWA, + veg_method; RealD // storing values in same order as defined in STEPWAT2/rgroup.in (0=tree, 1=shrub, 2=grass, 3=forb) diff --git a/testing/Input/veg.in b/testing/Input/veg.in index 51e67b316..240418d0a 100755 --- a/testing/Input/veg.in +++ b/testing/Input/veg.in @@ -7,7 +7,10 @@ # USER: Most of the other values in this file are parameters that # describe the four available vegetation types and should not be # modified unless a vegetation type itself is altered. - + +#---- Flag to activate/deactivate estimating vegetation based off weather +1 # 0 - Read in the values from file + # 1 - Estimate vegetation from weather #---- Composition of vegetation type components (0-1; must add up to 1) # Grasses Shrubs Trees Forbs BareGround From 290aa65af386768d559ff03ecd52f2753f28840f Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sat, 9 Jul 2022 15:33:47 -0400 Subject: [PATCH 071/326] Removed "Yesterday" index from `SW_WEATHER_2DAYS` - `SW_WEATHER_2DAYS` no longer has - Accommodated code for no longer needing a "Yesterday" index - Deleted `SW_WTH_end_day()` and `_update_yesterday()` --- SW_Control.c | 1 - SW_Flow.c | 12 ++++++------ SW_Output.c | 10 +++++----- SW_SoilWater.c | 2 +- SW_VegEstab.c | 2 +- SW_Weather.c | 35 +++++++++-------------------------- SW_Weather.h | 4 +--- 7 files changed, 23 insertions(+), 43 deletions(-) diff --git a/SW_Control.c b/SW_Control.c index 20836c9e4..03970f06d 100644 --- a/SW_Control.c +++ b/SW_Control.c @@ -76,7 +76,6 @@ static void _begin_day(void) { static void _end_day(void) { _collect_values(); - SW_WTH_end_day(); SW_SWC_end_day(); } diff --git a/SW_Flow.c b/SW_Flow.c index 9bcf5f6cb..293009bd8 100644 --- a/SW_Flow.c +++ b/SW_Flow.c @@ -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, @@ -408,7 +408,7 @@ void SW_Water_Flow(void) { x, SW_Sky.cloudcov_daily[doy], SW_Sky.r_humidity_daily[doy], - w->now.temp_avg[Today], + w->now.temp_avg, &sw->H_oh, &sw->H_ot, &sw->H_gh @@ -416,7 +416,7 @@ 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], @@ -444,7 +444,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 +837,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_Output.c b/SW_Output.c index f01717815..c6a3b481a 100644 --- a/SW_Output.c +++ b/SW_Output.c @@ -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; diff --git a/SW_SoilWater.c b/SW_SoilWater.c index 2b952ac36..78c7388f0 100644 --- a/SW_SoilWater.c +++ b/SW_SoilWater.c @@ -194,7 +194,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; diff --git a/SW_VegEstab.c b/SW_VegEstab.c index ccbf07805..649f9eb7b 100644 --- a/SW_VegEstab.c +++ b/SW_VegEstab.c @@ -235,7 +235,7 @@ static void _checkit(TimeInt doy, unsigned int sppnum) { 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) { diff --git a/SW_Weather.c b/SW_Weather.c index 4078eb3c6..5f27b99c1 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -79,15 +79,6 @@ static char *MyFileName; /* 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]; @@ -323,22 +314,14 @@ void SW_WTH_init_run(void) { * (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.ppt[Today] = SW_Weather.now.rain[Today] = 0.; + 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 Updates 'yesterday'. -*/ -void SW_WTH_end_day(void) { - /* =================================================== */ - _update_yesterday(); -} - /** @brief Guarantees that today's weather will not be invalid via -_todays_weth(). */ @@ -369,21 +352,21 @@ void SW_WTH_new_day(void) { #endif /* get the daily weather from allHist */ - wn->temp_max[Today] = w->allHist[year]->temp_max[day]; - wn->temp_min[Today] = w->allHist[year]->temp_min[day]; - wn->ppt[Today] = w->allHist[year]->ppt[day]; + wn->temp_max = w->allHist[year]->temp_max[day]; + wn->temp_min = w->allHist[year]->temp_min[day]; + wn->ppt = w->allHist[year]->ppt[day]; - wn->temp_avg[Today] = w->allHist[year]->temp_avg[day]; + wn->temp_avg = w->allHist[year]->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[Today], wn->temp_max[Today], wn->ppt[Today], - &wn->rain[Today], &w->snow, &w->snowmelt); + SW_SWC_adjust_snow(wn->temp_min, wn->temp_max, wn->ppt, + &wn->rain, &w->snow, &w->snowmelt); } else { - wn->rain[Today] = wn->ppt[Today]; + wn->rain = wn->ppt; } } diff --git a/SW_Weather.h b/SW_Weather.h index 8a50be8be..33f250d19 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -41,8 +41,7 @@ extern "C" { 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]; + RealD temp_avg, temp_max, temp_min, ppt, rain; } SW_WEATHER_2DAYS; typedef struct { @@ -113,7 +112,6 @@ void SW_WTH_construct(void); void SW_WTH_deconstruct(void); void SW_WTH_new_day(void); void SW_WTH_sum_today(void); -void SW_WTH_end_day(void); #ifdef DEBUG_MEM From d71e8dcd025c584f697375520c030d0d9f8c0068 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sat, 9 Jul 2022 15:39:11 -0400 Subject: [PATCH 072/326] Moved vegetation estimation wrapper function - Moved `estimateVegetationFromClimate()` from `feature_weather_before_sim` branch - Added check to call `estimateVegetationFromClimate()` --- SW_VegProd.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++------ SW_VegProd.h | 1 + 2 files changed, 84 insertions(+), 10 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index 9584a69ea..02f155b4b 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -53,6 +53,7 @@ changed _echo_inits() to now display the bare ground components in logfile.log #include "SW_Times.h" #include "SW_VegProd.h" #include "SW_Model.h" // externs SW_Model +#include "SW_Weather.h" @@ -582,18 +583,26 @@ void SW_VPD_construct(void) { void SW_VPD_init_run(void) { - TimeInt year; - int k; - - /* Set co2-multipliers to default */ - for (year = 0; year < MAX_NYEAR; year++) - { - ForEachVegType(k) + TimeInt year; + int k; + + SW_VEGPROD *veg = &SW_VegProd; + SW_MODEL *model = &SW_Model; + + /* Set co2-multipliers to default */ + for (year = 0; year < MAX_NYEAR; year++) { - SW_VegProd.veg[k].co2_multipliers[BIO_INDEX][year] = 1.; - SW_VegProd.veg[k].co2_multipliers[WUE_INDEX][year] = 1.; + ForEachVegType(k) + { + SW_VegProd.veg[k].co2_multipliers[BIO_INDEX][year] = 1.; + SW_VegProd.veg[k].co2_multipliers[WUE_INDEX][year] = 1.; + } } - } + + if(veg->veg_method) { + estimateVegetationFromClimate(veg, model->startyr, model->endyr); + } + } @@ -851,3 +860,67 @@ void get_critical_rank(void){ End of rank_SWPcrits ----------------------------------------------------------*/ } + +void estimateVegetationFromClimate(SW_VEGPROD *veg, int startYear, int endYear) { + + int numYears = endYear - startYear + 1, year, month; + + // Used for calculating climate + double **meanMonthlyTemp, **maxMonthlyTemp, **minMonthlyTemp, **meanMonthlyPPT, + MMP_cm[numYears], MMT_C[numYears], JulyMinTemp[numYears], degreeAbove65[numYears], sdC4[3], + PPTJuly[numYears], meanTempDryQuarter[numYears], minTempFebruary[numYears], sdCheatgrass[3]; + + int frostFreeDays[numYears]; + + // Used for average across years + double meanMonthlyTempAnn[MAX_MONTHS], maxMonthlyTempAnn[MAX_MONTHS], minMonthlyTempAnn[MAX_MONTHS], + meanMonthlyPPTAnn[MAX_MONTHS], MAP_cm, MAT_C; + + meanMonthlyTemp = (double **)malloc(sizeof(double *) * MAX_MONTHS); + maxMonthlyTemp = (double **)malloc(sizeof(double *) * MAX_MONTHS); + minMonthlyTemp = (double **)malloc(sizeof(double *) * MAX_MONTHS); + meanMonthlyPPT = (double **)malloc(sizeof(double *) * MAX_MONTHS); + + for(month = 0; month < MAX_MONTHS; month++) { + + meanMonthlyTemp[month] = (double *)malloc(sizeof(double) * numYears); + maxMonthlyTemp[month] = (double *)malloc(sizeof(double) * numYears); + minMonthlyTemp[month] = (double *)malloc(sizeof(double) * numYears); + meanMonthlyPPT[month] = (double *)malloc(sizeof(double) * numYears); + + } + + for(year = 0; year < numYears; year++) { + MMP_cm[year] = 0.; + MMT_C[year] = 0.; + JulyMinTemp[year] = 0.; + degreeAbove65[year] = 0.; + PPTJuly[year] = 0.; + meanTempDryQuarter[year] = 0.; + minTempFebruary[year] = 0.; + frostFreeDays[year] = 0.; + + } + + calcSiteClimate(SW_Weather.allHist, meanMonthlyTemp, maxMonthlyTemp, minMonthlyTemp, + meanMonthlyPPT, MMP_cm, MMT_C, JulyMinTemp, frostFreeDays, degreeAbove65, sdC4, + PPTJuly, meanTempDryQuarter, minTempFebruary, sdCheatgrass, numYears, startYear); + + averageAcrossYears(meanMonthlyTemp, maxMonthlyTemp, minMonthlyTemp, meanMonthlyPPT, meanMonthlyTempAnn, + maxMonthlyTempAnn, minMonthlyTempAnn, meanMonthlyPPTAnn, &MAP_cm, &MAT_C, MMT_C, + MMP_cm, numYears); + + // Clean up allocated memory + for(month = 0; month < MAX_MONTHS; month++) { + free(meanMonthlyTemp[month]); + free(maxMonthlyTemp[month]); + free(minMonthlyTemp[month]); + free(meanMonthlyPPT[month]); + } + + free(meanMonthlyTemp); + free(maxMonthlyTemp); + free(minMonthlyTemp); + free(meanMonthlyPPT); + +} diff --git a/SW_VegProd.h b/SW_VegProd.h index 77a4d217a..70f46937c 100644 --- a/SW_VegProd.h +++ b/SW_VegProd.h @@ -264,6 +264,7 @@ 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 *veg, int startYear, int endYear); void SW_VPD_init_run(void); void SW_VPD_deconstruct(void); void apply_biomassCO2effect(double* new_biomass, double *biomass, double multiplier); From bbbf6cc4045699d00910d9c84073b8cf9cede41a Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sat, 9 Jul 2022 15:43:42 -0400 Subject: [PATCH 073/326] Moved helper functions of `estimateVegetationFromClimate()` - Moved functions (from "feature_weather_before_sim" branch): * calcSiteClimate, FindDriestQtr, averageAcrossYears - These functions help calculate climate information to use for veg. estimation --- SW_Weather.c | 223 +++++++++++++++++++++++++++++++++++++++++++++++++-- SW_Weather.h | 11 +++ 2 files changed, 229 insertions(+), 5 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 5f27b99c1..ee99c32ff 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -79,13 +79,226 @@ static char *MyFileName; /* Local Function Definitions */ /* --------------------------------------------------- */ +/** + @brief Takes averages through the number of years of the calculated values from calc_SiteClimate + @param[in] meanMonthlyTemp 2D array containing all mean average monthly air temperature (deg;C) with dimensions + of row (months) size MAX_MONTHS and columns (years) of size numYears + @param[in] maxMonthlyTemp 2D array containing all mean max monthly air temperature (deg;C) with dimensions + of row (months) size MAX_MONTHS and columns (years) of size numYears + @param[in] minMonthlyTemp 2D array containing all mean min monthly air temperature (deg;C) with dimensions + of row (months) size MAX_MONTHS and columns (years) of size numYears + @param[out] meanMonthlyAvg Array containing sum of monthly mean temperatures + @param[out] meanMonthlyMax Array containing sum of monthly maximum temperatures + @param[out] meanMonthlyMin Array containing sum of monthly minimum temperatures + @param[out] meanMonthlyPPT Array containing sum of monthly mean precipitation + @param[out] MAT_C Value containing the sum of daily temperatures + @param[out] MAP_cm Value containing the sum of daily precipitation + @param[out] PPT_cm Array containing annual precipitation amount [cm] + @param[out] Temp_C Array containing annual temperatures [C] + @param[in] numYears Number of years we want to average across + */ + +void averageAcrossYears(double **meanMonthlyTemp, double **maxMonthlyTemp, + double **minMonthlyTemp, double **meanMonthlyPPT, double *meanMonthlyTempAnn, + double *maxMonthlyTempAnn, double *minMonthlyTempAnn, double *meanMonthlyPPTAnn, + double *MAP_cm, double *MAT_C, double MMT_C[], double MMP_cm[], int numYears) { + + int month; + + for(month = 0; month < MAX_MONTHS; month++) { + meanMonthlyTempAnn[month] = mean(meanMonthlyTemp[month], numYears); + maxMonthlyTempAnn[month] = mean(maxMonthlyTemp[month], numYears); + minMonthlyTempAnn[month] = mean(minMonthlyTemp[month], numYears); + meanMonthlyPPTAnn[month] = mean(meanMonthlyPPT[month], numYears); + } + + *MAP_cm = mean(MMP_cm, numYears); + *MAT_C = mean(MMT_C, numYears); +} + +/** + @brief Calculate climate variable from daily weather + @param[in] all_hist Array containing all historical data of a site + @param[out] meanMonthlyTemp 2D array containing all mean average monthly air temperature (deg;C) with + dimensions of row (months) size MAX_MONTHS and columns (years) of size numYears + @param[out] maxMonthlyTemp 2D array containing all mean max monthly air temperature (deg;C) with + dimensions of row (months) size MAX_MONTHS and columns (years) of size numYears + @param[out] minMonthlyTemp 2D array containing all mean min monthly air temperature (deg;C) with + dimensions of row (months) size MAX_MONTHS and columns (years) of size numYears + @param[out] meanMonthlyPPT 2D array containing all mean average monthly precipitation (cm) with dimensions + of row (months) size MAX_MONTHS and columns (years) of size numYears + @param[out] MMP_cm Array containing annual precipitation amount [cm] + @param[out] MMT_C Array containing annual temperatures [C] + @param[out] JulyMinTemp Array of size numYears holding minimum July temperatures for each year + @param[out] frostFreeDays Array of size numYears holding the maximum consecutive days in a year without frost for every year + @param[out] degreeAbove65 Array of size numYears holding the number of days in the year that is above 65F for every year + @param[out] sdC4 Array of size three holding the standard deviations of minimum July temperature (0), frost free days (1), number of days above 65F (2) + @param[out] PPTJuly Array of size numYears holding mean July precipitation (mm) for each year + @param[out] meanTempDryQuarter Array of size numYears holding the average temperature of the driest quarter of the year for every year + @param[out] minTempFebruary Array of size numYears holding the mean minimum temperature in february for every year + @param[out] sdCheatgrass Array of size 3 holding the standard deviations of July precipitation (0), mean + temperature of dry quarter (1), mean minimum temperature of February (2) + @param numYears[in] Number of years covered in the simulation + @param startYear[in] Start year of simulation + + */ + +void calcSiteClimate(SW_WEATHER_HIST **allHist, double **meanMonthlyTemp, double **maxMonthlyTemp, + double **minMonthlyTemp, double **meanMonthlyPPT, double *MMP_cm, double *MMT_C, + double *JulyMinTemp, int *frostFreeDays, double *degreeAbove65, double *sdC4, + double *PPTJuly, double *meanTempDryQuarter, double *minTempFebruary, + double *sdCheatgrass, int numYears, int startYear) { + + + int month, yearIndex, year, day, numDaysYear, numDaysMonth, currMonDay, + consecNonFrost, currentNonFrost, febDays, July = 6, February = 1; + + double currentTempMin, currentTempMean, totalAbove65, currentJulyMin, JulyPPT, + prevFrostMean, frostMean = 0, frostSqr = 0; + + for(month = 0; month < MAX_MONTHS; month++) { + memset(meanMonthlyTemp[month], 0., sizeof(double) * numYears); + memset(maxMonthlyTemp[month], 0., sizeof(double) * numYears); + memset(minMonthlyTemp[month], 0., sizeof(double) * numYears); + memset(meanMonthlyPPT[month], 0., sizeof(double) * numYears); + } + + for(yearIndex = 0; yearIndex < numYears; yearIndex++) { + year = yearIndex + startYear; + numDaysYear = isleapyear(year) ? 366 : 365; + febDays = isleapyear(year) ? 29 : 28; + month = 0; + currMonDay = 0; + numDaysMonth = 31; + currentJulyMin = 999; + totalAbove65 = 0; + currentNonFrost = 0; + consecNonFrost = 0; + JulyPPT = 0; + + for(day = 0; day < numDaysYear; day++) { + currMonDay++; + meanMonthlyTemp[month][yearIndex] += allHist[yearIndex]->temp_avg[day]; + maxMonthlyTemp[month][yearIndex] += allHist[yearIndex]->temp_max[day]; + minMonthlyTemp[month][yearIndex] += allHist[yearIndex]->temp_min[day]; + meanMonthlyPPT[month][yearIndex] += allHist[yearIndex]->ppt[day]; + + MMP_cm[yearIndex] += allHist[yearIndex]->ppt[day]; + MMT_C[yearIndex] += allHist[yearIndex]->temp_avg[day]; + + currentTempMin = allHist[yearIndex]->temp_min[day]; + currentTempMean = allHist[yearIndex]->temp_avg[day]; + + if(month == July){ + currentJulyMin = (currentTempMin < currentJulyMin) ? + currentTempMin : currentJulyMin; + JulyPPT += allHist[yearIndex]->ppt[day] * 10; + } + + if(currentTempMin > 0.0) { + currentNonFrost++; + } else if(currentNonFrost > consecNonFrost){ + consecNonFrost = currentNonFrost; + currentNonFrost = 0; + } else { + currentNonFrost = 0; + } + + if(month == February) { + minTempFebruary[yearIndex] += allHist[yearIndex]->temp_min[day]; + } - wn->temp_max[Yesterday] = wn->temp_max[Today]; - wn->temp_min[Yesterday] = wn->temp_min[Today]; - wn->temp_avg[Yesterday] = wn->temp_avg[Today]; + if(currMonDay == numDaysMonth) { + // Take the average of the current months values for current year + meanMonthlyTemp[month][yearIndex] /= numDaysMonth; + maxMonthlyTemp[month][yearIndex] /= numDaysMonth; + minMonthlyTemp[month][yearIndex] /= numDaysMonth; + meanMonthlyPPT[month][yearIndex] /= numDaysMonth; + + month++; + currMonDay = 0; + + if(month == Mar) minTempFebruary[yearIndex] /= febDays; + + numDaysMonth = (month == February) ? febDays : Time_days_in_month(month); + + } + + currentTempMean -= 18.333; + totalAbove65 += (currentTempMean > 0.0) ? currentTempMean : 0.; + + } + + JulyMinTemp[yearIndex] = currentJulyMin; + PPTJuly[yearIndex] = JulyPPT; + degreeAbove65[yearIndex] = totalAbove65; + frostFreeDays[yearIndex] = consecNonFrost; + + prevFrostMean = frostMean; + + // Get the running values needed for running standard deviation for frostFreeDays + frostMean = get_running_mean(yearIndex + 1, prevFrostMean, consecNonFrost); + frostSqr += get_running_sqr(prevFrostMean, frostMean, consecNonFrost); + } + + findDriestQtr(meanMonthlyTemp, meanMonthlyPPT, meanTempDryQuarter, numYears, startYear); + + // Calculate and set standard deviation of C4 variables (frostFreeDays is a running sd) + sdC4[0] = standardDeviation(JulyMinTemp, numYears); + sdC4[1] = final_running_sd(numYears, frostSqr); + sdC4[2] = standardDeviation(degreeAbove65, numYears); + + // Calculate and set the standard deviation of cheatgrass variables + sdCheatgrass[0] = standardDeviation(PPTJuly, numYears); + sdCheatgrass[1] = standardDeviation(meanTempDryQuarter, numYears); + sdCheatgrass[2] = standardDeviation(minTempFebruary, numYears); +} - wn->ppt[Yesterday] = wn->ppt[Today]; - wn->rain[Yesterday] = wn->rain[Today]; +/** + @brief Helper function to calcsiteClimate to find the driest quarter of the year's average temperature + + @param[in] meanMonthlyTemp 2D array holding sum of monthly temperature for every month and year + @param[in] meanMonthlyPPT 2D array holding sum of monthly precipitation for every month and year + @param[out] meanTempDryQuarter 2D array holding sum of monthly temperature for every month and year + @param[in] numYears Number of years in the simulation run + @param[in] startYear Start year of the simulation + */ +void findDriestQtr(double **meanMonthlyTemp, double **meanMonthlyPPT, double *meanTempDryQuarter, + int numYears, int startYear) { + + int yearIndex, year, month, prevMonth, nextMonth; + + double defaultVal = 999., driestThreeMonPPT, driestMeanTemp, + currentQtrPPT, currentQtrTemp; + + for(yearIndex = 0; yearIndex < numYears; yearIndex++) { + year = yearIndex + startYear; + driestThreeMonPPT = defaultVal; + driestMeanTemp = defaultVal; + + for(month = 0; month < MAX_MONTHS; month++) { + + prevMonth = (month == 0) ? 11 : month - 1; + nextMonth = (month == 11) ? 0 : month + 1; + + currentQtrPPT = (meanMonthlyPPT[prevMonth % 12][yearIndex]) + + (meanMonthlyPPT[month % 12][yearIndex]) + + (meanMonthlyPPT[nextMonth % 12][yearIndex]); + + currentQtrTemp = (meanMonthlyTemp[prevMonth % 12][yearIndex]) + + (meanMonthlyTemp[month % 12][yearIndex]) + + (meanMonthlyTemp[nextMonth % 12][yearIndex]); + + if(currentQtrPPT < driestThreeMonPPT) { + driestMeanTemp = currentQtrTemp; + driestThreeMonPPT = currentQtrPPT; + } + + } + + meanTempDryQuarter[yearIndex] = driestMeanTemp / 3; + + } } /** diff --git a/SW_Weather.h b/SW_Weather.h index 33f250d19..e9e2d76c1 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -104,6 +104,17 @@ extern SW_WEATHER SW_Weather; void SW_WTH_setup(void); void SW_WTH_read(void); Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather); +void averageAcrossYears(double **meanMonthlyTemp, double **maxMonthlyTemp, + double **minMonthlyTemp, double **meanMonthlyPPT, double *meanMonthlyTempAnn, + double *maxMonthlyTempAnn, double *minMonthlyTempAnn, double *meanMonthlyPPTAnn, + double *MAP_cm, double *MAT_C, double MMT_C[], double MMP_cm[], int numYears); +void calcSiteClimate(SW_WEATHER_HIST **allHist, double **meanMonthlyTemp, double **maxMonthlyTemp, + double **minMonthlyTemp, double **meanMonthlyPPT, double *MMP_cm, double *MMT_C, + double *JulyMinTemp, int *frostFreeDays, double *degreeAbove65, double *sdC4, + double *PPTJuly, double *meanTempDryQuarter, double *minTempFebruary, + double *sdCheatgrass, int numYears, int startYear); +void findDriestQtr(double **meanMonthlyTemp, double **meanMonthlyPPT, double *meanTempDryQuarter, + int numYears, int startYear); void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, unsigned int n_years); void deallocateAllWeather(void); void _clear_hist_weather(SW_WEATHER_HIST *yearWeather); From b1cbd6aaec19033675aa8f5b09e60d59ad0a21fd Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sat, 9 Jul 2022 22:39:45 -0400 Subject: [PATCH 074/326] Added unit tests for climate functions in `test_SW_Weather.cc` - Added unit test to test functions: * `findDriestQtr()`, `calcSiteClimate()` and `averageAcrossYears()` --- test/test_SW_Weather.cc | 171 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index abc8823d2..3145bf089 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -136,6 +136,177 @@ namespace { Reset_SOILWAT2_after_UnitTest(); + } + TEST(AverageAcrossYearsTest, FileValues) { + + // This test relies on allHist from `SW_WEATHER` being already filled + + double meanMonthlyAvgAnn[MAX_MONTHS]; + double meanMonthlyMaxAnn[MAX_MONTHS]; + double meanMonthlyMinAnn[MAX_MONTHS]; + double meanMonthlyPPTAnn[MAX_MONTHS]; + double JulyMinTemp[31]; + int frostFreeDays[31]; + double degreeAbove65[31]; + double PPTJuly[31]; + double meanTempDryQuarter[31]; + double minTempFebruary[31]; + double sdCheatgrass[3]; + double sdC4[3]; + double MMP_cm[31]; + double MMT_C[31]; + + double MAP_cm; + double MAT_C; + + double AnnTemp_C[31]; + double AnnPPT_cm[31]; + + double **arrayPPTYear; + arrayPPTYear = new double*[MAX_MONTHS]; + + double **arrayTempAvgYear = new double*[MAX_MONTHS]; + + double **arrayTempMinYear = new double*[MAX_MONTHS]; + + double **arrayTempMaxYear = new double*[MAX_MONTHS]; + + for(int month = 0; month < MAX_MONTHS; month++) { + arrayPPTYear[month] = new double[31]; + arrayTempAvgYear[month] = new double[31]; + arrayTempMinYear[month] = new double[31]; + arrayTempMaxYear[month] = new double[31]; + for(int year = 0; year < 31; year++) { + + arrayPPTYear[month][year] = 0.; + arrayTempAvgYear[month][year] = 0.; + arrayTempMinYear[month][year] = 0.; + arrayTempMaxYear[month][year] = 0.; + } + } + // 1980 is start year of the simulation + calcSiteClimate(SW_Weather.allHist, arrayTempAvgYear, arrayTempMaxYear, arrayTempMinYear, + arrayPPTYear, MMP_cm, MMT_C, JulyMinTemp, frostFreeDays, degreeAbove65, sdC4, + PPTJuly, meanTempDryQuarter, minTempFebruary, sdCheatgrass, 31, 1980); + + averageAcrossYears(arrayTempAvgYear, arrayTempMaxYear, arrayTempMinYear, arrayPPTYear, + meanMonthlyAvgAnn, meanMonthlyMaxAnn, meanMonthlyMinAnn, meanMonthlyPPTAnn, + &MAP_cm, &MAT_C, AnnTemp_C, AnnPPT_cm, 31); + + EXPECT_NEAR(meanMonthlyAvgAnn[0], -9.325551, tol6); + EXPECT_NEAR(meanMonthlyMaxAnn[0], -2.714381, tol6); + EXPECT_NEAR(meanMonthlyMinAnn[0], -15.936722, tol6); + EXPECT_NEAR(meanMonthlyPPTAnn[0], 0.221530, tol6); + + for(int month = 0; month < MAX_MONTHS; month++) { + delete[] arrayPPTYear[month]; + delete[] arrayTempAvgYear[month]; + delete[] arrayTempMinYear[month]; + delete[] arrayTempMaxYear[month]; + } + + delete[] arrayPPTYear; + delete[] arrayTempAvgYear; + delete[] arrayTempMinYear; + delete[] arrayTempMaxYear; + + } + + TEST(calcSiteClimateTest, FileValues) { + + // This test relies on allHist from `SW_WEATHER` being already filled + + double JulyMinTemp[31]; + int frostFreeDays[31]; + double degreeAbove65[31]; + double PPTJuly[31]; + double meanTempDryQuarter[31]; + double minTempFebruary[31]; + double sdCheatgrass[3]; + double sdC4[3]; + double MMP_cm[31]; + double MMT_C[31]; + + double **arrayPPTYear; + arrayPPTYear = new double*[MAX_MONTHS]; + + double **arrayTempAvgYear; + arrayTempAvgYear = new double*[MAX_MONTHS]; + + double **arrayTempMinYear; + arrayTempMinYear = new double*[MAX_MONTHS]; + + double **arrayTempMaxYear; + arrayTempMaxYear = new double*[MAX_MONTHS]; + + for(int month = 0; month < MAX_MONTHS; month++) { + arrayPPTYear[month] = new double[31]; + arrayTempAvgYear[month] = new double[31]; + arrayTempMinYear[month] = new double[31]; + arrayTempMaxYear[month] = new double[31]; + + for(int year = 0; year < 31; year++) { + + arrayPPTYear[month][year] = 0.; + arrayTempAvgYear[month][year] = 0.; + arrayTempMinYear[month][year] = 0.; + arrayTempMaxYear[month][year] = 0.; + } + } + // 1980 is start year of the simulation + calcSiteClimate(SW_Weather.allHist, arrayTempAvgYear, arrayTempMaxYear, arrayTempMinYear, + arrayPPTYear, MMP_cm, MMT_C, JulyMinTemp, frostFreeDays, degreeAbove65, sdC4, + PPTJuly, meanTempDryQuarter, minTempFebruary, sdCheatgrass, 31, 1980); + + EXPECT_NEAR(arrayTempAvgYear[0][0], -8.432581, tol6); + + for(int month = 0; month < MAX_MONTHS; month++) { + delete[] arrayPPTYear[month]; + delete[] arrayTempAvgYear[month]; + delete[] arrayTempMinYear[month]; + delete[] arrayTempMaxYear[month]; + } + + delete[] arrayPPTYear; + delete[] arrayTempAvgYear; + delete[] arrayTempMinYear; + delete[] arrayTempMaxYear; + + } + + TEST(DriestQuarterTest, OneYear) { + + 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[31]; // 31 = number of years in simulation + + double **arrayPPTYear; + arrayPPTYear = new double*[MAX_MONTHS]; + + double **arrayTempAvgYear = new double*[MAX_MONTHS]; + + for(int month = 0; month < MAX_MONTHS; month++) { + arrayPPTYear[month] = new double[31]; + arrayTempAvgYear[month] = new double[31]; + for(int year = 0; year < 31; year++) { + + arrayPPTYear[month][year] = monthlyPPT[month]; + arrayTempAvgYear[month][year] = monthlyTemp[month]; + } + } + // 1980 is start year of the simulation + findDriestQtr(arrayTempAvgYear, arrayPPTYear, result, 31, 1980); + + EXPECT_DOUBLE_EQ(result[0], 1.4333333333333333); + + for(int month = 0; month < MAX_MONTHS; month++) { + delete[] arrayPPTYear[month]; + delete[] arrayTempAvgYear[month]; + } + + delete[] arrayPPTYear; + delete[] arrayTempAvgYear; + } TEST(WeatherReadTest, Initialization) { From 9cb89b50a70c42178801f63805c01441b94bc4fd Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sat, 9 Jul 2022 22:43:48 -0400 Subject: [PATCH 075/326] Swapped arrays in `estimateVegetationFromClimate()` for pointers - This prevents arguments of the compiler when running test_severe --- SW_VegProd.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index 02f155b4b..9a0bc670a 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -867,10 +867,10 @@ void estimateVegetationFromClimate(SW_VEGPROD *veg, int startYear, int endYear) // Used for calculating climate double **meanMonthlyTemp, **maxMonthlyTemp, **minMonthlyTemp, **meanMonthlyPPT, - MMP_cm[numYears], MMT_C[numYears], JulyMinTemp[numYears], degreeAbove65[numYears], sdC4[3], - PPTJuly[numYears], meanTempDryQuarter[numYears], minTempFebruary[numYears], sdCheatgrass[3]; + *MMP_cm, *MMT_C, *JulyMinTemp, *degreeAbove65, sdC4[3], *PPTJuly, *meanTempDryQuarter, + *minTempFebruary, sdCheatgrass[3]; - int frostFreeDays[numYears]; + int *frostFreeDays; // Used for average across years double meanMonthlyTempAnn[MAX_MONTHS], maxMonthlyTempAnn[MAX_MONTHS], minMonthlyTempAnn[MAX_MONTHS], @@ -890,6 +890,15 @@ void estimateVegetationFromClimate(SW_VEGPROD *veg, int startYear, int endYear) } + MMP_cm = (double *)malloc(sizeof(double) * numYears); + MMT_C = (double *)malloc(sizeof(double) * numYears); + JulyMinTemp = (double *)malloc(sizeof(double) * numYears); + degreeAbove65 = (double *)malloc(sizeof(double) * numYears); + PPTJuly = (double *)malloc(sizeof(double) * numYears); + meanTempDryQuarter = (double *)malloc(sizeof(double) * numYears); + minTempFebruary = (double *)malloc(sizeof(double) * numYears); + frostFreeDays = (int *)malloc(sizeof(int) * numYears); + for(year = 0; year < numYears; year++) { MMP_cm[year] = 0.; MMT_C[year] = 0.; @@ -922,5 +931,13 @@ void estimateVegetationFromClimate(SW_VEGPROD *veg, int startYear, int endYear) free(maxMonthlyTemp); free(minMonthlyTemp); free(meanMonthlyPPT); + free(MMP_cm); + free(MMT_C); + free(JulyMinTemp); + free(degreeAbove65); + free(PPTJuly); + free(meanTempDryQuarter); + free(minTempFebruary); + free(frostFreeDays); } From 5d59b496975103f4cfb4de8544f15316a79edab7 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sat, 9 Jul 2022 22:46:22 -0400 Subject: [PATCH 076/326] Removed vegetation part of `estimateVegetationFromClimate()` --- SW_VegProd.c | 6 +++--- SW_VegProd.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index 9a0bc670a..5021a4fd2 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -600,7 +600,7 @@ void SW_VPD_init_run(void) { } if(veg->veg_method) { - estimateVegetationFromClimate(veg, model->startyr, model->endyr); + estimateVegetationFromClimate(model->startyr, model->endyr); } } @@ -861,8 +861,8 @@ void get_critical_rank(void){ ----------------------------------------------------------*/ } -void estimateVegetationFromClimate(SW_VEGPROD *veg, int startYear, int endYear) { - +void estimateVegetationFromClimate(int startYear, int endYear) { + //TODO: SW_VEGPROD veg int numYears = endYear - startYear + 1, year, month; // Used for calculating climate diff --git a/SW_VegProd.h b/SW_VegProd.h index 70f46937c..ea7021cf8 100644 --- a/SW_VegProd.h +++ b/SW_VegProd.h @@ -264,7 +264,7 @@ 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 *veg, int startYear, int endYear); +void estimateVegetationFromClimate(int startYear, int endYear); void SW_VPD_init_run(void); void SW_VPD_deconstruct(void); void apply_biomassCO2effect(double* new_biomass, double *biomass, double multiplier); From d5badcc174548a1d3d738efaa57792e95f7e2860 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sat, 9 Jul 2022 22:51:13 -0400 Subject: [PATCH 077/326] Fixed month bug and compiler error - Modified `calcSiteClimate()` to prevent incorrect month selection - Made sure to convert "veg_method" to Bool to prevent test_severe errors --- SW_VegProd.c | 2 +- SW_Weather.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index 5021a4fd2..a5c262cd8 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -102,7 +102,7 @@ void SW_VPD_read(void) { CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } - v->veg_method = veg_method; + v->veg_method = (Bool) veg_method; break; /* fractions of vegetation types */ diff --git a/SW_Weather.c b/SW_Weather.c index ee99c32ff..8ecfad91e 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -220,8 +220,8 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, double **meanMonthlyTemp, double if(month == Mar) minTempFebruary[yearIndex] /= febDays; - numDaysMonth = (month == February) ? febDays : Time_days_in_month(month); - + if(month != Dec + 1) + numDaysMonth = (month == February) ? febDays : Time_days_in_month(month); } currentTempMean -= 18.333; From d66e97979413c8ccca867007e5beaf2f7c4e9529 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Mon, 11 Jul 2022 23:48:41 -0400 Subject: [PATCH 078/326] Removed all vegetation traces - Removed file input and reverted code for it - Removed call to `estimateVegetationFromClimate()` - Removed `estimateVegetationFromClimate()` --- SW_VegProd.c | 154 ++++++++----------------------------------- SW_VegProd.h | 4 +- testing/Input/veg.in | 4 -- 3 files changed, 29 insertions(+), 133 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index a5c262cd8..b7de9d73b 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -85,7 +85,7 @@ void SW_VPD_read(void) { SW_VEGPROD *v = &SW_VegProd; FILE *f; TimeInt mon = Jan; - int x, k, lineno = 0, veg_method; + int x, k, lineno = 0; const int line_help = 28; // last case line number before monthly biomass densities RealF help_veg[NVEGTYPES], help_bareGround, litt, biom, pctl, laic; @@ -95,18 +95,8 @@ void SW_VPD_read(void) { while (GetALine(f, inbuf)) { if (lineno++ < line_help) { switch (lineno) { - case 1: - x = sscanf(inbuf, "%d", &veg_method); - if(x != 1) { - sprintf(errstr, "ERROR: invalid record in vegetation type components in %s\n", MyFileName); - CloseFile(&f); - LogError(logfp, LOGFATAL, errstr); - } - v->veg_method = (Bool) veg_method; - break; - /* fractions of vegetation types */ - case 2: + 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) { @@ -121,7 +111,7 @@ void SW_VPD_read(void) { break; /* albedo */ - case 3: + 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) { @@ -136,7 +126,7 @@ void SW_VPD_read(void) { break; /* canopy height */ - case 4: + 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) { @@ -148,7 +138,7 @@ void SW_VPD_read(void) { v->veg[k].cnpy.xinflec = help_veg[k]; } break; - case 5: + 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) { @@ -160,7 +150,7 @@ void SW_VPD_read(void) { v->veg[k].cnpy.yinflec = help_veg[k]; } break; - case 6: + 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) { @@ -172,7 +162,7 @@ void SW_VPD_read(void) { v->veg[k].cnpy.range = help_veg[k]; } break; - case 7: + 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) { @@ -184,7 +174,7 @@ void SW_VPD_read(void) { v->veg[k].cnpy.slope = help_veg[k]; } break; - case 8: + 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) { @@ -198,7 +188,7 @@ void SW_VPD_read(void) { break; /* vegetation interception parameters */ - case 9: + 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) { @@ -210,7 +200,7 @@ void SW_VPD_read(void) { v->veg[k].veg_kSmax = help_veg[k]; } break; - case 10: + 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) { @@ -224,7 +214,7 @@ void SW_VPD_read(void) { break; /* litter interception parameters */ - case 11: + 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) { @@ -238,7 +228,7 @@ void SW_VPD_read(void) { break; /* parameter for partitioning of bare-soil evaporation and transpiration */ - case 12: + 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) { @@ -252,7 +242,7 @@ void SW_VPD_read(void) { break; /* Parameter for scaling and limiting bare soil evaporation rate */ - case 13: + 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) { @@ -266,7 +256,7 @@ void SW_VPD_read(void) { break; /* shade effects */ - case 14: + 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) { @@ -278,7 +268,7 @@ void SW_VPD_read(void) { v->veg[k].shade_scale = help_veg[k]; } break; - case 15: + 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) { @@ -290,7 +280,7 @@ void SW_VPD_read(void) { v->veg[k].shade_deadmax = help_veg[k]; } break; - case 16: + 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) { @@ -302,7 +292,7 @@ void SW_VPD_read(void) { v->veg[k].tr_shade_effects.xinflec = help_veg[k]; } break; - case 17: + 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) { @@ -314,7 +304,7 @@ void SW_VPD_read(void) { v->veg[k].tr_shade_effects.yinflec = help_veg[k]; } break; - case 18: + 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) { @@ -326,7 +316,7 @@ void SW_VPD_read(void) { v->veg[k].tr_shade_effects.range = help_veg[k]; } break; - case 19: + 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) { @@ -340,7 +330,7 @@ void SW_VPD_read(void) { break; /* Hydraulic redistribution */ - case 20: + 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) { @@ -352,7 +342,7 @@ void SW_VPD_read(void) { v->veg[k].flagHydraulicRedistribution = (Bool) EQ(help_veg[k], 1.); } break; - case 21: + 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) { @@ -364,7 +354,7 @@ void SW_VPD_read(void) { v->veg[k].maxCondroot = help_veg[k]; } break; - case 22: + 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) { @@ -376,7 +366,7 @@ void SW_VPD_read(void) { v->veg[k].swpMatric50 = help_veg[k]; } break; - case 23: + 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) { @@ -390,7 +380,7 @@ void SW_VPD_read(void) { break; /* Critical soil water potential */ - case 24: + 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) { @@ -407,7 +397,7 @@ void SW_VPD_read(void) { /* CO2 Biomass Power Equation */ // Coefficient 1 - case 25: + 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) { @@ -420,7 +410,7 @@ void SW_VPD_read(void) { } break; // Coefficient 2 - case 26: + 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) { @@ -435,7 +425,7 @@ void SW_VPD_read(void) { /* CO2 WUE Power Equation */ // Coefficient 1 - case 27: + 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) { @@ -448,7 +438,7 @@ void SW_VPD_read(void) { } break; // Coefficient 2 - case 28: + 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) { @@ -585,9 +575,6 @@ void SW_VPD_construct(void) { void SW_VPD_init_run(void) { TimeInt year; int k; - - SW_VEGPROD *veg = &SW_VegProd; - SW_MODEL *model = &SW_Model; /* Set co2-multipliers to default */ for (year = 0; year < MAX_NYEAR; year++) @@ -599,10 +586,6 @@ void SW_VPD_init_run(void) { } } - if(veg->veg_method) { - estimateVegetationFromClimate(model->startyr, model->endyr); - } - } @@ -860,84 +843,3 @@ void get_critical_rank(void){ End of rank_SWPcrits ----------------------------------------------------------*/ } - -void estimateVegetationFromClimate(int startYear, int endYear) { - //TODO: SW_VEGPROD veg - int numYears = endYear - startYear + 1, year, month; - - // Used for calculating climate - double **meanMonthlyTemp, **maxMonthlyTemp, **minMonthlyTemp, **meanMonthlyPPT, - *MMP_cm, *MMT_C, *JulyMinTemp, *degreeAbove65, sdC4[3], *PPTJuly, *meanTempDryQuarter, - *minTempFebruary, sdCheatgrass[3]; - - int *frostFreeDays; - - // Used for average across years - double meanMonthlyTempAnn[MAX_MONTHS], maxMonthlyTempAnn[MAX_MONTHS], minMonthlyTempAnn[MAX_MONTHS], - meanMonthlyPPTAnn[MAX_MONTHS], MAP_cm, MAT_C; - - meanMonthlyTemp = (double **)malloc(sizeof(double *) * MAX_MONTHS); - maxMonthlyTemp = (double **)malloc(sizeof(double *) * MAX_MONTHS); - minMonthlyTemp = (double **)malloc(sizeof(double *) * MAX_MONTHS); - meanMonthlyPPT = (double **)malloc(sizeof(double *) * MAX_MONTHS); - - for(month = 0; month < MAX_MONTHS; month++) { - - meanMonthlyTemp[month] = (double *)malloc(sizeof(double) * numYears); - maxMonthlyTemp[month] = (double *)malloc(sizeof(double) * numYears); - minMonthlyTemp[month] = (double *)malloc(sizeof(double) * numYears); - meanMonthlyPPT[month] = (double *)malloc(sizeof(double) * numYears); - - } - - MMP_cm = (double *)malloc(sizeof(double) * numYears); - MMT_C = (double *)malloc(sizeof(double) * numYears); - JulyMinTemp = (double *)malloc(sizeof(double) * numYears); - degreeAbove65 = (double *)malloc(sizeof(double) * numYears); - PPTJuly = (double *)malloc(sizeof(double) * numYears); - meanTempDryQuarter = (double *)malloc(sizeof(double) * numYears); - minTempFebruary = (double *)malloc(sizeof(double) * numYears); - frostFreeDays = (int *)malloc(sizeof(int) * numYears); - - for(year = 0; year < numYears; year++) { - MMP_cm[year] = 0.; - MMT_C[year] = 0.; - JulyMinTemp[year] = 0.; - degreeAbove65[year] = 0.; - PPTJuly[year] = 0.; - meanTempDryQuarter[year] = 0.; - minTempFebruary[year] = 0.; - frostFreeDays[year] = 0.; - - } - - calcSiteClimate(SW_Weather.allHist, meanMonthlyTemp, maxMonthlyTemp, minMonthlyTemp, - meanMonthlyPPT, MMP_cm, MMT_C, JulyMinTemp, frostFreeDays, degreeAbove65, sdC4, - PPTJuly, meanTempDryQuarter, minTempFebruary, sdCheatgrass, numYears, startYear); - - averageAcrossYears(meanMonthlyTemp, maxMonthlyTemp, minMonthlyTemp, meanMonthlyPPT, meanMonthlyTempAnn, - maxMonthlyTempAnn, minMonthlyTempAnn, meanMonthlyPPTAnn, &MAP_cm, &MAT_C, MMT_C, - MMP_cm, numYears); - - // Clean up allocated memory - for(month = 0; month < MAX_MONTHS; month++) { - free(meanMonthlyTemp[month]); - free(maxMonthlyTemp[month]); - free(minMonthlyTemp[month]); - free(meanMonthlyPPT[month]); - } - - free(meanMonthlyTemp); - free(maxMonthlyTemp); - free(minMonthlyTemp); - free(meanMonthlyPPT); - free(MMP_cm); - free(MMT_C); - free(JulyMinTemp); - free(degreeAbove65); - free(PPTJuly); - free(meanTempDryQuarter); - free(minTempFebruary); - free(frostFreeDays); - -} diff --git a/SW_VegProd.h b/SW_VegProd.h index ea7021cf8..1641530ca 100644 --- a/SW_VegProd.h +++ b/SW_VegProd.h @@ -229,8 +229,7 @@ typedef struct { /** Flag that determines whether vegetation-type specific soil water availability should be calculated; user input from file `Input/outsetup.in` */ - use_SWA, - veg_method; + use_SWA; RealD // storing values in same order as defined in STEPWAT2/rgroup.in (0=tree, 1=shrub, 2=grass, 3=forb) @@ -264,7 +263,6 @@ void SW_VPD_read(void); void SW_VPD_new_year(void); void SW_VPD_fix_cover(void); void SW_VPD_construct(void); -void estimateVegetationFromClimate(int startYear, int endYear); void SW_VPD_init_run(void); void SW_VPD_deconstruct(void); void apply_biomassCO2effect(double* new_biomass, double *biomass, double multiplier); diff --git a/testing/Input/veg.in b/testing/Input/veg.in index 240418d0a..fd5af9a61 100755 --- a/testing/Input/veg.in +++ b/testing/Input/veg.in @@ -7,10 +7,6 @@ # USER: Most of the other values in this file are parameters that # describe the four available vegetation types and should not be # modified unless a vegetation type itself is altered. - -#---- Flag to activate/deactivate estimating vegetation based off weather -1 # 0 - Read in the values from file - # 1 - Estimate vegetation from weather #---- Composition of vegetation type components (0-1; must add up to 1) # Grasses Shrubs Trees Forbs BareGround From 4ae1c74c4bec79e5e40e3aa25154e2b218102521 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Mon, 11 Jul 2022 23:54:42 -0400 Subject: [PATCH 079/326] Expanded two unit tests in `test_SW_Weather.cc` - Added tests for one year of simulation in averageAcrossYears test - Now testing all variables related to `calcSiteClimate()` in calcSiteClimateTest.FileValues --- test/test_SW_Weather.cc | 82 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 3145bf089..2483cba79 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -189,15 +189,49 @@ namespace { arrayPPTYear, MMP_cm, MMT_C, JulyMinTemp, frostFreeDays, degreeAbove65, sdC4, PPTJuly, meanTempDryQuarter, minTempFebruary, sdCheatgrass, 31, 1980); - averageAcrossYears(arrayTempAvgYear, arrayTempMaxYear, arrayTempMinYear, arrayPPTYear, + averageClimateAcrossYears(arrayTempAvgYear, arrayTempMaxYear, arrayTempMinYear, arrayPPTYear, meanMonthlyAvgAnn, meanMonthlyMaxAnn, meanMonthlyMinAnn, meanMonthlyPPTAnn, - &MAP_cm, &MAT_C, AnnTemp_C, AnnPPT_cm, 31); + &MAP_cm, &MAT_C, MMT_C, MMP_cm, 31); + //swprintf("%f\n\n\n", MMT_C[0]); EXPECT_NEAR(meanMonthlyAvgAnn[0], -9.325551, tol6); EXPECT_NEAR(meanMonthlyMaxAnn[0], -2.714381, tol6); EXPECT_NEAR(meanMonthlyMinAnn[0], -15.936722, tol6); EXPECT_NEAR(meanMonthlyPPTAnn[0], 0.221530, tol6); + EXPECT_NEAR(MMT_C[0], 1656.100000, tol1); + EXPECT_NEAR(MMP_cm[0], 59.27, tol3); + EXPECT_NEAR(MAP_cm, 62.817419, tol1); + EXPECT_NEAR(MAT_C, 4.153896, tol1); + + // Reset values + for(int year = 0; year < 31; year++) { + for(int month = 0; month < MAX_MONTHS; month++) { + arrayPPTYear[month][year] = 0.; + arrayTempAvgYear[month][year] = 0.; + arrayTempMinYear[month][year] = 0.; + arrayTempMaxYear[month][year] = 0.; + } + MMP_cm[year] = 0.; + MMT_C[year] = 0.; + } + // Tests for one year of simulation + calcSiteClimate(SW_Weather.allHist, arrayTempAvgYear, arrayTempMaxYear, arrayTempMinYear, + arrayPPTYear, MMP_cm, MMT_C, JulyMinTemp, frostFreeDays, degreeAbove65, sdC4, + PPTJuly, meanTempDryQuarter, minTempFebruary, sdCheatgrass, 1, 1980); + + averageClimateAcrossYears(arrayTempAvgYear, arrayTempMaxYear, arrayTempMinYear, arrayPPTYear, + meanMonthlyAvgAnn, meanMonthlyMaxAnn, meanMonthlyMinAnn, meanMonthlyPPTAnn, + &MAP_cm, &MAT_C, MMT_C, MMP_cm, 1); + + EXPECT_NEAR(meanMonthlyAvgAnn[0], -8.432581, tol6); + EXPECT_NEAR(meanMonthlyMaxAnn[0], -2.562581, tol6); + EXPECT_NEAR(meanMonthlyMinAnn[0], -14.302581, tol6); + EXPECT_NEAR(meanMonthlyPPTAnn[0], 0.488387, tol6); + EXPECT_NEAR(MAP_cm, 59.27, tol1); + EXPECT_NEAR(MAT_C, 4.524863, tol1); + + for(int month = 0; month < MAX_MONTHS; month++) { delete[] arrayPPTYear[month]; delete[] arrayTempAvgYear[month]; @@ -258,8 +292,52 @@ namespace { arrayPPTYear, MMP_cm, MMT_C, JulyMinTemp, frostFreeDays, degreeAbove65, sdC4, PPTJuly, meanTempDryQuarter, minTempFebruary, sdCheatgrass, 31, 1980); + // Average of average temperature of January in 1980 EXPECT_NEAR(arrayTempAvgYear[0][0], -8.432581, tol6); + // Average of max temperature in Januaray 1980 + EXPECT_NEAR(arrayTempMaxYear[0][0], -2.562581, tol6); + + // Average of min temperature in Januaray 1980 + EXPECT_NEAR(arrayTempMinYear[0][0], -14.302581, tol6); + + // Average January precipitation in 1980 + EXPECT_NEAR(arrayPPTYear[0][0], 0.488387, tol6); + + // Average temperature of three driest month of first year + EXPECT_NEAR(meanTempDryQuarter[0], .936387, tol6); + + // Average precipiation of first year of simulation + EXPECT_NEAR(MMP_cm[0], 59.27, tol6); + + // Average temperature of first year of simulation + EXPECT_NEAR(MMT_C[0], 1656.100000, tol6); + + // First year's July minimum temperature + EXPECT_NEAR(JulyMinTemp[0], 2.810000, tol6); + + // First year's number of most consecutive frost free days + EXPECT_EQ(frostFreeDays[0], 92); + + // Sum of all temperature above 65F (18.333C) in first year + EXPECT_NEAR(degreeAbove65[0], 13.546000, tol6); + + // Standard deviation of C4 variables + EXPECT_NEAR(sdC4[0], 1.785535, tol6); + EXPECT_NEAR(sdC4[1], 14.091788, tol6); + EXPECT_NEAR(sdC4[2], 19.953560, tol6); + + // Total precipitation in July of first year + EXPECT_NEAR(PPTJuly[0], 18.300000, tol6); + + // Smallest temperature in all February first year + EXPECT_NEAR(minTempFebruary[0], -12.822069, tol6); + + // Standard deviation of cheatgrass variables + EXPECT_NEAR(sdCheatgrass[0], 21.598367, tol6); + EXPECT_NEAR(sdCheatgrass[1], 7.171922, tol6); + EXPECT_NEAR(sdCheatgrass[2], 2.618434, tol6); + for(int month = 0; month < MAX_MONTHS; month++) { delete[] arrayPPTYear[month]; delete[] arrayTempAvgYear[month]; From dfe133576ae79e4f6d7e28942a3709e3b6f5f7fb Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 12 Jul 2022 00:01:21 -0400 Subject: [PATCH 080/326] Tweaked variables/variable reset in `calcSiteClimate()` test - Removed unnecessary code in CalcSiteClimateTest.FileValues - Added code to make sure "MMP_cm" and "MMT_C" are reset --- test/test_SW_Weather.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 2483cba79..46abc62df 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -159,9 +159,6 @@ namespace { double MAP_cm; double MAT_C; - double AnnTemp_C[31]; - double AnnPPT_cm[31]; - double **arrayPPTYear; arrayPPTYear = new double*[MAX_MONTHS]; @@ -182,6 +179,8 @@ namespace { arrayTempAvgYear[month][year] = 0.; arrayTempMinYear[month][year] = 0.; arrayTempMaxYear[month][year] = 0.; + MMP_cm[year] = 0.; + MMT_C[year] = 0.; } } // 1980 is start year of the simulation From 63915fb022ef0ff38130db0179b381033a76dcda Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 12 Jul 2022 00:07:23 -0400 Subject: [PATCH 081/326] Renamed `averageAcrossYears()`/modified "MAT_C" calculation - Renamed `averageAcrossYears()` to `averageClimateAcrossYears()` - Modified how "MAT_C" average is calculated --- SW_Weather.c | 8 +++++--- SW_Weather.h | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 8ecfad91e..2fc7d419c 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -98,12 +98,14 @@ static char *MyFileName; @param[in] numYears Number of years we want to average across */ -void averageAcrossYears(double **meanMonthlyTemp, double **maxMonthlyTemp, +void averageClimateAcrossYears(double **meanMonthlyTemp, double **maxMonthlyTemp, double **minMonthlyTemp, double **meanMonthlyPPT, double *meanMonthlyTempAnn, double *maxMonthlyTempAnn, double *minMonthlyTempAnn, double *meanMonthlyPPTAnn, double *MAP_cm, double *MAT_C, double MMT_C[], double MMP_cm[], int numYears) { - int month; + int month, numLeapYears = (numYears / 4) + 1; + double numDaysInSimulation = (numYears * 365.) + numLeapYears; + double avgDaysInSimulation = numDaysInSimulation / numYears; for(month = 0; month < MAX_MONTHS; month++) { meanMonthlyTempAnn[month] = mean(meanMonthlyTemp[month], numYears); @@ -113,7 +115,7 @@ void averageAcrossYears(double **meanMonthlyTemp, double **maxMonthlyTemp, } *MAP_cm = mean(MMP_cm, numYears); - *MAT_C = mean(MMT_C, numYears); + *MAT_C = mean(MMT_C, numYears) / avgDaysInSimulation; } /** diff --git a/SW_Weather.h b/SW_Weather.h index e9e2d76c1..b4de95f21 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -104,7 +104,7 @@ extern SW_WEATHER SW_Weather; void SW_WTH_setup(void); void SW_WTH_read(void); Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather); -void averageAcrossYears(double **meanMonthlyTemp, double **maxMonthlyTemp, +void averageClimateAcrossYears(double **meanMonthlyTemp, double **maxMonthlyTemp, double **minMonthlyTemp, double **meanMonthlyPPT, double *meanMonthlyTempAnn, double *maxMonthlyTempAnn, double *minMonthlyTempAnn, double *meanMonthlyPPTAnn, double *MAP_cm, double *MAT_C, double MMT_C[], double MMP_cm[], int numYears); From 12cd160810b651afeddf912d49ca306be631d804 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 12 Jul 2022 00:10:14 -0400 Subject: [PATCH 082/326] Modified `calcSiteClimate()` code and documentation - Put "[in]" in the correct place for two parameters in documentation - Makes use of `Time_new_year()` eliminating the need for "febDays" - Modified any places that made use of "febDays" --- SW_Weather.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 2fc7d419c..35174d144 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -140,8 +140,8 @@ void averageClimateAcrossYears(double **meanMonthlyTemp, double **maxMonthlyTemp @param[out] minTempFebruary Array of size numYears holding the mean minimum temperature in february for every year @param[out] sdCheatgrass Array of size 3 holding the standard deviations of July precipitation (0), mean temperature of dry quarter (1), mean minimum temperature of February (2) - @param numYears[in] Number of years covered in the simulation - @param startYear[in] Start year of simulation + @param[in] numYears Number of years covered in the simulation + @param[in] startYear Start year of simulation */ @@ -153,7 +153,7 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, double **meanMonthlyTemp, double int month, yearIndex, year, day, numDaysYear, numDaysMonth, currMonDay, - consecNonFrost, currentNonFrost, febDays, July = 6, February = 1; + consecNonFrost, currentNonFrost, July = 6, February = 1; double currentTempMin, currentTempMean, totalAbove65, currentJulyMin, JulyPPT, prevFrostMean, frostMean = 0, frostSqr = 0; @@ -167,8 +167,8 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, double **meanMonthlyTemp, double for(yearIndex = 0; yearIndex < numYears; yearIndex++) { year = yearIndex + startYear; + Time_new_year(year); numDaysYear = isleapyear(year) ? 366 : 365; - febDays = isleapyear(year) ? 29 : 28; month = 0; currMonDay = 0; numDaysMonth = 31; @@ -217,13 +217,11 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, double **meanMonthlyTemp, double minMonthlyTemp[month][yearIndex] /= numDaysMonth; meanMonthlyPPT[month][yearIndex] /= numDaysMonth; + if(month == Feb) minTempFebruary[yearIndex] /= numDaysMonth; + month++; + if(month != Dec + 1) numDaysMonth = Time_days_in_month(month); currMonDay = 0; - - if(month == Mar) minTempFebruary[yearIndex] /= febDays; - - if(month != Dec + 1) - numDaysMonth = (month == February) ? febDays : Time_days_in_month(month); } currentTempMean -= 18.333; From 788b4c5fddfcde64f59f1a2a6447fad07b85487e Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 12 Jul 2022 00:13:56 -0400 Subject: [PATCH 083/326] Modified `findDriestQtr()` code and its tests - Removed unnecessary variables (year and startYear) - Instead of thirty-one years, it now tests one and two years --- SW_Weather.c | 7 +++---- SW_Weather.h | 2 +- test/test_SW_Weather.cc | 18 ++++++++++++------ 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 35174d144..e1e2ca513 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -241,7 +241,7 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, double **meanMonthlyTemp, double frostSqr += get_running_sqr(prevFrostMean, frostMean, consecNonFrost); } - findDriestQtr(meanMonthlyTemp, meanMonthlyPPT, meanTempDryQuarter, numYears, startYear); + findDriestQtr(meanMonthlyTemp, meanMonthlyPPT, meanTempDryQuarter, numYears); // Calculate and set standard deviation of C4 variables (frostFreeDays is a running sd) sdC4[0] = standardDeviation(JulyMinTemp, numYears); @@ -264,15 +264,14 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, double **meanMonthlyTemp, double @param[in] startYear Start year of the simulation */ void findDriestQtr(double **meanMonthlyTemp, double **meanMonthlyPPT, double *meanTempDryQuarter, - int numYears, int startYear) { + int numYears) { - int yearIndex, year, month, prevMonth, nextMonth; + int yearIndex, month, prevMonth, nextMonth; double defaultVal = 999., driestThreeMonPPT, driestMeanTemp, currentQtrPPT, currentQtrTemp; for(yearIndex = 0; yearIndex < numYears; yearIndex++) { - year = yearIndex + startYear; driestThreeMonPPT = defaultVal; driestMeanTemp = defaultVal; diff --git a/SW_Weather.h b/SW_Weather.h index b4de95f21..2b48ca4c9 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -114,7 +114,7 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, double **meanMonthlyTemp, double double *PPTJuly, double *meanTempDryQuarter, double *minTempFebruary, double *sdCheatgrass, int numYears, int startYear); void findDriestQtr(double **meanMonthlyTemp, double **meanMonthlyPPT, double *meanTempDryQuarter, - int numYears, int startYear); + int numYears); void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, unsigned int n_years); void deallocateAllWeather(void); void _clear_hist_weather(SW_WEATHER_HIST *yearWeather); diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 46abc62df..51f6d5e8b 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -351,11 +351,11 @@ namespace { } - TEST(DriestQuarterTest, OneYear) { + TEST(AverageTemperatureOfDriestQuarterTest, OneAndTwoYear) { 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[31]; // 31 = number of years in simulation + double result[2]; // 2 = max number of years in test double **arrayPPTYear; arrayPPTYear = new double*[MAX_MONTHS]; @@ -363,16 +363,22 @@ namespace { double **arrayTempAvgYear = new double*[MAX_MONTHS]; for(int month = 0; month < MAX_MONTHS; month++) { - arrayPPTYear[month] = new double[31]; - arrayTempAvgYear[month] = new double[31]; - for(int year = 0; year < 31; year++) { + arrayPPTYear[month] = new double[2]; + arrayTempAvgYear[month] = new double[2]; + for(int year = 0; year < 2; year++) { arrayPPTYear[month][year] = monthlyPPT[month]; arrayTempAvgYear[month][year] = monthlyTemp[month]; } } // 1980 is start year of the simulation - findDriestQtr(arrayTempAvgYear, arrayPPTYear, result, 31, 1980); + findDriestQtr(arrayTempAvgYear, arrayPPTYear, result, 1); + + // Value 1.433333... is the average temperature of the driest quarter of the year + // In this case, the driest quarter is February-April + EXPECT_DOUBLE_EQ(result[0], 1.4333333333333333); + + findDriestQtr(arrayTempAvgYear, arrayPPTYear, result, 2); EXPECT_DOUBLE_EQ(result[0], 1.4333333333333333); From 93519f354ec236db6a802661b9a1e7d95f02725f Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 12 Jul 2022 00:18:46 -0400 Subject: [PATCH 084/326] Decremented `line_help` variable to get correct output - Forgotten in earlier commit that is necessary to get correct output --- SW_VegProd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index b7de9d73b..8fb85d35e 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -86,7 +86,7 @@ void SW_VPD_read(void) { FILE *f; TimeInt mon = Jan; int x, k, lineno = 0; - const int line_help = 28; // last case line number before monthly biomass densities + 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); From 58a8e70399b09d8a7f7851ebad757c246ef45f92 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 12 Jul 2022 14:07:20 -0400 Subject: [PATCH 085/326] Fixed unexpected test fails in suite `CalcSiteClimateTest` - Initialized following variables to get correct values after function call: * "MMP_cm", "MMT_C" and "minTempFebruary" --- test/test_SW_Weather.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 51f6d5e8b..2688d8b17 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -245,7 +245,7 @@ namespace { } - TEST(calcSiteClimateTest, FileValues) { + TEST(CalcSiteClimateTest, FileValues) { // This test relies on allHist from `SW_WEATHER` being already filled @@ -284,6 +284,9 @@ namespace { arrayTempAvgYear[month][year] = 0.; arrayTempMinYear[month][year] = 0.; arrayTempMaxYear[month][year] = 0.; + MMP_cm[year] = 0.; + MMT_C[year] = 0.; + minTempFebruary[year] = 0.; } } // 1980 is start year of the simulation From 05e382c605bd511d4527c7ef416796181e43a220 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Wed, 13 Jul 2022 23:51:37 -0400 Subject: [PATCH 086/326] Updated variable names of climate-related functions - Updated names to clarify properties of variables - Names now include units --- SW_Weather.c | 66 ++++++++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index e1e2ca513..eb1504a54 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -106,16 +106,17 @@ void averageClimateAcrossYears(double **meanMonthlyTemp, double **maxMonthlyTemp int month, numLeapYears = (numYears / 4) + 1; double numDaysInSimulation = (numYears * 365.) + numLeapYears; double avgDaysInSimulation = numDaysInSimulation / numYears; + int month; for(month = 0; month < MAX_MONTHS; month++) { - meanMonthlyTempAnn[month] = mean(meanMonthlyTemp[month], numYears); - maxMonthlyTempAnn[month] = mean(maxMonthlyTemp[month], numYears); - minMonthlyTempAnn[month] = mean(minMonthlyTemp[month], numYears); - meanMonthlyPPTAnn[month] = mean(meanMonthlyPPT[month], numYears); + meanMonthlyTempAnn[month] = mean(meanMonthlyTemp_C[month], numYears); + maxMonthlyTempAnn[month] = mean(maxMonthlyTemp_C[month], numYears); + minMonthlyTempAnn[month] = mean(minMonthlyTemp_C[month], numYears); + meanMonthlyPPTAnn[month] = mean(meanMonthlyPPT_cm[month], numYears); } - *MAP_cm = mean(MMP_cm, numYears); - *MAT_C = mean(MMT_C, numYears) / avgDaysInSimulation; + *MAP_cm = mean(annualPPT_cm, numYears); + *MAT_C = mean(meanAnnualTemp_C, numYears); } /** @@ -155,14 +156,13 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, double **meanMonthlyTemp, double int month, yearIndex, year, day, numDaysYear, numDaysMonth, currMonDay, consecNonFrost, currentNonFrost, July = 6, February = 1; - double currentTempMin, currentTempMean, totalAbove65, currentJulyMin, JulyPPT, - prevFrostMean, frostMean = 0, frostSqr = 0; + double currentTempMin, currentTempMean, totalAbove65, currentJulyMin, JulyPPT; for(month = 0; month < MAX_MONTHS; month++) { - memset(meanMonthlyTemp[month], 0., sizeof(double) * numYears); - memset(maxMonthlyTemp[month], 0., sizeof(double) * numYears); - memset(minMonthlyTemp[month], 0., sizeof(double) * numYears); - memset(meanMonthlyPPT[month], 0., sizeof(double) * numYears); + memset(meanMonthlyTemp_C[month], 0., sizeof(double) * numYears); + memset(maxMonthlyTemp_C[month], 0., sizeof(double) * numYears); + memset(minMonthlyTemp_C[month], 0., sizeof(double) * numYears); + memset(meanMonthlyPPT_cm[month], 0., sizeof(double) * numYears); } for(yearIndex = 0; yearIndex < numYears; yearIndex++) { @@ -180,13 +180,13 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, double **meanMonthlyTemp, double for(day = 0; day < numDaysYear; day++) { currMonDay++; - meanMonthlyTemp[month][yearIndex] += allHist[yearIndex]->temp_avg[day]; - maxMonthlyTemp[month][yearIndex] += allHist[yearIndex]->temp_max[day]; - minMonthlyTemp[month][yearIndex] += allHist[yearIndex]->temp_min[day]; - meanMonthlyPPT[month][yearIndex] += allHist[yearIndex]->ppt[day]; + meanMonthlyTemp_C[month][yearIndex] += allHist[yearIndex]->temp_avg[day]; + maxMonthlyTemp_C[month][yearIndex] += allHist[yearIndex]->temp_max[day]; + minMonthlyTemp_C[month][yearIndex] += allHist[yearIndex]->temp_min[day]; + meanMonthlyPPT_cm[month][yearIndex] += allHist[yearIndex]->ppt[day]; - MMP_cm[yearIndex] += allHist[yearIndex]->ppt[day]; - MMT_C[yearIndex] += allHist[yearIndex]->temp_avg[day]; + annualPPT_cm[yearIndex] += allHist[yearIndex]->ppt[day]; + meanAnnualTemp_C[yearIndex] += allHist[yearIndex]->temp_avg[day]; currentTempMin = allHist[yearIndex]->temp_min[day]; currentTempMean = allHist[yearIndex]->temp_avg[day]; @@ -212,12 +212,12 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, double **meanMonthlyTemp, double if(currMonDay == numDaysMonth) { // Take the average of the current months values for current year - meanMonthlyTemp[month][yearIndex] /= numDaysMonth; - maxMonthlyTemp[month][yearIndex] /= numDaysMonth; - minMonthlyTemp[month][yearIndex] /= numDaysMonth; - meanMonthlyPPT[month][yearIndex] /= numDaysMonth; + meanMonthlyTemp_C[month][yearIndex] /= numDaysMonth; + maxMonthlyTemp_C[month][yearIndex] /= numDaysMonth; + minMonthlyTemp_C[month][yearIndex] /= numDaysMonth; + meanMonthlyPPT_cm[month][yearIndex] /= numDaysMonth; - if(month == Feb) minTempFebruary[yearIndex] /= numDaysMonth; + if(month == Feb) minTempFebruary_C[yearIndex] /= numDaysMonth; month++; if(month != Dec + 1) numDaysMonth = Time_days_in_month(month); @@ -230,9 +230,9 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, double **meanMonthlyTemp, double } JulyMinTemp[yearIndex] = currentJulyMin; - PPTJuly[yearIndex] = JulyPPT; - degreeAbove65[yearIndex] = totalAbove65; - frostFreeDays[yearIndex] = consecNonFrost; + JulyPPT_mm[yearIndex] = JulyPPT; + ddAbove65F_degday[yearIndex] = totalAbove65; + frostFreeDays_days[yearIndex] = (double)consecNonFrost; prevFrostMean = frostMean; @@ -280,13 +280,13 @@ void findDriestQtr(double **meanMonthlyTemp, double **meanMonthlyPPT, double *me prevMonth = (month == 0) ? 11 : month - 1; nextMonth = (month == 11) ? 0 : month + 1; - currentQtrPPT = (meanMonthlyPPT[prevMonth % 12][yearIndex]) + - (meanMonthlyPPT[month % 12][yearIndex]) + - (meanMonthlyPPT[nextMonth % 12][yearIndex]); + currentQtrPPT = (meanMonthlyPPT_cm[prevMonth][yearIndex]) + + (meanMonthlyPPT_cm[month][yearIndex]) + + (meanMonthlyPPT_cm[nextMonth][yearIndex]); - currentQtrTemp = (meanMonthlyTemp[prevMonth % 12][yearIndex]) + - (meanMonthlyTemp[month % 12][yearIndex]) + - (meanMonthlyTemp[nextMonth % 12][yearIndex]); + currentQtrTemp = (meanMonthlyTemp_C[prevMonth][yearIndex]) + + (meanMonthlyTemp_C[month][yearIndex]) + + (meanMonthlyTemp_C[nextMonth][yearIndex]); if(currentQtrPPT < driestThreeMonPPT) { driestMeanTemp = currentQtrTemp; @@ -295,7 +295,7 @@ void findDriestQtr(double **meanMonthlyTemp, double **meanMonthlyPPT, double *me } - meanTempDryQuarter[yearIndex] = driestMeanTemp / 3; + meanTempDriestQuarter_C[yearIndex] = driestMeanTemp / 3; } } From d5f9d6c7227dbfad72bfee3dc13e3c71815591c7 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Wed, 13 Jul 2022 23:57:24 -0400 Subject: [PATCH 087/326] Rearranged parameter order and changed variable names - Changed names in function documentation to match variable name changes - Rearranged documentation/parameter order to have [in] variables first and [out] last --- SW_Weather.c | 109 +++++++++++++++++++++++++++++---------------------- SW_Weather.h | 25 ++++++------ 2 files changed, 77 insertions(+), 57 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index eb1504a54..3d70f4663 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -81,27 +81,42 @@ static char *MyFileName; /** @brief Takes averages through the number of years of the calculated values from calc_SiteClimate - @param[in] meanMonthlyTemp 2D array containing all mean average monthly air temperature (deg;C) with dimensions + + @param[in] meanMonthlyTemp_C 2D array containing monthly means of average daily air temperature (deg;C) with dimensions + of row (months) size MAX_MONTHS and columns (years) of size numYears + @param[in] maxMonthlyTemp_C 2D array containing monthly means of maximum daily air temperature (deg;C) with dimensions of row (months) size MAX_MONTHS and columns (years) of size numYears - @param[in] maxMonthlyTemp 2D array containing all mean max monthly air temperature (deg;C) with dimensions + @param[in] minMonthlyTemp_C 2D array containing monthly means of minimum daily air temperature (deg;C) with dimensions of row (months) size MAX_MONTHS and columns (years) of size numYears - @param[in] minMonthlyTemp 2D array containing all mean min monthly air temperature (deg;C) with dimensions + @param[in] meanMonthlyPPT_cm 2D array containing monthly precipitation amount (deg;C) with dimensions of row (months) size MAX_MONTHS and columns (years) of size numYears - @param[out] meanMonthlyAvg Array containing sum of monthly mean temperatures - @param[out] meanMonthlyMax Array containing sum of monthly maximum temperatures - @param[out] meanMonthlyMin Array containing sum of monthly minimum temperatures - @param[out] meanMonthlyPPT Array containing sum of monthly mean precipitation + @param[in] numYears Calendar year corresponding to first year of `allHist` + @param[in] JulyMinTemp Array of size numYears holding minimum July temperatures [C] for each year + @param[in] frostFreeDays_days Array of size numYears holding the maximum consecutive days in a year without frost for every year + @param[in] ddAbove65F_degday Array of size numYears holding the amount of degree days [C x day] above 65 F + @param[in] JulyPPT_mm Array of size numYears holding July precipitation amount (mm) + @param[in] meanTempDriestQuarter_C Array of size numYears holding the average temperature of the driest quarter of the year for every year + @param[in] minTempFebruary_C Array of size numYears holding the mean minimum temperature in february for every year + @param[out] annualPPT_cm Array of size `numYears` containing annual precipitation amount [cm] + @param[out] meanAnnualTemp_C Array of size `numYears` containing annual mean temperatures [C] + @param[out] meanMonthlyTempAnn Array of size `numYears` containing sum of monthly mean temperatures + @param[out] maxMonthlyTempAnn Array of size `numYears` containing sum of monthly maximum temperatures + @param[out] minMonthlyTempAnn Array of size `numYears` containing sum of monthly minimum temperatures + @param[out] meanMonthlyPPTAnn Array of size `numYears` containing sum of monthly mean precipitation + @param[out] sdC4 Array of size three holding the standard deviations of minimum July temperature (0), frost free days (1), number of days above 65F (2) + @param[out] sdCheatgrass Array of size 3 holding the standard deviations of July precipitation (0), mean + temperature of dry quarter (1), mean minimum temperature of February (2) @param[out] MAT_C Value containing the sum of daily temperatures @param[out] MAP_cm Value containing the sum of daily precipitation - @param[out] PPT_cm Array containing annual precipitation amount [cm] - @param[out] Temp_C Array containing annual temperatures [C] - @param[in] numYears Number of years we want to average across */ -void averageClimateAcrossYears(double **meanMonthlyTemp, double **maxMonthlyTemp, - double **minMonthlyTemp, double **meanMonthlyPPT, double *meanMonthlyTempAnn, - double *maxMonthlyTempAnn, double *minMonthlyTempAnn, double *meanMonthlyPPTAnn, - double *MAP_cm, double *MAT_C, double MMT_C[], double MMP_cm[], int numYears) { +void averageClimateAcrossYears(double **meanMonthlyTemp_C, double **maxMonthlyTemp_C, + double **minMonthlyTemp_C, double **meanMonthlyPPT_cm, double numYears, double JulyMinTemp[], + double frostFreeDays_days[], double ddAbove65F_degday[], double JulyPPT_mm[], + double meanTempDriestQuarter_C[], double minTempFebruary_C[], double annualPPT_cm[], + double meanAnnualTemp_C[], double *meanMonthlyTempAnn, double *maxMonthlyTempAnn, + double *minMonthlyTempAnn, double *meanMonthlyPPTAnn, double *sdC4, double *sdCheatgrass, + double *MAT_C, double *MAP_cm) { int month, numLeapYears = (numYears / 4) + 1; double numDaysInSimulation = (numYears * 365.) + numLeapYears; @@ -120,37 +135,34 @@ void averageClimateAcrossYears(double **meanMonthlyTemp, double **maxMonthlyTemp } /** - @brief Calculate climate variable from daily weather - @param[in] all_hist Array containing all historical data of a site - @param[out] meanMonthlyTemp 2D array containing all mean average monthly air temperature (deg;C) with + @brief Calculate monthly and annual time series of climate variables from daily weather + + @param[in] allHist Array containing all historical data of a site + @param[in] numYears Number of years represented by `allHist` + @param[in] startYear Calendar year corresponding to first year of `allHist` + @param[out] meanMonthlyTemp_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] maxMonthlyTemp 2D array containing all mean max monthly air temperature (deg;C) with + @param[out] maxMonthlyTemp_C 2D array containing monthly means max daily air temperature (deg;C) with dimensions of row (months) size MAX_MONTHS and columns (years) of size numYears - @param[out] minMonthlyTemp 2D array containing all mean min monthly air temperature (deg;C) with + @param[out] minMonthlyTemp_C 2D array containing monthly means min daily air temperature (deg;C) with dimensions of row (months) size MAX_MONTHS and columns (years) of size numYears - @param[out] meanMonthlyPPT 2D array containing all mean average monthly precipitation (cm) with dimensions + @param[out] meanMonthlyPPT_cm_cm 2D array containing monthly amount precipitation (cm) with dimensions of row (months) size MAX_MONTHS and columns (years) of size numYears - @param[out] MMP_cm Array containing annual precipitation amount [cm] - @param[out] MMT_C Array containing annual temperatures [C] - @param[out] JulyMinTemp Array of size numYears holding minimum July temperatures for each year - @param[out] frostFreeDays Array of size numYears holding the maximum consecutive days in a year without frost for every year - @param[out] degreeAbove65 Array of size numYears holding the number of days in the year that is above 65F for every year - @param[out] sdC4 Array of size three holding the standard deviations of minimum July temperature (0), frost free days (1), number of days above 65F (2) - @param[out] PPTJuly Array of size numYears holding mean July precipitation (mm) for each year - @param[out] meanTempDryQuarter Array of size numYears holding the average temperature of the driest quarter of the year for every year - @param[out] minTempFebruary Array of size numYears holding the mean minimum temperature in february for every year - @param[out] sdCheatgrass Array of size 3 holding the standard deviations of July precipitation (0), mean - temperature of dry quarter (1), mean minimum temperature of February (2) - @param[in] numYears Number of years covered in the simulation - @param[in] startYear Start year of simulation - + @param[out] annualPPT_cm Array of size `numYears` containing annual precipitation amount [cm] + @param[out] meanAnnualTemp_C Array of size `numYears` containing annual mean temperatures [C] + @param[out] JulyMinTemp Array of size numYears holding minimum July temperatures [C] for each year + @param[out] frostFreeDays_days Array of size numYears holding the maximum consecutive days in a year without frost for every year + @param[out] ddAbove65F_degday Array of size numYears holding the amount of degree days [C x day] above 65 F + @param[out] JulyPPT_mm Array of size numYears holding July precipitation amount (mm) + @param[out] meanTempDriestQuarter_C Array of size numYears holding the average temperature of the driest quarter of the year for every year + @param[out] minTempFebruary_C Array of size numYears holding the mean minimum temperature in february for every year */ -void calcSiteClimate(SW_WEATHER_HIST **allHist, double **meanMonthlyTemp, double **maxMonthlyTemp, - double **minMonthlyTemp, double **meanMonthlyPPT, double *MMP_cm, double *MMT_C, - double *JulyMinTemp, int *frostFreeDays, double *degreeAbove65, double *sdC4, - double *PPTJuly, double *meanTempDryQuarter, double *minTempFebruary, - double *sdCheatgrass, int numYears, int startYear) { +void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, + double **meanMonthlyTemp_C, double **maxMonthlyTemp_C, double **minMonthlyTemp_C, + double **meanMonthlyPPT_cm, double *annualPPT_cm, double *meanAnnualTemp_C, double *JulyMinTemp, + double *frostFreeDays_days, double *ddAbove65F_degday, double *JulyPPT_mm, + double *meanTempDriestQuarter_C, double *minTempFebruary_C) { int month, yearIndex, year, day, numDaysYear, numDaysMonth, currMonDay, @@ -239,6 +251,8 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, double **meanMonthlyTemp, double // Get the running values needed for running standard deviation for frostFreeDays frostMean = get_running_mean(yearIndex + 1, prevFrostMean, consecNonFrost); frostSqr += get_running_sqr(prevFrostMean, frostMean, consecNonFrost); + meanAnnualTemp_C[yearIndex] /= numDaysYear; + // TODO: Mention in commit: R code incorrectly calculates meanAnnualTemp_C/MAT_C (doesn't account for leap years) } findDriestQtr(meanMonthlyTemp, meanMonthlyPPT, meanTempDryQuarter, numYears); @@ -252,19 +266,22 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, double **meanMonthlyTemp, double sdCheatgrass[0] = standardDeviation(PPTJuly, numYears); sdCheatgrass[1] = standardDeviation(meanTempDryQuarter, numYears); sdCheatgrass[2] = standardDeviation(minTempFebruary, numYears); + findDriestQtr(meanTempDriestQuarter_C, numYears, meanMonthlyTemp_C, meanMonthlyPPT_cm); } /** @brief Helper function to calcsiteClimate to find the driest quarter of the year's average temperature - @param[in] meanMonthlyTemp 2D array holding sum of monthly temperature for every month and year - @param[in] meanMonthlyPPT 2D array holding sum of monthly precipitation for every month and year - @param[out] meanTempDryQuarter 2D array holding sum of monthly temperature for every month and year - @param[in] numYears Number of years in the simulation run - @param[in] startYear Start year of the simulation + @param[in] numYears Number of years represented within simulation + @param[in] startYear Calendar year corresponding to first year of simulation + @param[out] meanMonthlyTemp_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] meanMonthlyPPT_cm_cm 2D array containing monthly amount precipitation (cm) with dimensions + of row (months) size MAX_MONTHS and columns (years) of size numYears + @param[out] meanTempDriestQuarter_C Array of size numYears holding the average temperature of the driest quarter of the year for every year */ -void findDriestQtr(double **meanMonthlyTemp, double **meanMonthlyPPT, double *meanTempDryQuarter, - int numYears) { +void findDriestQtr(double *meanTempDriestQuarter_C, int numYears, double **meanMonthlyTemp_C, + double **meanMonthlyPPT_cm) { int yearIndex, month, prevMonth, nextMonth; diff --git a/SW_Weather.h b/SW_Weather.h index 2b48ca4c9..7e66e8d0b 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -104,17 +104,20 @@ extern SW_WEATHER SW_Weather; void SW_WTH_setup(void); void SW_WTH_read(void); Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather); -void averageClimateAcrossYears(double **meanMonthlyTemp, double **maxMonthlyTemp, - double **minMonthlyTemp, double **meanMonthlyPPT, double *meanMonthlyTempAnn, - double *maxMonthlyTempAnn, double *minMonthlyTempAnn, double *meanMonthlyPPTAnn, - double *MAP_cm, double *MAT_C, double MMT_C[], double MMP_cm[], int numYears); -void calcSiteClimate(SW_WEATHER_HIST **allHist, double **meanMonthlyTemp, double **maxMonthlyTemp, - double **minMonthlyTemp, double **meanMonthlyPPT, double *MMP_cm, double *MMT_C, - double *JulyMinTemp, int *frostFreeDays, double *degreeAbove65, double *sdC4, - double *PPTJuly, double *meanTempDryQuarter, double *minTempFebruary, - double *sdCheatgrass, int numYears, int startYear); -void findDriestQtr(double **meanMonthlyTemp, double **meanMonthlyPPT, double *meanTempDryQuarter, - int numYears); +void averageClimateAcrossYears(double **meanMonthlyTemp_C, double **maxMonthlyTemp_C, + double **minMonthlyTemp_C, double **meanMonthlyPPT_cm, double numYears, double JulyMinTemp[], + double frostFreeDays_days[], double ddAbove65F_degday[], double JulyPPT_mm[], + double meanTempDriestQuarter_C[], double minTempFebruary_C[], double annualPPT_cm[], + double meanAnnualTemp_C[], double *meanMonthlyTempAnn, double *maxMonthlyTempAnn, + double *minMonthlyTempAnn, double *meanMonthlyPPTAnn, double *sdC4, double *sdCheatgrass, + double *MAT_C, double *MAP_cm); +void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, + double **meanMonthlyTemp_C, double **maxMonthlyTemp_C, double **minMonthlyTemp_C, + double **meanMonthlyPPT_cm, double *annualPPT_cm, double *meanAnnualTemp_C, double *JulyMinTemp, + double *frostFreeDays_days, double *ddAbove65F_degday, double *JulyPPT_mm, + double *meanTempDriestQuarter_C, double *minTempFebruary_C); +void findDriestQtr(double *meanTempDriestQuarter_C, int numYears, double **meanMonthlyTemp_C, + double **meanMonthlyPPT_cm); void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, unsigned int n_years); void deallocateAllWeather(void); void _clear_hist_weather(SW_WEATHER_HIST *yearWeather); From 0b87577b2d54ff783dc093ba9d438fb289f0e965 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 14 Jul 2022 00:00:09 -0400 Subject: [PATCH 088/326] Moved standard deviation to `averageClimateAcrossYears()` - Moved standard deviation of C4 and cheatgrass variables from `calcSiteClimate()` to `averageClimateAcrossYears()` --- SW_Weather.c | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 3d70f4663..4ce0c1ecc 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -118,9 +118,6 @@ void averageClimateAcrossYears(double **meanMonthlyTemp_C, double **maxMonthlyTe double *minMonthlyTempAnn, double *meanMonthlyPPTAnn, double *sdC4, double *sdCheatgrass, double *MAT_C, double *MAP_cm) { - int month, numLeapYears = (numYears / 4) + 1; - double numDaysInSimulation = (numYears * 365.) + numLeapYears; - double avgDaysInSimulation = numDaysInSimulation / numYears; int month; for(month = 0; month < MAX_MONTHS; month++) { @@ -132,6 +129,16 @@ void averageClimateAcrossYears(double **meanMonthlyTemp_C, double **maxMonthlyTe *MAP_cm = mean(annualPPT_cm, numYears); *MAT_C = mean(meanAnnualTemp_C, numYears); + + // Calculate and set standard deviation of C4 variables (frostFreeDays is a running sd) + sdC4[0] = standardDeviation(JulyMinTemp, numYears); + sdC4[1] = standardDeviation(frostFreeDays_days, numYears); + sdC4[2] = standardDeviation(ddAbove65F_degday, numYears); + + // Calculate and set the standard deviation of cheatgrass variables + sdCheatgrass[0] = standardDeviation(JulyPPT_mm, numYears); + sdCheatgrass[1] = standardDeviation(meanTempDriestQuarter_C, numYears); + sdCheatgrass[2] = standardDeviation(minTempFebruary_C, numYears); } /** @@ -246,26 +253,10 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, ddAbove65F_degday[yearIndex] = totalAbove65; frostFreeDays_days[yearIndex] = (double)consecNonFrost; - prevFrostMean = frostMean; - - // Get the running values needed for running standard deviation for frostFreeDays - frostMean = get_running_mean(yearIndex + 1, prevFrostMean, consecNonFrost); - frostSqr += get_running_sqr(prevFrostMean, frostMean, consecNonFrost); meanAnnualTemp_C[yearIndex] /= numDaysYear; // TODO: Mention in commit: R code incorrectly calculates meanAnnualTemp_C/MAT_C (doesn't account for leap years) } - findDriestQtr(meanMonthlyTemp, meanMonthlyPPT, meanTempDryQuarter, numYears); - - // Calculate and set standard deviation of C4 variables (frostFreeDays is a running sd) - sdC4[0] = standardDeviation(JulyMinTemp, numYears); - sdC4[1] = final_running_sd(numYears, frostSqr); - sdC4[2] = standardDeviation(degreeAbove65, numYears); - - // Calculate and set the standard deviation of cheatgrass variables - sdCheatgrass[0] = standardDeviation(PPTJuly, numYears); - sdCheatgrass[1] = standardDeviation(meanTempDryQuarter, numYears); - sdCheatgrass[2] = standardDeviation(minTempFebruary, numYears); findDriestQtr(meanTempDriestQuarter_C, numYears, meanMonthlyTemp_C, meanMonthlyPPT_cm); } From a0ca4306af32313b802d77281c9beb02c482e588 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 14 Jul 2022 00:02:58 -0400 Subject: [PATCH 089/326] Made days and years better to read in `calcSiteClimate()` - Removed ternary operator and added function call to get `numDaysYear` - Added modulo to getting number of days in month to make it full-proof --- SW_Weather.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 4ce0c1ecc..7ae8f3db5 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -172,8 +172,8 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, double *meanTempDriestQuarter_C, double *minTempFebruary_C) { - int month, yearIndex, year, day, numDaysYear, numDaysMonth, currMonDay, - consecNonFrost, currentNonFrost, July = 6, February = 1; + int month, yearIndex, year, day, numDaysYear, numDaysMonth = Time_days_in_month(0), + currMonDay, consecNonFrost, currentNonFrost; double currentTempMin, currentTempMean, totalAbove65, currentJulyMin, JulyPPT; @@ -187,10 +187,9 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, for(yearIndex = 0; yearIndex < numYears; yearIndex++) { year = yearIndex + startYear; Time_new_year(year); - numDaysYear = isleapyear(year) ? 366 : 365; + numDaysYear = Time_get_lastdoy_y(year); month = 0; currMonDay = 0; - numDaysMonth = 31; currentJulyMin = 999; totalAbove65 = 0; currentNonFrost = 0; @@ -239,7 +238,7 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, if(month == Feb) minTempFebruary_C[yearIndex] /= numDaysMonth; month++; - if(month != Dec + 1) numDaysMonth = Time_days_in_month(month); + numDaysMonth = Time_days_in_month(month % 12); currMonDay = 0; } From bc5bb975c92cf0b88a903e91d684007b9321f01a Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 14 Jul 2022 00:06:32 -0400 Subject: [PATCH 090/326] Added `memset()` use/variable changes in `calcSiteClimate()` - Use `memset()` to initialize all variables that use accumulation to zero - Exchanged "July" and "February" for macros "Jul" and "Feb" --- SW_Weather.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 7ae8f3db5..ef25b9ebe 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -183,6 +183,10 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, memset(minMonthlyTemp_C[month], 0., sizeof(double) * numYears); memset(meanMonthlyPPT_cm[month], 0., sizeof(double) * numYears); } + memset(annualPPT_cm, 0., sizeof(double) * numYears); + memset(meanAnnualTemp_C, 0., sizeof(double) * numYears); + memset(minTempFebruary_C, 0., sizeof(double) * numYears); + memset(JulyPPT_mm, 0., sizeof(double) * numYears); for(yearIndex = 0; yearIndex < numYears; yearIndex++) { year = yearIndex + startYear; @@ -209,7 +213,7 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, currentTempMin = allHist[yearIndex]->temp_min[day]; currentTempMean = allHist[yearIndex]->temp_avg[day]; - if(month == July){ + if(month == Jul){ currentJulyMin = (currentTempMin < currentJulyMin) ? currentTempMin : currentJulyMin; JulyPPT += allHist[yearIndex]->ppt[day] * 10; @@ -224,8 +228,8 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, currentNonFrost = 0; } - if(month == February) { - minTempFebruary[yearIndex] += allHist[yearIndex]->temp_min[day]; + if(month == Feb) { + minTempFebruary_C[yearIndex] += allHist[yearIndex]->temp_min[day]; } if(currMonDay == numDaysMonth) { From 562f2de58b294a8d10f479d6948c0b2e1f05a94b Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 14 Jul 2022 00:07:46 -0400 Subject: [PATCH 091/326] Added unit tests for `standardDeviation()` and `mean()` --- test/test_generic.cc | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test/test_generic.cc b/test/test_generic.cc index c89a40ed7..4678398d3 100644 --- a/test/test_generic.cc +++ b/test/test_generic.cc @@ -59,4 +59,43 @@ namespace { } } + TEST(StandardDeviationTest, UnexpectedAndExpectedCases) { + double value[1] = {5.}; + double noValue[0] = {}; + double values[5] = {5.4, 3.4, 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(noValue, 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); + } + + TEST(MeanTest, UnexpectedAndExpectedCases) { + + double value[0] = {}; + double result; + double values[5] = {1.8, 2.2, 10., 13.5, 3.2}; + + result = mean(value, 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); + + } + } // namespace From 70990b55fd564ddd70745c28114581c3f16d3e7f Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 14 Jul 2022 00:14:40 -0400 Subject: [PATCH 092/326] Matched unit test variable names to ones related to climate --- test/test_SW_Weather.cc | 257 ++++++++++++++++++++-------------------- 1 file changed, 129 insertions(+), 128 deletions(-) diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 2688d8b17..512a9e865 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -141,107 +141,110 @@ namespace { // This test relies on allHist from `SW_WEATHER` being already filled - double meanMonthlyAvgAnn[MAX_MONTHS]; - double meanMonthlyMaxAnn[MAX_MONTHS]; - double meanMonthlyMinAnn[MAX_MONTHS]; + double meanMonthlyTempAnn[MAX_MONTHS]; + double maxMonthlyTempAnn[MAX_MONTHS]; + double minMonthlyTempAnn[MAX_MONTHS]; double meanMonthlyPPTAnn[MAX_MONTHS]; double JulyMinTemp[31]; - int frostFreeDays[31]; - double degreeAbove65[31]; - double PPTJuly[31]; - double meanTempDryQuarter[31]; - double minTempFebruary[31]; + double frostFreeDays_days[31]; + double ddAbove65F_degday[31]; + double JulyPPT_mm[31]; + double meanTempDriestQuarter_C[31]; + double minTempFebruary_C[31]; double sdCheatgrass[3]; double sdC4[3]; - double MMP_cm[31]; - double MMT_C[31]; + double annualPPT_cm[31]; + double meanAnnualTemp_C[31]; double MAP_cm; double MAT_C; - double **arrayPPTYear; - arrayPPTYear = new double*[MAX_MONTHS]; + double **meanMonthlyPPT_cm; + meanMonthlyPPT_cm = new double*[MAX_MONTHS]; - double **arrayTempAvgYear = new double*[MAX_MONTHS]; + double **meanMonthlyTemp_C = new double*[MAX_MONTHS]; - double **arrayTempMinYear = new double*[MAX_MONTHS]; + double **minMonthlyTemp_C = new double*[MAX_MONTHS]; - double **arrayTempMaxYear = new double*[MAX_MONTHS]; + double **maxMonthlyTemp_C = new double*[MAX_MONTHS]; for(int month = 0; month < MAX_MONTHS; month++) { - arrayPPTYear[month] = new double[31]; - arrayTempAvgYear[month] = new double[31]; - arrayTempMinYear[month] = new double[31]; - arrayTempMaxYear[month] = new double[31]; + meanMonthlyPPT_cm[month] = new double[31]; + meanMonthlyTemp_C[month] = new double[31]; + minMonthlyTemp_C[month] = new double[31]; + maxMonthlyTemp_C[month] = new double[31]; for(int year = 0; year < 31; year++) { - arrayPPTYear[month][year] = 0.; - arrayTempAvgYear[month][year] = 0.; - arrayTempMinYear[month][year] = 0.; - arrayTempMaxYear[month][year] = 0.; - MMP_cm[year] = 0.; - MMT_C[year] = 0.; + meanMonthlyPPT_cm[month][year] = 0.; + meanMonthlyTemp_C[month][year] = 0.; + minMonthlyTemp_C[month][year] = 0.; + maxMonthlyTemp_C[month][year] = 0.; + annualPPT_cm[year] = 0.; + meanAnnualTemp_C[year] = 0.; } } // 1980 is start year of the simulation - calcSiteClimate(SW_Weather.allHist, arrayTempAvgYear, arrayTempMaxYear, arrayTempMinYear, - arrayPPTYear, MMP_cm, MMT_C, JulyMinTemp, frostFreeDays, degreeAbove65, sdC4, - PPTJuly, meanTempDryQuarter, minTempFebruary, sdCheatgrass, 31, 1980); - - averageClimateAcrossYears(arrayTempAvgYear, arrayTempMaxYear, arrayTempMinYear, arrayPPTYear, - meanMonthlyAvgAnn, meanMonthlyMaxAnn, meanMonthlyMinAnn, meanMonthlyPPTAnn, - &MAP_cm, &MAT_C, MMT_C, MMP_cm, 31); - - //swprintf("%f\n\n\n", MMT_C[0]); - EXPECT_NEAR(meanMonthlyAvgAnn[0], -9.325551, tol6); - EXPECT_NEAR(meanMonthlyMaxAnn[0], -2.714381, tol6); - EXPECT_NEAR(meanMonthlyMinAnn[0], -15.936722, tol6); + calcSiteClimate(SW_Weather.allHist, 31, 1980, meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, + meanMonthlyPPT_cm, annualPPT_cm, meanAnnualTemp_C, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, + JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C); + + averageClimateAcrossYears(meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, + meanMonthlyPPT_cm, 31, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, + JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C, annualPPT_cm, + meanAnnualTemp_C, meanMonthlyTempAnn, maxMonthlyTempAnn, minMonthlyTempAnn, + meanMonthlyPPTAnn, sdC4, sdCheatgrass, &MAT_C, &MAP_cm); + + EXPECT_NEAR(meanMonthlyTempAnn[0], -9.325551, tol6); + EXPECT_NEAR(maxMonthlyTempAnn[0], -2.714381, tol6); + EXPECT_NEAR(minMonthlyTempAnn[0], -15.936722, tol6); EXPECT_NEAR(meanMonthlyPPTAnn[0], 0.221530, tol6); - EXPECT_NEAR(MMT_C[0], 1656.100000, tol1); - EXPECT_NEAR(MMP_cm[0], 59.27, tol3); - EXPECT_NEAR(MAP_cm, 62.817419, tol1); - EXPECT_NEAR(MAT_C, 4.153896, tol1); + EXPECT_NEAR(meanAnnualTemp_C[0], 4.524863, tol6); + EXPECT_NEAR(annualPPT_cm[0], 59.2700004, tol6); + EXPECT_NEAR(MAP_cm, 62.817419, tol6); + EXPECT_NEAR(MAT_C, 4.154009, tol6); // Reset values for(int year = 0; year < 31; year++) { for(int month = 0; month < MAX_MONTHS; month++) { - arrayPPTYear[month][year] = 0.; - arrayTempAvgYear[month][year] = 0.; - arrayTempMinYear[month][year] = 0.; - arrayTempMaxYear[month][year] = 0.; + meanMonthlyPPT_cm[month][year] = 0.; + meanMonthlyTemp_C[month][year] = 0.; + minMonthlyTemp_C[month][year] = 0.; + maxMonthlyTemp_C[month][year] = 0.; } - MMP_cm[year] = 0.; - MMT_C[year] = 0.; + annualPPT_cm[year] = 0.; + meanAnnualTemp_C[year] = 0.; } // Tests for one year of simulation - calcSiteClimate(SW_Weather.allHist, arrayTempAvgYear, arrayTempMaxYear, arrayTempMinYear, - arrayPPTYear, MMP_cm, MMT_C, JulyMinTemp, frostFreeDays, degreeAbove65, sdC4, - PPTJuly, meanTempDryQuarter, minTempFebruary, sdCheatgrass, 1, 1980); - - averageClimateAcrossYears(arrayTempAvgYear, arrayTempMaxYear, arrayTempMinYear, arrayPPTYear, - meanMonthlyAvgAnn, meanMonthlyMaxAnn, meanMonthlyMinAnn, meanMonthlyPPTAnn, - &MAP_cm, &MAT_C, MMT_C, MMP_cm, 1); - - EXPECT_NEAR(meanMonthlyAvgAnn[0], -8.432581, tol6); - EXPECT_NEAR(meanMonthlyMaxAnn[0], -2.562581, tol6); - EXPECT_NEAR(meanMonthlyMinAnn[0], -14.302581, tol6); + calcSiteClimate(SW_Weather.allHist, 1, 1980, meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, + meanMonthlyPPT_cm, annualPPT_cm, meanAnnualTemp_C, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, + JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C); + + averageClimateAcrossYears(meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, + meanMonthlyPPT_cm, 1, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, + JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C, annualPPT_cm, + meanAnnualTemp_C, meanMonthlyTempAnn, maxMonthlyTempAnn, minMonthlyTempAnn, + meanMonthlyPPTAnn, sdC4, sdCheatgrass, &MAT_C, &MAP_cm); + + EXPECT_NEAR(meanMonthlyTempAnn[0], -8.432581, tol6); + EXPECT_NEAR(maxMonthlyTempAnn[0], -2.562581, tol6); + EXPECT_NEAR(minMonthlyTempAnn[0], -14.302581, tol6); EXPECT_NEAR(meanMonthlyPPTAnn[0], 0.488387, tol6); EXPECT_NEAR(MAP_cm, 59.27, tol1); EXPECT_NEAR(MAT_C, 4.524863, tol1); for(int month = 0; month < MAX_MONTHS; month++) { - delete[] arrayPPTYear[month]; - delete[] arrayTempAvgYear[month]; - delete[] arrayTempMinYear[month]; - delete[] arrayTempMaxYear[month]; + delete[] meanMonthlyPPT_cm[month]; + delete[] meanMonthlyTemp_C[month]; + delete[] minMonthlyTemp_C[month]; + delete[] maxMonthlyTemp_C[month]; } - delete[] arrayPPTYear; - delete[] arrayTempAvgYear; - delete[] arrayTempMinYear; - delete[] arrayTempMaxYear; + delete[] meanMonthlyPPT_cm; + delete[] meanMonthlyTemp_C; + delete[] minMonthlyTemp_C; + delete[] maxMonthlyTemp_C; } @@ -249,80 +252,78 @@ namespace { // This test relies on allHist from `SW_WEATHER` being already filled - double JulyMinTemp[31]; - int frostFreeDays[31]; - double degreeAbove65[31]; - double PPTJuly[31]; - double meanTempDryQuarter[31]; - double minTempFebruary[31]; - double sdCheatgrass[3]; - double sdC4[3]; - double MMP_cm[31]; - double MMT_C[31]; + double JulyMinTemp[31]; // 31 = Number of years in the simulation + double frostFreeDays_days[31]; + double ddAbove65F_degday[31]; + double JulyPPT_mm[31]; + double meanTempDriestQuarter_C[31]; + double minTempFebruary_C[31]; + double annualPPT_cm[31]; + double meanAnnualTemp_C[31]; - double **arrayPPTYear; - arrayPPTYear = new double*[MAX_MONTHS]; + double **meanMonthlyPPT_cm; + meanMonthlyPPT_cm = new double*[MAX_MONTHS]; - double **arrayTempAvgYear; - arrayTempAvgYear = new double*[MAX_MONTHS]; + double **meanMonthlyTemp_C; + meanMonthlyTemp_C = new double*[MAX_MONTHS]; - double **arrayTempMinYear; - arrayTempMinYear = new double*[MAX_MONTHS]; + double **minMonthlyTemp_C; + minMonthlyTemp_C = new double*[MAX_MONTHS]; - double **arrayTempMaxYear; - arrayTempMaxYear = new double*[MAX_MONTHS]; + double **maxMonthlyTemp_C; + maxMonthlyTemp_C = new double*[MAX_MONTHS]; for(int month = 0; month < MAX_MONTHS; month++) { - arrayPPTYear[month] = new double[31]; - arrayTempAvgYear[month] = new double[31]; - arrayTempMinYear[month] = new double[31]; - arrayTempMaxYear[month] = new double[31]; + meanMonthlyPPT_cm[month] = new double[31]; + meanMonthlyTemp_C[month] = new double[31]; + minMonthlyTemp_C[month] = new double[31]; + maxMonthlyTemp_C[month] = new double[31]; for(int year = 0; year < 31; year++) { - arrayPPTYear[month][year] = 0.; - arrayTempAvgYear[month][year] = 0.; - arrayTempMinYear[month][year] = 0.; - arrayTempMaxYear[month][year] = 0.; - MMP_cm[year] = 0.; - MMT_C[year] = 0.; - minTempFebruary[year] = 0.; + meanMonthlyPPT_cm[month][year] = 0.; + meanMonthlyTemp_C[month][year] = 0.; + minMonthlyTemp_C[month][year] = 0.; + maxMonthlyTemp_C[month][year] = 0.; + annualPPT_cm[year] = 0.; + meanAnnualTemp_C[year] = 0.; + minTempFebruary_C[year] = 0.; } } // 1980 is start year of the simulation - calcSiteClimate(SW_Weather.allHist, arrayTempAvgYear, arrayTempMaxYear, arrayTempMinYear, - arrayPPTYear, MMP_cm, MMT_C, JulyMinTemp, frostFreeDays, degreeAbove65, sdC4, - PPTJuly, meanTempDryQuarter, minTempFebruary, sdCheatgrass, 31, 1980); + calcSiteClimate(SW_Weather.allHist, 31, 1980, meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, + meanMonthlyPPT_cm, annualPPT_cm, meanAnnualTemp_C, JulyMinTemp, frostFreeDays_days, + ddAbove65F_degday, JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C); // Average of average temperature of January in 1980 - EXPECT_NEAR(arrayTempAvgYear[0][0], -8.432581, tol6); + EXPECT_NEAR(meanMonthlyTemp_C[0][0], -8.432581, tol6); // Average of max temperature in Januaray 1980 - EXPECT_NEAR(arrayTempMaxYear[0][0], -2.562581, tol6); + EXPECT_NEAR(maxMonthlyTemp_C[0][0], -2.562581, tol6); // Average of min temperature in Januaray 1980 - EXPECT_NEAR(arrayTempMinYear[0][0], -14.302581, tol6); + EXPECT_NEAR(minMonthlyTemp_C[0][0], -14.302581, tol6); // Average January precipitation in 1980 - EXPECT_NEAR(arrayPPTYear[0][0], 0.488387, tol6); + EXPECT_NEAR(meanMonthlyPPT_cm[0][0], 0.488387, tol6); // Average temperature of three driest month of first year - EXPECT_NEAR(meanTempDryQuarter[0], .936387, tol6); + EXPECT_NEAR(meanTempDriestQuarter_C[0], .936387, tol6); // Average precipiation of first year of simulation - EXPECT_NEAR(MMP_cm[0], 59.27, tol6); + EXPECT_NEAR(annualPPT_cm[0], 59.27, tol6); // Average temperature of first year of simulation - EXPECT_NEAR(MMT_C[0], 1656.100000, tol6); + EXPECT_NEAR(meanAnnualTemp_C[0], 4.5248633, tol6); // First year's July minimum temperature EXPECT_NEAR(JulyMinTemp[0], 2.810000, tol6); // First year's number of most consecutive frost free days - EXPECT_EQ(frostFreeDays[0], 92); + EXPECT_EQ(frostFreeDays_days[0], 92); // Sum of all temperature above 65F (18.333C) in first year - EXPECT_NEAR(degreeAbove65[0], 13.546000, tol6); + EXPECT_NEAR(ddAbove65F_degday[0], 13.546000, tol6); // Standard deviation of C4 variables EXPECT_NEAR(sdC4[0], 1.785535, tol6); @@ -341,16 +342,16 @@ namespace { EXPECT_NEAR(sdCheatgrass[2], 2.618434, tol6); for(int month = 0; month < MAX_MONTHS; month++) { - delete[] arrayPPTYear[month]; - delete[] arrayTempAvgYear[month]; - delete[] arrayTempMinYear[month]; - delete[] arrayTempMaxYear[month]; + delete[] meanMonthlyPPT_cm[month]; + delete[] meanMonthlyTemp_C[month]; + delete[] minMonthlyTemp_C[month]; + delete[] maxMonthlyTemp_C[month]; } - delete[] arrayPPTYear; - delete[] arrayTempAvgYear; - delete[] arrayTempMinYear; - delete[] arrayTempMaxYear; + delete[] meanMonthlyPPT_cm; + delete[] meanMonthlyTemp_C; + delete[] minMonthlyTemp_C; + delete[] maxMonthlyTemp_C; } @@ -360,38 +361,38 @@ namespace { 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 - double **arrayPPTYear; - arrayPPTYear = new double*[MAX_MONTHS]; + double **meanMonthlyPPT_cm; + meanMonthlyPPT_cm = new double*[MAX_MONTHS]; - double **arrayTempAvgYear = new double*[MAX_MONTHS]; + double **meanMonthlyTemp_C = new double*[MAX_MONTHS]; for(int month = 0; month < MAX_MONTHS; month++) { - arrayPPTYear[month] = new double[2]; - arrayTempAvgYear[month] = new double[2]; + meanMonthlyPPT_cm[month] = new double[2]; + meanMonthlyTemp_C[month] = new double[2]; for(int year = 0; year < 2; year++) { - arrayPPTYear[month][year] = monthlyPPT[month]; - arrayTempAvgYear[month][year] = monthlyTemp[month]; + meanMonthlyPPT_cm[month][year] = monthlyPPT[month]; + meanMonthlyTemp_C[month][year] = monthlyTemp[month]; } } // 1980 is start year of the simulation - findDriestQtr(arrayTempAvgYear, arrayPPTYear, result, 1); + findDriestQtr(result, 1, meanMonthlyTemp_C, meanMonthlyPPT_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_DOUBLE_EQ(result[0], 1.4333333333333333); - findDriestQtr(arrayTempAvgYear, arrayPPTYear, result, 2); + findDriestQtr(result, 2, meanMonthlyTemp_C, meanMonthlyPPT_cm); EXPECT_DOUBLE_EQ(result[0], 1.4333333333333333); for(int month = 0; month < MAX_MONTHS; month++) { - delete[] arrayPPTYear[month]; - delete[] arrayTempAvgYear[month]; + delete[] meanMonthlyPPT_cm[month]; + delete[] meanMonthlyTemp_C[month]; } - delete[] arrayPPTYear; - delete[] arrayTempAvgYear; + delete[] meanMonthlyPPT_cm; + delete[] meanMonthlyTemp_C; } From a337514593678a2faa5aa9278deea8cd5cb80dda Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 14 Jul 2022 00:19:25 -0400 Subject: [PATCH 093/326] Standard deviation tests moved to `averageClimateAcrossYears()` tests --- test/test_SW_Weather.cc | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 512a9e865..0f88ca083 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -204,6 +204,15 @@ namespace { EXPECT_NEAR(MAP_cm, 62.817419, tol6); EXPECT_NEAR(MAT_C, 4.154009, tol6); + // Standard deviation of C4 variables + EXPECT_NEAR(sdC4[0], 1.785535, tol6); + EXPECT_NEAR(sdC4[1], 14.091788, tol6); + EXPECT_NEAR(sdC4[2], 19.953560, tol6); + + // Standard deviation of cheatgrass variables + EXPECT_NEAR(sdCheatgrass[0], 21.598367, tol6); + EXPECT_NEAR(sdCheatgrass[1], 7.171922, tol6); + EXPECT_NEAR(sdCheatgrass[2], 2.618434, tol6); // Reset values for(int year = 0; year < 31; year++) { for(int month = 0; month < MAX_MONTHS; month++) { @@ -325,10 +334,6 @@ namespace { // Sum of all temperature above 65F (18.333C) in first year EXPECT_NEAR(ddAbove65F_degday[0], 13.546000, tol6); - // Standard deviation of C4 variables - EXPECT_NEAR(sdC4[0], 1.785535, tol6); - EXPECT_NEAR(sdC4[1], 14.091788, tol6); - EXPECT_NEAR(sdC4[2], 19.953560, tol6); // Total precipitation in July of first year EXPECT_NEAR(PPTJuly[0], 18.300000, tol6); @@ -336,10 +341,6 @@ namespace { // Smallest temperature in all February first year EXPECT_NEAR(minTempFebruary[0], -12.822069, tol6); - // Standard deviation of cheatgrass variables - EXPECT_NEAR(sdCheatgrass[0], 21.598367, tol6); - EXPECT_NEAR(sdCheatgrass[1], 7.171922, tol6); - EXPECT_NEAR(sdCheatgrass[2], 2.618434, tol6); for(int month = 0; month < MAX_MONTHS; month++) { delete[] meanMonthlyPPT_cm[month]; From d6c42a14b3f2b9c6c5ad65ea6f6fe1e5916e652c Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 14 Jul 2022 00:25:28 -0400 Subject: [PATCH 094/326] Expanded unit tests for `calcSiteClimate()` - Values are tested when input is all one - This is done for when "startYear" is 1980 and 1981 --- test/test_SW_Weather.cc | 98 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 2 deletions(-) diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 0f88ca083..0bb3810c7 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -299,6 +299,9 @@ namespace { minTempFebruary_C[year] = 0.; } } + + SW_WTH_read(); + // 1980 is start year of the simulation calcSiteClimate(SW_Weather.allHist, 31, 1980, meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, meanMonthlyPPT_cm, annualPPT_cm, meanAnnualTemp_C, JulyMinTemp, frostFreeDays_days, @@ -334,13 +337,104 @@ namespace { // Sum of all temperature above 65F (18.333C) in first year EXPECT_NEAR(ddAbove65F_degday[0], 13.546000, tol6); + // Total precipitation in July of first year + EXPECT_NEAR(JulyPPT_mm[0], 18.300000, tol6); + + // Smallest temperature in all February first year + EXPECT_NEAR(minTempFebruary_C[0], -12.822069, tol6); + + for(int year = 0; year < 2; year++) { + for(int day = 0; day < 366; day++) { + SW_Weather.allHist[year]->temp_max[day] = 1.; + SW_Weather.allHist[year]->temp_min[day] = 1.; + SW_Weather.allHist[year]->temp_avg[day] = 1.; + SW_Weather.allHist[year]->ppt[day] = 1.; + } + } + + calcSiteClimate(SW_Weather.allHist, 2, 1980, meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, + meanMonthlyPPT_cm, annualPPT_cm, meanAnnualTemp_C, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, + JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C); + + // Start of leap year tests (startYear = 1980) + + // Average of average temperature of January in 1980 + EXPECT_EQ(meanMonthlyTemp_C[0][0], 1.); + + // Average of max temperature in Januaray 1980 + EXPECT_EQ(maxMonthlyTemp_C[0][0], 1.); + + // Average of min temperature in Januaray 1980 + EXPECT_EQ(minMonthlyTemp_C[0][0], 1.); + + // Average January precipitation in 1980 + EXPECT_EQ(meanMonthlyPPT_cm[0][0], 1.); + + // Average temperature of three driest month of first year + EXPECT_EQ(meanTempDriestQuarter_C[0], 1.); + + // Average precipiation of first year of simulation + EXPECT_EQ(annualPPT_cm[0], 366.); + + // Average temperature of first year of simulation + EXPECT_EQ(meanAnnualTemp_C[0], 1.); + + // First year's July minimum temperature + EXPECT_EQ(JulyMinTemp[0], 1.); + + // First year's number of most consecutive frost free days + EXPECT_EQ(frostFreeDays_days[0], 0); + + // Sum of all temperature above 65F (18.333C) in first year + EXPECT_EQ(ddAbove65F_degday[0], 0); // Total precipitation in July of first year - EXPECT_NEAR(PPTJuly[0], 18.300000, tol6); + EXPECT_EQ(JulyPPT_mm[0], 310.); // Smallest temperature in all February first year - EXPECT_NEAR(minTempFebruary[0], -12.822069, tol6); + EXPECT_NEAR(minTempFebruary_C[0], 1., tol6); + + // Start of nonleap year tests (startYear = 1981) + + calcSiteClimate(SW_Weather.allHist, 2, 1981, meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, + meanMonthlyPPT_cm, annualPPT_cm, meanAnnualTemp_C, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, + JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C); + + // Average of average temperature of January in 1981 + EXPECT_EQ(meanMonthlyTemp_C[0][0], 1.); + + // Average of max temperature in Januaray 1981 + EXPECT_EQ(maxMonthlyTemp_C[0][0], 1.); + + // Average of min temperature in Januaray 1981 + EXPECT_EQ(minMonthlyTemp_C[0][0], 1.); + + // Average January precipitation in 1980 + EXPECT_EQ(meanMonthlyPPT_cm[0][0], 1.); + + // Average temperature of three driest month of first year + EXPECT_EQ(meanTempDriestQuarter_C[0], 1.); + + // Average precipiation of first year of simulation + EXPECT_EQ(annualPPT_cm[0], 365.); + + // Average temperature of first year of simulation + EXPECT_EQ(meanAnnualTemp_C[0], 1.); + + // First year's July minimum temperature + EXPECT_EQ(JulyMinTemp[0], 1.); + // First year's number of most consecutive frost free days + EXPECT_EQ(frostFreeDays_days[0], 0); + + // Sum of all temperature above 65F (18.333C) in first year + EXPECT_EQ(ddAbove65F_degday[0], 0); + + // Total precipitation in July of first year + EXPECT_EQ(JulyPPT_mm[0], 310.); + + // Smallest temperature in all February first year + EXPECT_NEAR(minTempFebruary_C[0], 1., tol6); for(int month = 0; month < MAX_MONTHS; month++) { delete[] meanMonthlyPPT_cm[month]; From 10bb99e35d651acde29199ab24e93a3417bcaefc Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 14 Jul 2022 00:26:49 -0400 Subject: [PATCH 095/326] Expanded unit tests for `averageClimateAcrossYears()` - Added a section of testing that tests values when all input is one --- test/test_SW_Weather.cc | 61 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 0bb3810c7..6f55f79a2 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -213,6 +213,19 @@ namespace { EXPECT_NEAR(sdCheatgrass[0], 21.598367, tol6); EXPECT_NEAR(sdCheatgrass[1], 7.171922, tol6); EXPECT_NEAR(sdCheatgrass[2], 2.618434, tol6); + + for(int month = 0; month < MAX_MONTHS; month++) { + for(int year = 0; year < 31; year++) { + + meanMonthlyPPT_cm[month][year] = 1.; + meanMonthlyTemp_C[month][year] = 1.; + minMonthlyTemp_C[month][year] = 1.; + maxMonthlyTemp_C[month][year] = 1.; + annualPPT_cm[year] = 1.; + meanAnnualTemp_C[year] = 1.; + } + } + // Reset values for(int year = 0; year < 31; year++) { for(int month = 0; month < MAX_MONTHS; month++) { @@ -242,6 +255,54 @@ namespace { EXPECT_NEAR(MAP_cm, 59.27, tol1); EXPECT_NEAR(MAT_C, 4.524863, tol1); + // Standard deviation of C4 variables of one year + EXPECT_TRUE(isnan(sdC4[0])); + EXPECT_TRUE(isnan(sdC4[1])); + EXPECT_TRUE(isnan(sdC4[2])); + + // Standard deviation of cheatgrass variables of one year + EXPECT_TRUE(isnan(sdCheatgrass[0])); + EXPECT_TRUE(isnan(sdCheatgrass[1])); + EXPECT_TRUE(isnan(sdCheatgrass[2])); + + for(int year = 0; year < 31; year++) { + for(int day = 0; day < 366; day++) { + SW_Weather.allHist[year]->temp_max[day] = 1.; + SW_Weather.allHist[year]->temp_min[day] = 1.; + SW_Weather.allHist[year]->temp_avg[day] = 1.; + SW_Weather.allHist[year]->ppt[day] = 1.; + } + } + + // Start of tests with all `allHist` inputs of 1 + calcSiteClimate(SW_Weather.allHist, 31, 1980, meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, + meanMonthlyPPT_cm, annualPPT_cm, meanAnnualTemp_C, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, + JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C); + + averageClimateAcrossYears(meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, + meanMonthlyPPT_cm, 31, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, + JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C, annualPPT_cm, + meanAnnualTemp_C, meanMonthlyTempAnn, maxMonthlyTempAnn, minMonthlyTempAnn, + meanMonthlyPPTAnn, sdC4, sdCheatgrass, &MAT_C, &MAP_cm); + + EXPECT_EQ(meanMonthlyTempAnn[0], 1.); + EXPECT_EQ(maxMonthlyTempAnn[0], 1.); + EXPECT_EQ(minMonthlyTempAnn[0], 1.); + EXPECT_EQ(meanMonthlyPPTAnn[0], 1.); + // EXPECT_NEAR because of tests inaccuracy with the actual value + // Tolerance of 1 because the inaccurate value the test has is closer to 365 + EXPECT_NEAR(MAP_cm, 366., 1); + EXPECT_EQ(MAT_C, 1.); + + // Standard deviation of C4 variables of one year + EXPECT_EQ(sdC4[0], 0.); + EXPECT_EQ(sdC4[1], 0.); + EXPECT_EQ(sdC4[2], 0.); + + // Standard deviation of cheatgrass variables of one year + EXPECT_EQ(sdCheatgrass[0], 0.); + EXPECT_EQ(sdCheatgrass[1], 0.); + EXPECT_EQ(sdCheatgrass[2], 0.); for(int month = 0; month < MAX_MONTHS; month++) { delete[] meanMonthlyPPT_cm[month]; From 3c4aa2667e7fc0622c0de5afdad0df96dd9c409f Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 14 Jul 2022 00:27:54 -0400 Subject: [PATCH 096/326] Updated test names to mention purely one input tests --- test/test_SW_Weather.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 6f55f79a2..c63c7b390 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -137,7 +137,7 @@ namespace { Reset_SOILWAT2_after_UnitTest(); } - TEST(AverageAcrossYearsTest, FileValues) { + TEST(AverageClimateAcrossYearsTest, FileAndValuesOfOne) { // This test relies on allHist from `SW_WEATHER` being already filled @@ -318,7 +318,7 @@ namespace { } - TEST(CalcSiteClimateTest, FileValues) { + TEST(CalcSiteClimateTest, FileAndValuesOfOne) { // This test relies on allHist from `SW_WEATHER` being already filled From 094172fecabd8ff185af0a0a1f858c43028071de Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 14 Jul 2022 00:33:05 -0400 Subject: [PATCH 097/326] Deleted zero size arrays to silence `test_severe` --- test/test_generic.cc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/test_generic.cc b/test/test_generic.cc index 4678398d3..3c95cd60a 100644 --- a/test/test_generic.cc +++ b/test/test_generic.cc @@ -61,7 +61,6 @@ namespace { TEST(StandardDeviationTest, UnexpectedAndExpectedCases) { double value[1] = {5.}; - double noValue[0] = {}; double values[5] = {5.4, 3.4, 7.6, 5.6, 1.8}; double standardDev = standardDeviation(value, 1); @@ -69,7 +68,7 @@ namespace { // Testing that one value for a standard deviation is `NAN` EXPECT_TRUE(isnan(standardDev)); - standardDev = standardDeviation(noValue, 0); + standardDev = standardDeviation(value, 0); // Testing that no value put into standard deviation is `NAN` EXPECT_DOUBLE_EQ(standardDev, 0.); @@ -82,11 +81,10 @@ namespace { TEST(MeanTest, UnexpectedAndExpectedCases) { - double value[0] = {}; double result; double values[5] = {1.8, 2.2, 10., 13.5, 3.2}; - result = mean(value, 0); + result = mean(values, 0); // Testing that a set of size zero returns `NAN` for a mean EXPECT_TRUE(isnan(result)); From 19e1d4d3f2ce96460682d817f4c840e2b261acf5 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 14 Jul 2022 11:32:45 -0400 Subject: [PATCH 098/326] Removed useless "TODO" in `calcSiteClimate()` --- SW_Weather.c | 1 - 1 file changed, 1 deletion(-) diff --git a/SW_Weather.c b/SW_Weather.c index ef25b9ebe..b943ab2e4 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -257,7 +257,6 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, frostFreeDays_days[yearIndex] = (double)consecNonFrost; meanAnnualTemp_C[yearIndex] /= numDaysYear; - // TODO: Mention in commit: R code incorrectly calculates meanAnnualTemp_C/MAT_C (doesn't account for leap years) } findDriestQtr(meanTempDriestQuarter_C, numYears, meanMonthlyTemp_C, meanMonthlyPPT_cm); From 98f320ef22323243c4500073b8de45d317b3e348 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 15 Jul 2022 14:15:07 -0400 Subject: [PATCH 099/326] Swapped variable `meanMonthlyPPT_cm` for `monthlyPPT_cm` - Variable was incorrectly named describing the variable holding monthly averages - Corrected code to not take the monthly average - Made the same change of name in `test_SW_Weather.cc` --- SW_Weather.c | 28 +++++++++++------------- test/test_SW_Weather.cc | 48 ++++++++++++++++++++--------------------- 2 files changed, 36 insertions(+), 40 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index b943ab2e4..a89873569 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -88,7 +88,7 @@ static char *MyFileName; of row (months) size MAX_MONTHS and columns (years) of size numYears @param[in] minMonthlyTemp_C 2D array containing monthly means of minimum daily air temperature (deg;C) with dimensions of row (months) size MAX_MONTHS and columns (years) of size numYears - @param[in] meanMonthlyPPT_cm 2D array containing monthly precipitation amount (deg;C) with dimensions + @param[in] monthlyPPT_cm 2D array containing monthly precipitation amount (deg;C) with dimensions of row (months) size MAX_MONTHS and columns (years) of size numYears @param[in] numYears Calendar year corresponding to first year of `allHist` @param[in] JulyMinTemp Array of size numYears holding minimum July temperatures [C] for each year @@ -111,7 +111,7 @@ static char *MyFileName; */ void averageClimateAcrossYears(double **meanMonthlyTemp_C, double **maxMonthlyTemp_C, - double **minMonthlyTemp_C, double **meanMonthlyPPT_cm, double numYears, double JulyMinTemp[], + double **minMonthlyTemp_C, double **monthlyPPT_cm, double numYears, double JulyMinTemp[], double frostFreeDays_days[], double ddAbove65F_degday[], double JulyPPT_mm[], double meanTempDriestQuarter_C[], double minTempFebruary_C[], double annualPPT_cm[], double meanAnnualTemp_C[], double *meanMonthlyTempAnn, double *maxMonthlyTempAnn, @@ -124,7 +124,7 @@ void averageClimateAcrossYears(double **meanMonthlyTemp_C, double **maxMonthlyTe meanMonthlyTempAnn[month] = mean(meanMonthlyTemp_C[month], numYears); maxMonthlyTempAnn[month] = mean(maxMonthlyTemp_C[month], numYears); minMonthlyTempAnn[month] = mean(minMonthlyTemp_C[month], numYears); - meanMonthlyPPTAnn[month] = mean(meanMonthlyPPT_cm[month], numYears); + meanMonthlyPPTAnn[month] = mean(monthlyPPT_cm[month], numYears); } *MAP_cm = mean(annualPPT_cm, numYears); @@ -153,7 +153,7 @@ void averageClimateAcrossYears(double **meanMonthlyTemp_C, double **maxMonthlyTe dimensions of row (months) size MAX_MONTHS and columns (years) of size numYears @param[out] minMonthlyTemp_C 2D array containing monthly means min daily air temperature (deg;C) with dimensions of row (months) size MAX_MONTHS and columns (years) of size numYears - @param[out] meanMonthlyPPT_cm_cm 2D array containing monthly amount precipitation (cm) with dimensions + @param[out] monthlyPPT_cm 2D array containing monthly amount precipitation (cm) with dimensions of row (months) size MAX_MONTHS and columns (years) of size numYears @param[out] annualPPT_cm Array of size `numYears` containing annual precipitation amount [cm] @param[out] meanAnnualTemp_C Array of size `numYears` containing annual mean temperatures [C] @@ -167,7 +167,7 @@ void averageClimateAcrossYears(double **meanMonthlyTemp_C, double **maxMonthlyTe void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, double **meanMonthlyTemp_C, double **maxMonthlyTemp_C, double **minMonthlyTemp_C, - double **meanMonthlyPPT_cm, double *annualPPT_cm, double *meanAnnualTemp_C, double *JulyMinTemp, + double **monthlyPPT_cm, double *annualPPT_cm, double *meanAnnualTemp_C, double *JulyMinTemp, double *frostFreeDays_days, double *ddAbove65F_degday, double *JulyPPT_mm, double *meanTempDriestQuarter_C, double *minTempFebruary_C) { @@ -181,7 +181,7 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, memset(meanMonthlyTemp_C[month], 0., sizeof(double) * numYears); memset(maxMonthlyTemp_C[month], 0., sizeof(double) * numYears); memset(minMonthlyTemp_C[month], 0., sizeof(double) * numYears); - memset(meanMonthlyPPT_cm[month], 0., sizeof(double) * numYears); + memset(monthlyPPT_cm[month], 0., sizeof(double) * numYears); } memset(annualPPT_cm, 0., sizeof(double) * numYears); memset(meanAnnualTemp_C, 0., sizeof(double) * numYears); @@ -205,7 +205,7 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, meanMonthlyTemp_C[month][yearIndex] += allHist[yearIndex]->temp_avg[day]; maxMonthlyTemp_C[month][yearIndex] += allHist[yearIndex]->temp_max[day]; minMonthlyTemp_C[month][yearIndex] += allHist[yearIndex]->temp_min[day]; - meanMonthlyPPT_cm[month][yearIndex] += allHist[yearIndex]->ppt[day]; + monthlyPPT_cm[month][yearIndex] += allHist[yearIndex]->ppt[day]; annualPPT_cm[yearIndex] += allHist[yearIndex]->ppt[day]; meanAnnualTemp_C[yearIndex] += allHist[yearIndex]->temp_avg[day]; @@ -237,7 +237,6 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, meanMonthlyTemp_C[month][yearIndex] /= numDaysMonth; maxMonthlyTemp_C[month][yearIndex] /= numDaysMonth; minMonthlyTemp_C[month][yearIndex] /= numDaysMonth; - meanMonthlyPPT_cm[month][yearIndex] /= numDaysMonth; if(month == Feb) minTempFebruary_C[yearIndex] /= numDaysMonth; @@ -258,8 +257,7 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, meanAnnualTemp_C[yearIndex] /= numDaysYear; } - - findDriestQtr(meanTempDriestQuarter_C, numYears, meanMonthlyTemp_C, meanMonthlyPPT_cm); + findDriestQtr(meanTempDriestQuarter_C, numYears, meanMonthlyTemp_C, monthlyPPT_cm); } /** @@ -269,12 +267,12 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, @param[in] startYear Calendar year corresponding to first year of simulation @param[out] meanMonthlyTemp_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] meanMonthlyPPT_cm_cm 2D array containing monthly amount precipitation (cm) with dimensions + @param[out] monthlyPPT_cm 2D array containing monthly amount precipitation (cm) with dimensions of row (months) size MAX_MONTHS and columns (years) of size numYears @param[out] meanTempDriestQuarter_C Array of size numYears holding the average temperature of the driest quarter of the year for every year */ void findDriestQtr(double *meanTempDriestQuarter_C, int numYears, double **meanMonthlyTemp_C, - double **meanMonthlyPPT_cm) { + double **monthlyPPT_cm) { int yearIndex, month, prevMonth, nextMonth; @@ -290,9 +288,9 @@ void findDriestQtr(double *meanTempDriestQuarter_C, int numYears, double **meanM prevMonth = (month == 0) ? 11 : month - 1; nextMonth = (month == 11) ? 0 : month + 1; - currentQtrPPT = (meanMonthlyPPT_cm[prevMonth][yearIndex]) + - (meanMonthlyPPT_cm[month][yearIndex]) + - (meanMonthlyPPT_cm[nextMonth][yearIndex]); + currentQtrPPT = (monthlyPPT_cm[prevMonth][yearIndex]) + + (monthlyPPT_cm[month][yearIndex]) + + (monthlyPPT_cm[nextMonth][yearIndex]); currentQtrTemp = (meanMonthlyTemp_C[prevMonth][yearIndex]) + (meanMonthlyTemp_C[month][yearIndex]) + diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index c63c7b390..bec31d054 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -169,13 +169,13 @@ namespace { double **maxMonthlyTemp_C = new double*[MAX_MONTHS]; for(int month = 0; month < MAX_MONTHS; month++) { - meanMonthlyPPT_cm[month] = new double[31]; + monthlyPPT_cm[month] = new double[31]; meanMonthlyTemp_C[month] = new double[31]; minMonthlyTemp_C[month] = new double[31]; maxMonthlyTemp_C[month] = new double[31]; for(int year = 0; year < 31; year++) { - meanMonthlyPPT_cm[month][year] = 0.; + monthlyPPT_cm[month][year] = 0.; meanMonthlyTemp_C[month][year] = 0.; minMonthlyTemp_C[month][year] = 0.; maxMonthlyTemp_C[month][year] = 0.; @@ -185,11 +185,11 @@ namespace { } // 1980 is start year of the simulation calcSiteClimate(SW_Weather.allHist, 31, 1980, meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, - meanMonthlyPPT_cm, annualPPT_cm, meanAnnualTemp_C, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, + monthlyPPT_cm, annualPPT_cm, meanAnnualTemp_C, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C); averageClimateAcrossYears(meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, - meanMonthlyPPT_cm, 31, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, + monthlyPPT_cm, 31, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C, annualPPT_cm, meanAnnualTemp_C, meanMonthlyTempAnn, maxMonthlyTempAnn, minMonthlyTempAnn, meanMonthlyPPTAnn, sdC4, sdCheatgrass, &MAT_C, &MAP_cm); @@ -216,8 +216,6 @@ namespace { for(int month = 0; month < MAX_MONTHS; month++) { for(int year = 0; year < 31; year++) { - - meanMonthlyPPT_cm[month][year] = 1.; meanMonthlyTemp_C[month][year] = 1.; minMonthlyTemp_C[month][year] = 1.; maxMonthlyTemp_C[month][year] = 1.; @@ -229,7 +227,7 @@ namespace { // Reset values for(int year = 0; year < 31; year++) { for(int month = 0; month < MAX_MONTHS; month++) { - meanMonthlyPPT_cm[month][year] = 0.; + monthlyPPT_cm[month][year] = 0.; meanMonthlyTemp_C[month][year] = 0.; minMonthlyTemp_C[month][year] = 0.; maxMonthlyTemp_C[month][year] = 0.; @@ -311,7 +309,7 @@ namespace { delete[] maxMonthlyTemp_C[month]; } - delete[] meanMonthlyPPT_cm; + delete[] monthlyPPT_cm; delete[] meanMonthlyTemp_C; delete[] minMonthlyTemp_C; delete[] maxMonthlyTemp_C; @@ -331,8 +329,8 @@ namespace { double annualPPT_cm[31]; double meanAnnualTemp_C[31]; - double **meanMonthlyPPT_cm; - meanMonthlyPPT_cm = new double*[MAX_MONTHS]; + double **monthlyPPT_cm; + monthlyPPT_cm = new double*[MAX_MONTHS]; double **meanMonthlyTemp_C; meanMonthlyTemp_C = new double*[MAX_MONTHS]; @@ -344,14 +342,14 @@ namespace { maxMonthlyTemp_C = new double*[MAX_MONTHS]; for(int month = 0; month < MAX_MONTHS; month++) { - meanMonthlyPPT_cm[month] = new double[31]; + monthlyPPT_cm[month] = new double[31]; meanMonthlyTemp_C[month] = new double[31]; minMonthlyTemp_C[month] = new double[31]; maxMonthlyTemp_C[month] = new double[31]; for(int year = 0; year < 31; year++) { - meanMonthlyPPT_cm[month][year] = 0.; + monthlyPPT_cm[month][year] = 0.; meanMonthlyTemp_C[month][year] = 0.; minMonthlyTemp_C[month][year] = 0.; maxMonthlyTemp_C[month][year] = 0.; @@ -365,7 +363,7 @@ namespace { // 1980 is start year of the simulation calcSiteClimate(SW_Weather.allHist, 31, 1980, meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, - meanMonthlyPPT_cm, annualPPT_cm, meanAnnualTemp_C, JulyMinTemp, frostFreeDays_days, + monthlyPPT_cm, annualPPT_cm, meanAnnualTemp_C, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C); // Average of average temperature of January in 1980 @@ -414,7 +412,7 @@ namespace { } calcSiteClimate(SW_Weather.allHist, 2, 1980, meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, - meanMonthlyPPT_cm, annualPPT_cm, meanAnnualTemp_C, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, + monthlyPPT_cm, annualPPT_cm, meanAnnualTemp_C, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C); // Start of leap year tests (startYear = 1980) @@ -458,7 +456,7 @@ namespace { // Start of nonleap year tests (startYear = 1981) calcSiteClimate(SW_Weather.allHist, 2, 1981, meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, - meanMonthlyPPT_cm, annualPPT_cm, meanAnnualTemp_C, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, + monthlyPPT_cm, annualPPT_cm, meanAnnualTemp_C, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C); // Average of average temperature of January in 1981 @@ -498,13 +496,13 @@ namespace { EXPECT_NEAR(minTempFebruary_C[0], 1., tol6); for(int month = 0; month < MAX_MONTHS; month++) { - delete[] meanMonthlyPPT_cm[month]; + delete[] monthlyPPT_cm[month]; delete[] meanMonthlyTemp_C[month]; delete[] minMonthlyTemp_C[month]; delete[] maxMonthlyTemp_C[month]; } - delete[] meanMonthlyPPT_cm; + delete[] monthlyPPT_cm; delete[] meanMonthlyTemp_C; delete[] minMonthlyTemp_C; delete[] maxMonthlyTemp_C; @@ -517,37 +515,37 @@ namespace { 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 - double **meanMonthlyPPT_cm; - meanMonthlyPPT_cm = new double*[MAX_MONTHS]; + double **monthlyPPT_cm; + monthlyPPT_cm = new double*[MAX_MONTHS]; double **meanMonthlyTemp_C = new double*[MAX_MONTHS]; for(int month = 0; month < MAX_MONTHS; month++) { - meanMonthlyPPT_cm[month] = new double[2]; + monthlyPPT_cm[month] = new double[2]; meanMonthlyTemp_C[month] = new double[2]; for(int year = 0; year < 2; year++) { - meanMonthlyPPT_cm[month][year] = monthlyPPT[month]; + monthlyPPT_cm[month][year] = monthlyPPT[month]; meanMonthlyTemp_C[month][year] = monthlyTemp[month]; } } // 1980 is start year of the simulation - findDriestQtr(result, 1, meanMonthlyTemp_C, meanMonthlyPPT_cm); + findDriestQtr(result, 1, meanMonthlyTemp_C, monthlyPPT_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_DOUBLE_EQ(result[0], 1.4333333333333333); - findDriestQtr(result, 2, meanMonthlyTemp_C, meanMonthlyPPT_cm); + findDriestQtr(result, 2, meanMonthlyTemp_C, monthlyPPT_cm); EXPECT_DOUBLE_EQ(result[0], 1.4333333333333333); for(int month = 0; month < MAX_MONTHS; month++) { - delete[] meanMonthlyPPT_cm[month]; + delete[] monthlyPPT_cm[month]; delete[] meanMonthlyTemp_C[month]; } - delete[] meanMonthlyPPT_cm; + delete[] monthlyPPT_cm; delete[] meanMonthlyTemp_C; } From 4ae251f7d53f95addb4ae3498a29edb814cdb7dd Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 15 Jul 2022 14:21:46 -0400 Subject: [PATCH 100/326] New averages in `averageClimateAcrossYears()` - `averageClimateAcrossYears()` now calculates overall average for C4 and cheatgrass variables --- SW_Weather.c | 21 ++++++++++++++++++--- SW_Weather.h | 8 +++++--- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index a89873569..2642d9597 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -106,8 +106,15 @@ static char *MyFileName; @param[out] sdC4 Array of size three holding the standard deviations of minimum July temperature (0), frost free days (1), number of days above 65F (2) @param[out] sdCheatgrass Array of size 3 holding the standard deviations of July precipitation (0), mean temperature of dry quarter (1), mean minimum temperature of February (2) - @param[out] MAT_C Value containing the sum of daily temperatures - @param[out] MAP_cm Value containing the sum of daily precipitation + @param[out] MAT_C Value containing the average of yearly temperatures + @param[out] MAP_cm Value containing the average of yearly precipitation + @param[out] JulyPPTAnn_mm Value containing average of July precipitation through all simulation years (mm) + @param[out] meanTempDriestQuarterAnn_C Value containing average of mean temperatures in the + driest quarters of years through all simulation years + @param[out] minTempFebruaryAnn_C Value containing average through all simulation years of sum of minimum temperatures in February + @param[out] ddAbove65F_degdayAnn Value containing average of total degrees above 65F (18.33C) throughout the year across all simulation years + @param[out] frostFreeAnn Value containing average of most consectutive days in a year without frost throughout all simulation years + @param[out] JulyMinTempAnn Value containing the average of lowest temperature in July in all years */ void averageClimateAcrossYears(double **meanMonthlyTemp_C, double **maxMonthlyTemp_C, @@ -116,7 +123,9 @@ void averageClimateAcrossYears(double **meanMonthlyTemp_C, double **maxMonthlyTe double meanTempDriestQuarter_C[], double minTempFebruary_C[], double annualPPT_cm[], double meanAnnualTemp_C[], double *meanMonthlyTempAnn, double *maxMonthlyTempAnn, double *minMonthlyTempAnn, double *meanMonthlyPPTAnn, double *sdC4, double *sdCheatgrass, - double *MAT_C, double *MAP_cm) { + double *MAT_C, double *MAP_cm, double *JulyPPTAnn_mm, double *meanTempDriestQuarterAnn_C, + double *minTempFebruaryAnn_C, double *ddAbove65F_degdayAnn, double *frostFreeAnn, + double *JulyMinTempAnn) { int month; @@ -129,6 +138,12 @@ void averageClimateAcrossYears(double **meanMonthlyTemp_C, double **maxMonthlyTe *MAP_cm = mean(annualPPT_cm, numYears); *MAT_C = mean(meanAnnualTemp_C, numYears); + *JulyPPTAnn_mm = mean(JulyPPT_mm, numYears); + *meanTempDriestQuarterAnn_C = mean(meanTempDriestQuarter_C, numYears); + *minTempFebruaryAnn_C = mean(minTempFebruary_C, numYears); + *ddAbove65F_degdayAnn = mean(ddAbove65F_degday, numYears); + *frostFreeAnn = mean(frostFreeDays_days, numYears); + *JulyMinTempAnn = mean(JulyMinTemp, numYears); // Calculate and set standard deviation of C4 variables (frostFreeDays is a running sd) sdC4[0] = standardDeviation(JulyMinTemp, numYears); diff --git a/SW_Weather.h b/SW_Weather.h index 7e66e8d0b..98fcfec60 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -105,12 +105,14 @@ void SW_WTH_setup(void); void SW_WTH_read(void); Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather); void averageClimateAcrossYears(double **meanMonthlyTemp_C, double **maxMonthlyTemp_C, - double **minMonthlyTemp_C, double **meanMonthlyPPT_cm, double numYears, double JulyMinTemp[], - double frostFreeDays_days[], double ddAbove65F_degday[], double JulyPPT_mm[], + double **minMonthlyTemp_C, double **monthlyPPT_cm, double numYears, double JulyMinTemp[], + double frostFreeDays_days[], double ddAbove65FDegday[], double JulyPPT_mm[], double meanTempDriestQuarter_C[], double minTempFebruary_C[], double annualPPT_cm[], double meanAnnualTemp_C[], double *meanMonthlyTempAnn, double *maxMonthlyTempAnn, double *minMonthlyTempAnn, double *meanMonthlyPPTAnn, double *sdC4, double *sdCheatgrass, - double *MAT_C, double *MAP_cm); + double *MAT_C, double *MAP_cm, double *JulyPPTAnn_mm, double *meanTempDriestQuarterAnn_C, + double *minTempFebruaryAnn_C, double *ddAbove65FDegdayAnn, double *frostFreeAnn, + double *JulyMinTempAnn); void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, double **meanMonthlyTemp_C, double **maxMonthlyTemp_C, double **minMonthlyTemp_C, double **meanMonthlyPPT_cm, double *annualPPT_cm, double *meanAnnualTemp_C, double *JulyMinTemp, From 5a36eaf497122ea38a4ad77255ffa81cb6d1bd15 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 15 Jul 2022 14:24:09 -0400 Subject: [PATCH 101/326] New unit tests for `averageClimateAcrossYears()` - Added new unit tests to cover new variables that are averaged - Swapped `EXPECT_EQ()` with `EXPECT_DOUBLE_EQ()` when double is tested --- test/test_SW_Weather.cc | 133 +++++++++++++++++++++++++--------------- 1 file changed, 82 insertions(+), 51 deletions(-) diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index bec31d054..2e2e5b283 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -158,9 +158,15 @@ namespace { double MAP_cm; double MAT_C; + double JulyPPTAnn_mm; + double meanTempDriestQuarterAnn_C; + double minTempFebruaryAnn_C; + double ddAbove65F_degdayAnn; + double frostFreeAnn; + double JulyMinTempAnn; - double **meanMonthlyPPT_cm; - meanMonthlyPPT_cm = new double*[MAX_MONTHS]; + double **monthlyPPT_cm; + monthlyPPT_cm = new double*[MAX_MONTHS]; double **meanMonthlyTemp_C = new double*[MAX_MONTHS]; @@ -192,17 +198,25 @@ namespace { monthlyPPT_cm, 31, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C, annualPPT_cm, meanAnnualTemp_C, meanMonthlyTempAnn, maxMonthlyTempAnn, minMonthlyTempAnn, - meanMonthlyPPTAnn, sdC4, sdCheatgrass, &MAT_C, &MAP_cm); + meanMonthlyPPTAnn, sdC4, sdCheatgrass, &MAT_C, &MAP_cm, &JulyPPTAnn_mm, + &meanTempDriestQuarterAnn_C, &minTempFebruaryAnn_C, &ddAbove65F_degdayAnn, + &frostFreeAnn, &JulyMinTempAnn); EXPECT_NEAR(meanMonthlyTempAnn[0], -9.325551, tol6); EXPECT_NEAR(maxMonthlyTempAnn[0], -2.714381, tol6); EXPECT_NEAR(minMonthlyTempAnn[0], -15.936722, tol6); - EXPECT_NEAR(meanMonthlyPPTAnn[0], 0.221530, tol6); + EXPECT_NEAR(meanMonthlyPPTAnn[0], 6.867419, tol6); EXPECT_NEAR(meanAnnualTemp_C[0], 4.524863, tol6); EXPECT_NEAR(annualPPT_cm[0], 59.2700004, tol6); EXPECT_NEAR(MAP_cm, 62.817419, tol6); EXPECT_NEAR(MAT_C, 4.154009, tol6); + EXPECT_NEAR(JulyPPTAnn_mm, 35.729032, tol6); + EXPECT_NEAR(meanTempDriestQuarterAnn_C, 11.524859, tol6); + EXPECT_NEAR(minTempFebruaryAnn_C, -13.904599, tol6); + EXPECT_NEAR(ddAbove65F_degdayAnn, 21.168032, tol6); + EXPECT_NEAR(frostFreeAnn, 90.612903, tol6); + EXPECT_NEAR(JulyMinTempAnn, 3.078387, tol6); // Standard deviation of C4 variables EXPECT_NEAR(sdC4[0], 1.785535, tol6); @@ -216,6 +230,7 @@ namespace { for(int month = 0; month < MAX_MONTHS; month++) { for(int year = 0; year < 31; year++) { + monthlyPPT_cm[month][year] = 1.; meanMonthlyTemp_C[month][year] = 1.; minMonthlyTemp_C[month][year] = 1.; maxMonthlyTemp_C[month][year] = 1.; @@ -237,21 +252,29 @@ namespace { } // Tests for one year of simulation calcSiteClimate(SW_Weather.allHist, 1, 1980, meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, - meanMonthlyPPT_cm, annualPPT_cm, meanAnnualTemp_C, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, + monthlyPPT_cm, annualPPT_cm, meanAnnualTemp_C, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C); averageClimateAcrossYears(meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, - meanMonthlyPPT_cm, 1, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, + monthlyPPT_cm, 1, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C, annualPPT_cm, meanAnnualTemp_C, meanMonthlyTempAnn, maxMonthlyTempAnn, minMonthlyTempAnn, - meanMonthlyPPTAnn, sdC4, sdCheatgrass, &MAT_C, &MAP_cm); + meanMonthlyPPTAnn, sdC4, sdCheatgrass, &MAT_C, &MAP_cm, &JulyPPTAnn_mm, + &meanTempDriestQuarterAnn_C, &minTempFebruaryAnn_C, &ddAbove65F_degdayAnn, + &frostFreeAnn, &JulyMinTempAnn); EXPECT_NEAR(meanMonthlyTempAnn[0], -8.432581, tol6); EXPECT_NEAR(maxMonthlyTempAnn[0], -2.562581, tol6); EXPECT_NEAR(minMonthlyTempAnn[0], -14.302581, tol6); - EXPECT_NEAR(meanMonthlyPPTAnn[0], 0.488387, tol6); + EXPECT_NEAR(meanMonthlyPPTAnn[0], 15.1400001, tol6); EXPECT_NEAR(MAP_cm, 59.27, tol1); EXPECT_NEAR(MAT_C, 4.524863, tol1); + EXPECT_NEAR(JulyPPTAnn_mm, 18.299999, tol6); + EXPECT_NEAR(meanTempDriestQuarterAnn_C, 0.936387, tol6); + EXPECT_NEAR(minTempFebruaryAnn_C, -12.822068, tol6); + EXPECT_NEAR(ddAbove65F_degdayAnn, 13.546000, tol6); + EXPECT_NEAR(frostFreeAnn, 92, tol6); + EXPECT_NEAR(JulyMinTempAnn, 2.809999, tol6); // Standard deviation of C4 variables of one year EXPECT_TRUE(isnan(sdC4[0])); @@ -273,37 +296,45 @@ namespace { } // Start of tests with all `allHist` inputs of 1 - calcSiteClimate(SW_Weather.allHist, 31, 1980, meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, - meanMonthlyPPT_cm, annualPPT_cm, meanAnnualTemp_C, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, + calcSiteClimate(SW_Weather.allHist, 2, 1980, meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, + monthlyPPT_cm, annualPPT_cm, meanAnnualTemp_C, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C); averageClimateAcrossYears(meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, - meanMonthlyPPT_cm, 31, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, + monthlyPPT_cm, 2, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C, annualPPT_cm, meanAnnualTemp_C, meanMonthlyTempAnn, maxMonthlyTempAnn, minMonthlyTempAnn, - meanMonthlyPPTAnn, sdC4, sdCheatgrass, &MAT_C, &MAP_cm); - - EXPECT_EQ(meanMonthlyTempAnn[0], 1.); - EXPECT_EQ(maxMonthlyTempAnn[0], 1.); - EXPECT_EQ(minMonthlyTempAnn[0], 1.); - EXPECT_EQ(meanMonthlyPPTAnn[0], 1.); - // EXPECT_NEAR because of tests inaccuracy with the actual value - // Tolerance of 1 because the inaccurate value the test has is closer to 365 - EXPECT_NEAR(MAP_cm, 366., 1); - EXPECT_EQ(MAT_C, 1.); + meanMonthlyPPTAnn, sdC4, sdCheatgrass, &MAT_C, &MAP_cm, &JulyPPTAnn_mm, + &meanTempDriestQuarterAnn_C, &minTempFebruaryAnn_C, &ddAbove65F_degdayAnn, + &frostFreeAnn, &JulyMinTempAnn); + + EXPECT_DOUBLE_EQ(meanMonthlyTempAnn[0], 1.); + EXPECT_DOUBLE_EQ(maxMonthlyTempAnn[0], 1.); + EXPECT_DOUBLE_EQ(minMonthlyTempAnn[0], 1.); + EXPECT_DOUBLE_EQ(meanMonthlyPPTAnn[0], 31.); + EXPECT_DOUBLE_EQ(JulyPPTAnn_mm, 310.); + EXPECT_DOUBLE_EQ(meanTempDriestQuarterAnn_C, 1.); + EXPECT_DOUBLE_EQ(minTempFebruaryAnn_C, 1.); + EXPECT_DOUBLE_EQ(ddAbove65F_degdayAnn, 0.); + EXPECT_DOUBLE_EQ(frostFreeAnn, 365.5); + EXPECT_DOUBLE_EQ(JulyMinTempAnn, 1.); + // MAP_cm is expected to be 365.5 because we are running a leap year + // and nonleap year where the number of days average to 365.5 + EXPECT_DOUBLE_EQ(MAP_cm, 365.5); + EXPECT_DOUBLE_EQ(MAT_C, 1.); // Standard deviation of C4 variables of one year - EXPECT_EQ(sdC4[0], 0.); - EXPECT_EQ(sdC4[1], 0.); - EXPECT_EQ(sdC4[2], 0.); + EXPECT_DOUBLE_EQ(sdC4[0], 0.); + EXPECT_NEAR(sdC4[1], .7071067, tol6); + EXPECT_DOUBLE_EQ(sdC4[2], 0.); // Standard deviation of cheatgrass variables of one year - EXPECT_EQ(sdCheatgrass[0], 0.); - EXPECT_EQ(sdCheatgrass[1], 0.); - EXPECT_EQ(sdCheatgrass[2], 0.); + EXPECT_DOUBLE_EQ(sdCheatgrass[0], 0.); + EXPECT_DOUBLE_EQ(sdCheatgrass[1], 0.); + EXPECT_DOUBLE_EQ(sdCheatgrass[2], 0.); for(int month = 0; month < MAX_MONTHS; month++) { - delete[] meanMonthlyPPT_cm[month]; + delete[] monthlyPPT_cm[month]; delete[] meanMonthlyTemp_C[month]; delete[] minMonthlyTemp_C[month]; delete[] maxMonthlyTemp_C[month]; @@ -376,7 +407,7 @@ namespace { EXPECT_NEAR(minMonthlyTemp_C[0][0], -14.302581, tol6); // Average January precipitation in 1980 - EXPECT_NEAR(meanMonthlyPPT_cm[0][0], 0.488387, tol6); + EXPECT_NEAR(monthlyPPT_cm[0][0], 15.14, tol6); // Average temperature of three driest month of first year EXPECT_NEAR(meanTempDriestQuarter_C[0], .936387, tol6); @@ -418,37 +449,37 @@ namespace { // Start of leap year tests (startYear = 1980) // Average of average temperature of January in 1980 - EXPECT_EQ(meanMonthlyTemp_C[0][0], 1.); + EXPECT_DOUBLE_EQ(meanMonthlyTemp_C[0][0], 1.); // Average of max temperature in Januaray 1980 - EXPECT_EQ(maxMonthlyTemp_C[0][0], 1.); + EXPECT_DOUBLE_EQ(maxMonthlyTemp_C[0][0], 1.); // Average of min temperature in Januaray 1980 - EXPECT_EQ(minMonthlyTemp_C[0][0], 1.); + EXPECT_DOUBLE_EQ(minMonthlyTemp_C[0][0], 1.); // Average January precipitation in 1980 - EXPECT_EQ(meanMonthlyPPT_cm[0][0], 1.); + EXPECT_DOUBLE_EQ(monthlyPPT_cm[0][0], 31.); // Average temperature of three driest month of first year - EXPECT_EQ(meanTempDriestQuarter_C[0], 1.); + EXPECT_DOUBLE_EQ(meanTempDriestQuarter_C[0], 1.); // Average precipiation of first year of simulation - EXPECT_EQ(annualPPT_cm[0], 366.); + EXPECT_DOUBLE_EQ(annualPPT_cm[0], 366.); // Average temperature of first year of simulation - EXPECT_EQ(meanAnnualTemp_C[0], 1.); + EXPECT_DOUBLE_EQ(meanAnnualTemp_C[0], 1.); // First year's July minimum temperature - EXPECT_EQ(JulyMinTemp[0], 1.); + EXPECT_DOUBLE_EQ(JulyMinTemp[0], 1.); // First year's number of most consecutive frost free days - EXPECT_EQ(frostFreeDays_days[0], 0); + EXPECT_DOUBLE_EQ(frostFreeDays_days[0], 366); // Sum of all temperature above 65F (18.333C) in first year - EXPECT_EQ(ddAbove65F_degday[0], 0); + EXPECT_DOUBLE_EQ(ddAbove65F_degday[0], 0); // Total precipitation in July of first year - EXPECT_EQ(JulyPPT_mm[0], 310.); + EXPECT_DOUBLE_EQ(JulyPPT_mm[0], 310.); // Smallest temperature in all February first year EXPECT_NEAR(minTempFebruary_C[0], 1., tol6); @@ -460,37 +491,37 @@ namespace { JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C); // Average of average temperature of January in 1981 - EXPECT_EQ(meanMonthlyTemp_C[0][0], 1.); + EXPECT_DOUBLE_EQ(meanMonthlyTemp_C[0][0], 1.); // Average of max temperature in Januaray 1981 - EXPECT_EQ(maxMonthlyTemp_C[0][0], 1.); + EXPECT_DOUBLE_EQ(maxMonthlyTemp_C[0][0], 1.); // Average of min temperature in Januaray 1981 - EXPECT_EQ(minMonthlyTemp_C[0][0], 1.); + EXPECT_DOUBLE_EQ(minMonthlyTemp_C[0][0], 1.); // Average January precipitation in 1980 - EXPECT_EQ(meanMonthlyPPT_cm[0][0], 1.); + EXPECT_DOUBLE_EQ(monthlyPPT_cm[0][0], 31.); // Average temperature of three driest month of first year - EXPECT_EQ(meanTempDriestQuarter_C[0], 1.); + EXPECT_DOUBLE_EQ(meanTempDriestQuarter_C[0], 1.); // Average precipiation of first year of simulation - EXPECT_EQ(annualPPT_cm[0], 365.); + EXPECT_DOUBLE_EQ(annualPPT_cm[0], 365.); // Average temperature of first year of simulation - EXPECT_EQ(meanAnnualTemp_C[0], 1.); + EXPECT_DOUBLE_EQ(meanAnnualTemp_C[0], 1.); // First year's July minimum temperature - EXPECT_EQ(JulyMinTemp[0], 1.); + EXPECT_DOUBLE_EQ(JulyMinTemp[0], 1.); // First year's number of most consecutive frost free days - EXPECT_EQ(frostFreeDays_days[0], 0); + EXPECT_DOUBLE_EQ(frostFreeDays_days[0], 365); // Sum of all temperature above 65F (18.333C) in first year - EXPECT_EQ(ddAbove65F_degday[0], 0); + EXPECT_DOUBLE_EQ(ddAbove65F_degday[0], 0); // Total precipitation in July of first year - EXPECT_EQ(JulyPPT_mm[0], 310.); + EXPECT_DOUBLE_EQ(JulyPPT_mm[0], 310.); // Smallest temperature in all February first year EXPECT_NEAR(minTempFebruary_C[0], 1., tol6); From 667bee118ff5ce710fa1ac8b4960c3c8e352dad9 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 15 Jul 2022 14:26:45 -0400 Subject: [PATCH 102/326] Fixed bug in number of consecutive frost free days - Switched type of `frostFreeDays_days` to double from integer - Covered case if all days are above 32F --- SW_Weather.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 2642d9597..870d8c51b 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -188,9 +188,10 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, int month, yearIndex, year, day, numDaysYear, numDaysMonth = Time_days_in_month(0), - currMonDay, consecNonFrost, currentNonFrost; + currMonDay; - double currentTempMin, currentTempMean, totalAbove65, currentJulyMin, JulyPPT; + double currentTempMin, currentTempMean, totalAbove65, currentJulyMin, JulyPPT, + consecNonFrost, currentNonFrost; for(month = 0; month < MAX_MONTHS; month++) { memset(meanMonthlyTemp_C[month], 0., sizeof(double) * numYears); @@ -235,12 +236,12 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, } if(currentTempMin > 0.0) { - currentNonFrost++; + currentNonFrost += 1.; } else if(currentNonFrost > consecNonFrost){ consecNonFrost = currentNonFrost; - currentNonFrost = 0; + currentNonFrost = 0.; } else { - currentNonFrost = 0; + currentNonFrost = 0.; } if(month == Feb) { @@ -268,7 +269,10 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, JulyMinTemp[yearIndex] = currentJulyMin; JulyPPT_mm[yearIndex] = JulyPPT; ddAbove65F_degday[yearIndex] = totalAbove65; - frostFreeDays_days[yearIndex] = (double)consecNonFrost; + + // The reason behind checking if consecNonFrost is greater than zero, + // is that there is a chance all days in the year are above 32F + frostFreeDays_days[yearIndex] = (consecNonFrost > 0) ? consecNonFrost : currentNonFrost; meanAnnualTemp_C[yearIndex] /= numDaysYear; } From 68bf96d77465cbcec5d8367cb026fc12f5bc5c55 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Wed, 20 Jul 2022 10:20:06 -0400 Subject: [PATCH 103/326] Deleted "ifdef" conditional in `readAllWeather()` --- SW_Weather.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 4078eb3c6..bfbfb98ac 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -139,11 +139,7 @@ void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, unsigned int n_yea currentMonDays = 0; if(!SW_Weather.use_weathergenerator_only) { - #ifdef RSOILWAT - weth_found = onSet_WTH_DATA_YEAR(year, allHist[yearIndex]); - #else weth_found = _read_weather_hist(year, allHist[yearIndex]); - #endif } for(day = 0; day < yearDays; day++) { if(currentMonDays == monthDays) { From 184e6572c4ee2bc988a518f0597f50079a614b1c Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Wed, 20 Jul 2022 10:26:34 -0400 Subject: [PATCH 104/326] Cleaned up `readAllWeather()` ternaries and names - Removed two ternary operations - Removed unused variables and made first month self-documenting (Jan) --- SW_Weather.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index bfbfb98ac..6aa0adc14 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -124,8 +124,8 @@ static void _update_yesterday(void) { void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, unsigned int n_years) { - int day, yearDays, monthDays, month, currentMonDays, year; - unsigned int yearIndex; + int monthDays, month, currentMonDays, year; + unsigned int yearIndex, numDaysYear, day; double yesterdayPPT = 0., yesterdayMin = 0., yesterdayMax = 0.; @@ -133,18 +133,19 @@ void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, unsigned int n_yea for(yearIndex = 0; yearIndex < n_years; yearIndex++) { year = yearIndex + startYear; - yearDays = isleapyear(year) ? 366 : 365; - monthDays = 31; - month = 0; + Time_new_year(year); + numDaysYear = Time_get_lastdoy_y(year); + month = Jan; + monthDays = Time_days_in_month(month); currentMonDays = 0; if(!SW_Weather.use_weathergenerator_only) { weth_found = _read_weather_hist(year, allHist[yearIndex]); } - for(day = 0; day < yearDays; day++) { + for(day = 0; day < numDaysYear; day++) { if(currentMonDays == monthDays) { month++; - monthDays = (month == Feb) ? (isleapyear(year) ? 29 : 28) : Time_days_in_month(month); + monthDays = Time_days_in_month(month % 12); currentMonDays = 0; } currentMonDays++; From fecc6fc7645047f617a528707e03ad54db3f544c Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 21 Jul 2022 10:43:39 -0400 Subject: [PATCH 105/326] Created two structs to hold climate-related variables - Created two structs of types: SW_CLIMATE_OUTPUT and SW_CLIMATE_AVERAGES - SW_CLIMATE_OUTPUT holds variables that are output of `calcSiteClimate()` * and is input to `averageClimateAcrossYears()` - SW_CLIMATE_AVERAGES hold variables that `averageClimateAcrossYears()` output --- SW_Weather.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/SW_Weather.h b/SW_Weather.h index 98fcfec60..8e524f2ca 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -57,6 +57,19 @@ typedef struct { snowRunoff, surfaceRunoff, surfaceRunon, soil_inf, et, aet, pet, surfaceAvg, surfaceMax, surfaceMin; } SW_WEATHER_OUTPUTS; +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_OUTPUT; + +typedef struct { + RealD *annualPPT_cm, *meanAnnualTemp_C, *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 From ea2a922f771200ede9f34b863fea56542506de0d Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 21 Jul 2022 10:55:21 -0400 Subject: [PATCH 106/326] Modified headers and documentation to use new stucts - Replaced output variables with SW_CLIMATE_OUTPUT struct in `calcSiteClimate()` - Replaced input/output variables with SW_CLIMATE_OUTPUT and SW_CLIMATE_AVERAGE in `averageClimateAcrossYears()` - Accommodated code to properly access the variables inside struct --- SW_Weather.c | 159 +++++++++++++++++---------------------------------- SW_Weather.h | 16 +----- 2 files changed, 56 insertions(+), 119 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 870d8c51b..290208584 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -82,78 +82,42 @@ static char *MyFileName; /** @brief Takes averages through the number of years of the calculated values from calc_SiteClimate - @param[in] meanMonthlyTemp_C 2D array containing monthly means of average daily air temperature (deg;C) with dimensions - of row (months) size MAX_MONTHS and columns (years) of size numYears - @param[in] maxMonthlyTemp_C 2D array containing monthly means of maximum daily air temperature (deg;C) with dimensions - of row (months) size MAX_MONTHS and columns (years) of size numYears - @param[in] minMonthlyTemp_C 2D array containing monthly means of minimum daily air temperature (deg;C) with dimensions - of row (months) size MAX_MONTHS and columns (years) of size numYears - @param[in] monthlyPPT_cm 2D array containing monthly precipitation amount (deg;C) with dimensions - of row (months) size MAX_MONTHS and columns (years) of size numYears + @param[in] climateOutput Structure of type SW_CLIMATE_OUTPUT that holds all output from `calcSiteClimate()` @param[in] numYears Calendar year corresponding to first year of `allHist` - @param[in] JulyMinTemp Array of size numYears holding minimum July temperatures [C] for each year - @param[in] frostFreeDays_days Array of size numYears holding the maximum consecutive days in a year without frost for every year - @param[in] ddAbove65F_degday Array of size numYears holding the amount of degree days [C x day] above 65 F - @param[in] JulyPPT_mm Array of size numYears holding July precipitation amount (mm) - @param[in] meanTempDriestQuarter_C Array of size numYears holding the average temperature of the driest quarter of the year for every year - @param[in] minTempFebruary_C Array of size numYears holding the mean minimum temperature in february for every year - @param[out] annualPPT_cm Array of size `numYears` containing annual precipitation amount [cm] - @param[out] meanAnnualTemp_C Array of size `numYears` containing annual mean temperatures [C] - @param[out] meanMonthlyTempAnn Array of size `numYears` containing sum of monthly mean temperatures - @param[out] maxMonthlyTempAnn Array of size `numYears` containing sum of monthly maximum temperatures - @param[out] minMonthlyTempAnn Array of size `numYears` containing sum of monthly minimum temperatures - @param[out] meanMonthlyPPTAnn Array of size `numYears` containing sum of monthly mean precipitation - @param[out] sdC4 Array of size three holding the standard deviations of minimum July temperature (0), frost free days (1), number of days above 65F (2) - @param[out] sdCheatgrass Array of size 3 holding the standard deviations of July precipitation (0), mean - temperature of dry quarter (1), mean minimum temperature of February (2) - @param[out] MAT_C Value containing the average of yearly temperatures - @param[out] MAP_cm Value containing the average of yearly precipitation - @param[out] JulyPPTAnn_mm Value containing average of July precipitation through all simulation years (mm) - @param[out] meanTempDriestQuarterAnn_C Value containing average of mean temperatures in the - driest quarters of years through all simulation years - @param[out] minTempFebruaryAnn_C Value containing average through all simulation years of sum of minimum temperatures in February - @param[out] ddAbove65F_degdayAnn Value containing average of total degrees above 65F (18.33C) throughout the year across all simulation years - @param[out] frostFreeAnn Value containing average of most consectutive days in a year without frost throughout all simulation years - @param[out] JulyMinTempAnn Value containing the average of lowest temperature in July in all years + @param[out] climateAverages Structure of type SW_CLIMATE_AVERAGES that holds averages and + standard deviations output by `averageClimateAcrossYears()` */ -void averageClimateAcrossYears(double **meanMonthlyTemp_C, double **maxMonthlyTemp_C, - double **minMonthlyTemp_C, double **monthlyPPT_cm, double numYears, double JulyMinTemp[], - double frostFreeDays_days[], double ddAbove65F_degday[], double JulyPPT_mm[], - double meanTempDriestQuarter_C[], double minTempFebruary_C[], double annualPPT_cm[], - double meanAnnualTemp_C[], double *meanMonthlyTempAnn, double *maxMonthlyTempAnn, - double *minMonthlyTempAnn, double *meanMonthlyPPTAnn, double *sdC4, double *sdCheatgrass, - double *MAT_C, double *MAP_cm, double *JulyPPTAnn_mm, double *meanTempDriestQuarterAnn_C, - double *minTempFebruaryAnn_C, double *ddAbove65F_degdayAnn, double *frostFreeAnn, - double *JulyMinTempAnn) { +void averageClimateAcrossYears(SW_CLIMATE_OUTPUT climateOutput, int numYears, + SW_CLIMATE_AVERAGES climateAverages) { int month; for(month = 0; month < MAX_MONTHS; month++) { - meanMonthlyTempAnn[month] = mean(meanMonthlyTemp_C[month], numYears); - maxMonthlyTempAnn[month] = mean(maxMonthlyTemp_C[month], numYears); - minMonthlyTempAnn[month] = mean(minMonthlyTemp_C[month], numYears); - meanMonthlyPPTAnn[month] = mean(monthlyPPT_cm[month], numYears); + climateAverages.meanMonthlyTempAnn[month] = mean(climateOutput.meanMonthlyTemp_C[month], numYears); + climateAverages.maxMonthlyTempAnn[month] = mean(climateOutput.maxMonthlyTemp_C[month], numYears); + climateAverages.minMonthlyTempAnn[month] = mean(climateOutput.minMonthlyTemp_C[month], numYears); + climateAverages.meanMonthlyPPTAnn[month] = mean(climateOutput.monthlyPPT_cm[month], numYears); } - *MAP_cm = mean(annualPPT_cm, numYears); - *MAT_C = mean(meanAnnualTemp_C, numYears); - *JulyPPTAnn_mm = mean(JulyPPT_mm, numYears); - *meanTempDriestQuarterAnn_C = mean(meanTempDriestQuarter_C, numYears); - *minTempFebruaryAnn_C = mean(minTempFebruary_C, numYears); - *ddAbove65F_degdayAnn = mean(ddAbove65F_degday, numYears); - *frostFreeAnn = mean(frostFreeDays_days, numYears); - *JulyMinTempAnn = mean(JulyMinTemp, numYears); + *climateAverages.MAP_cm = mean(climateOutput.annualPPT_cm, numYears); + *climateAverages.MAT_C = mean(climateOutput.meanAnnualTemp_C, numYears); + *climateAverages.JulyPPTAnn_mm = mean(climateOutput.JulyPPT_mm, numYears); + *climateAverages.meanTempDriestQuarterAnn_C = mean(climateOutput.meanTempDriestQuarter_C, numYears); + *climateAverages.minTempFebruaryAnn_C = mean(climateOutput.minTempFebruary_C, numYears); + *climateAverages.ddAbove65F_degdayAnn = mean(climateOutput.ddAbove65F_degday, numYears); + *climateAverages.frostFreeAnn = mean(climateOutput.frostFreeDays_days, numYears); + *climateAverages.JulyMinTempAnn = mean(climateOutput.JulyMinTemp, numYears); // Calculate and set standard deviation of C4 variables (frostFreeDays is a running sd) - sdC4[0] = standardDeviation(JulyMinTemp, numYears); - sdC4[1] = standardDeviation(frostFreeDays_days, numYears); - sdC4[2] = standardDeviation(ddAbove65F_degday, numYears); + climateAverages.sdC4[0] = standardDeviation(climateOutput.JulyMinTemp, numYears); + climateAverages.sdC4[1] = standardDeviation(climateOutput.frostFreeDays_days, numYears); + climateAverages.sdC4[2] = standardDeviation(climateOutput.ddAbove65F_degday, numYears); // Calculate and set the standard deviation of cheatgrass variables - sdCheatgrass[0] = standardDeviation(JulyPPT_mm, numYears); - sdCheatgrass[1] = standardDeviation(meanTempDriestQuarter_C, numYears); - sdCheatgrass[2] = standardDeviation(minTempFebruary_C, numYears); + climateAverages.sdCheatgrass[0] = standardDeviation(climateOutput.JulyPPT_mm, numYears); + climateAverages.sdCheatgrass[1] = standardDeviation(climateOutput.meanTempDriestQuarter_C, numYears); + climateAverages.sdCheatgrass[2] = standardDeviation(climateOutput.minTempFebruary_C, numYears); } /** @@ -162,30 +126,12 @@ void averageClimateAcrossYears(double **meanMonthlyTemp_C, double **maxMonthlyTe @param[in] allHist Array containing all historical data of a site @param[in] numYears Number of years represented by `allHist` @param[in] startYear Calendar year corresponding to first year of `allHist` - @param[out] meanMonthlyTemp_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] maxMonthlyTemp_C 2D array containing monthly means max daily air temperature (deg;C) with - dimensions of row (months) size MAX_MONTHS and columns (years) of size numYears - @param[out] minMonthlyTemp_C 2D array containing monthly means min daily air temperature (deg;C) with - dimensions of row (months) size MAX_MONTHS and columns (years) of size numYears - @param[out] monthlyPPT_cm 2D array containing monthly amount precipitation (cm) with dimensions - of row (months) size MAX_MONTHS and columns (years) of size numYears - @param[out] annualPPT_cm Array of size `numYears` containing annual precipitation amount [cm] - @param[out] meanAnnualTemp_C Array of size `numYears` containing annual mean temperatures [C] - @param[out] JulyMinTemp Array of size numYears holding minimum July temperatures [C] for each year - @param[out] frostFreeDays_days Array of size numYears holding the maximum consecutive days in a year without frost for every year - @param[out] ddAbove65F_degday Array of size numYears holding the amount of degree days [C x day] above 65 F - @param[out] JulyPPT_mm Array of size numYears holding July precipitation amount (mm) - @param[out] meanTempDriestQuarter_C Array of size numYears holding the average temperature of the driest quarter of the year for every year - @param[out] minTempFebruary_C Array of size numYears holding the mean minimum temperature in february for every year + @param[out] climateOutput Structure of type SW_CLIMATE_AVERAGES that holds averages and + standard deviations output by `averageClimateAcrossYears()` */ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, - double **meanMonthlyTemp_C, double **maxMonthlyTemp_C, double **minMonthlyTemp_C, - double **monthlyPPT_cm, double *annualPPT_cm, double *meanAnnualTemp_C, double *JulyMinTemp, - double *frostFreeDays_days, double *ddAbove65F_degday, double *JulyPPT_mm, - double *meanTempDriestQuarter_C, double *minTempFebruary_C) { - + SW_CLIMATE_OUTPUT climateOutput) { int month, yearIndex, year, day, numDaysYear, numDaysMonth = Time_days_in_month(0), currMonDay; @@ -194,15 +140,15 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, consecNonFrost, currentNonFrost; for(month = 0; month < MAX_MONTHS; month++) { - memset(meanMonthlyTemp_C[month], 0., sizeof(double) * numYears); - memset(maxMonthlyTemp_C[month], 0., sizeof(double) * numYears); - memset(minMonthlyTemp_C[month], 0., sizeof(double) * numYears); - memset(monthlyPPT_cm[month], 0., sizeof(double) * numYears); + memset(climateOutput.meanMonthlyTemp_C[month], 0., sizeof(double) * numYears); + memset(climateOutput.maxMonthlyTemp_C[month], 0., sizeof(double) * numYears); + memset(climateOutput.minMonthlyTemp_C[month], 0., sizeof(double) * numYears); + memset(climateOutput.monthlyPPT_cm[month], 0., sizeof(double) * numYears); } - memset(annualPPT_cm, 0., sizeof(double) * numYears); - memset(meanAnnualTemp_C, 0., sizeof(double) * numYears); - memset(minTempFebruary_C, 0., sizeof(double) * numYears); - memset(JulyPPT_mm, 0., sizeof(double) * numYears); + memset(climateOutput.annualPPT_cm, 0., sizeof(double) * numYears); + memset(climateOutput.meanAnnualTemp_C, 0., sizeof(double) * numYears); + memset(climateOutput.minTempFebruary_C, 0., sizeof(double) * numYears); + memset(climateOutput.JulyPPT_mm, 0., sizeof(double) * numYears); for(yearIndex = 0; yearIndex < numYears; yearIndex++) { year = yearIndex + startYear; @@ -210,7 +156,7 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, numDaysYear = Time_get_lastdoy_y(year); month = 0; currMonDay = 0; - currentJulyMin = 999; + currentJulyMin = SW_MISSING; totalAbove65 = 0; currentNonFrost = 0; consecNonFrost = 0; @@ -218,13 +164,13 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, for(day = 0; day < numDaysYear; day++) { currMonDay++; - meanMonthlyTemp_C[month][yearIndex] += allHist[yearIndex]->temp_avg[day]; - maxMonthlyTemp_C[month][yearIndex] += allHist[yearIndex]->temp_max[day]; - minMonthlyTemp_C[month][yearIndex] += allHist[yearIndex]->temp_min[day]; - monthlyPPT_cm[month][yearIndex] += allHist[yearIndex]->ppt[day]; + climateOutput.meanMonthlyTemp_C[month][yearIndex] += allHist[yearIndex]->temp_avg[day]; + climateOutput.maxMonthlyTemp_C[month][yearIndex] += allHist[yearIndex]->temp_max[day]; + climateOutput.minMonthlyTemp_C[month][yearIndex] += allHist[yearIndex]->temp_min[day]; + climateOutput.monthlyPPT_cm[month][yearIndex] += allHist[yearIndex]->ppt[day]; - annualPPT_cm[yearIndex] += allHist[yearIndex]->ppt[day]; - meanAnnualTemp_C[yearIndex] += allHist[yearIndex]->temp_avg[day]; + climateOutput.annualPPT_cm[yearIndex] += allHist[yearIndex]->ppt[day]; + climateOutput.meanAnnualTemp_C[yearIndex] += allHist[yearIndex]->temp_avg[day]; currentTempMin = allHist[yearIndex]->temp_min[day]; currentTempMean = allHist[yearIndex]->temp_avg[day]; @@ -245,16 +191,16 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, } if(month == Feb) { - minTempFebruary_C[yearIndex] += allHist[yearIndex]->temp_min[day]; + climateOutput.minTempFebruary_C[yearIndex] += allHist[yearIndex]->temp_min[day]; } if(currMonDay == numDaysMonth) { // Take the average of the current months values for current year - meanMonthlyTemp_C[month][yearIndex] /= numDaysMonth; - maxMonthlyTemp_C[month][yearIndex] /= numDaysMonth; - minMonthlyTemp_C[month][yearIndex] /= numDaysMonth; + climateOutput.meanMonthlyTemp_C[month][yearIndex] /= numDaysMonth; + climateOutput.maxMonthlyTemp_C[month][yearIndex] /= numDaysMonth; + climateOutput.minMonthlyTemp_C[month][yearIndex] /= numDaysMonth; - if(month == Feb) minTempFebruary_C[yearIndex] /= numDaysMonth; + if(month == Feb) climateOutput.minTempFebruary_C[yearIndex] /= numDaysMonth; month++; numDaysMonth = Time_days_in_month(month % 12); @@ -266,17 +212,18 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, } - JulyMinTemp[yearIndex] = currentJulyMin; - JulyPPT_mm[yearIndex] = JulyPPT; - ddAbove65F_degday[yearIndex] = totalAbove65; + climateOutput.JulyMinTemp[yearIndex] = currentJulyMin; + climateOutput.JulyPPT_mm[yearIndex] = JulyPPT; + 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 - frostFreeDays_days[yearIndex] = (consecNonFrost > 0) ? consecNonFrost : currentNonFrost; + climateOutput.frostFreeDays_days[yearIndex] = (consecNonFrost > 0) ? consecNonFrost : currentNonFrost; - meanAnnualTemp_C[yearIndex] /= numDaysYear; + climateOutput.meanAnnualTemp_C[yearIndex] /= numDaysYear; } - findDriestQtr(meanTempDriestQuarter_C, numYears, meanMonthlyTemp_C, monthlyPPT_cm); + findDriestQtr(climateOutput.meanTempDriestQuarter_C, numYears, + climateOutput.meanMonthlyTemp_C, climateOutput.monthlyPPT_cm); } /** diff --git a/SW_Weather.h b/SW_Weather.h index 8e524f2ca..8f1473a8a 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -117,20 +117,10 @@ extern SW_WEATHER SW_Weather; void SW_WTH_setup(void); void SW_WTH_read(void); Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather); -void averageClimateAcrossYears(double **meanMonthlyTemp_C, double **maxMonthlyTemp_C, - double **minMonthlyTemp_C, double **monthlyPPT_cm, double numYears, double JulyMinTemp[], - double frostFreeDays_days[], double ddAbove65FDegday[], double JulyPPT_mm[], - double meanTempDriestQuarter_C[], double minTempFebruary_C[], double annualPPT_cm[], - double meanAnnualTemp_C[], double *meanMonthlyTempAnn, double *maxMonthlyTempAnn, - double *minMonthlyTempAnn, double *meanMonthlyPPTAnn, double *sdC4, double *sdCheatgrass, - double *MAT_C, double *MAP_cm, double *JulyPPTAnn_mm, double *meanTempDriestQuarterAnn_C, - double *minTempFebruaryAnn_C, double *ddAbove65FDegdayAnn, double *frostFreeAnn, - double *JulyMinTempAnn); +void averageClimateAcrossYears(SW_CLIMATE_OUTPUT climateOutput, int numYears, + SW_CLIMATE_AVERAGES climateAverages); void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, - double **meanMonthlyTemp_C, double **maxMonthlyTemp_C, double **minMonthlyTemp_C, - double **meanMonthlyPPT_cm, double *annualPPT_cm, double *meanAnnualTemp_C, double *JulyMinTemp, - double *frostFreeDays_days, double *ddAbove65F_degday, double *JulyPPT_mm, - double *meanTempDriestQuarter_C, double *minTempFebruary_C); + SW_CLIMATE_OUTPUT climateOutput); void findDriestQtr(double *meanTempDriestQuarter_C, int numYears, double **meanMonthlyTemp_C, double **meanMonthlyPPT_cm); void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, unsigned int n_years); From ce43eae36a3a724c3eff614e5eac0092d3ed1601 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Thu, 21 Jul 2022 16:43:01 -0400 Subject: [PATCH 107/326] Fix potential for memory leak in `SW_WTH_read()` - new test "ReadAllWeatherTest.NoMemoryLeakIfDecreasedNumberOfYears" causes a memory leak - this is because `SW_WTH_read()` was previously first calculating `n_years` and then deallocating previously allocated `allHist` memory - is worked correctly only if the value of `n_years` did not change since a previous allocation - in particular, a memory leak occurred if `n_years` decreased since a previous allocation -> fix: `SW_WTH_read()` now first deallocates `allHist` (using unchanged value of `n_years) and then calculates `n_years` for the new allocation -> this fix allows to simplify a few other tests (that called `deallocateAllWeather()` to avoid memory leaks) --- SW_Weather.c | 10 +++++++--- test/test_SW_Weather.cc | 26 +++++++++++++++++++------- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 6aa0adc14..5f66672ee 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -479,7 +479,12 @@ void SW_WTH_setup(void) { } void SW_WTH_read(void) { - + + // Deallocate (previous, if any) `allHist` + // (using value of `SW_Weather.n_years` previously used to allocate) + deallocateAllWeather(); + + // Determine required (new) size of new `allHist` #ifdef STEPWAT SW_Weather.n_years = SuperGlobals.runModelYears; #else @@ -487,8 +492,7 @@ void SW_WTH_read(void) { #endif unsigned int year; - deallocateAllWeather(); - + // Allocate new `allHist` (based on current `SW_Weather.n_years`) SW_Weather.allHist = (SW_WEATHER_HIST **)malloc(sizeof(SW_WEATHER_HIST *) * SW_Weather.n_years); for(year = 0; year < SW_Weather.n_years; year++) { diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index abc8823d2..546c6f5b6 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -33,7 +33,25 @@ namespace { 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); - + + } + + 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) { @@ -62,9 +80,6 @@ namespace { TEST(ReadAllWeatherTest, SomeMissingValuesYears) { int year, day; - - deallocateAllWeather(); - SW_Weather.use_weathergenerator = swTRUE; // Change directory to get input files with some missing data @@ -115,9 +130,6 @@ namespace { TEST(ReadAllWeatherTest, CheckMissingForMissingYear) { int day; - - deallocateAllWeather(); - // Change directory to get input files with some missing data strcpy(SW_Weather.name_prefix, "Input/data_weather_nonexisting/weath"); From 2941dd90b60b3555deeacf050dbdd33cea10d645 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 21 Jul 2022 17:37:08 -0400 Subject: [PATCH 108/326] Renamed type `SW_CLIMATE_OUTPUT` to `SW_CLIMATE_CALC` - Made `climateOutput` and `climateAverages` a pointer within parameters --- SW_Weather.c | 6 +++--- SW_Weather.h | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 290208584..c161a7e54 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -88,8 +88,8 @@ static char *MyFileName; standard deviations output by `averageClimateAcrossYears()` */ -void averageClimateAcrossYears(SW_CLIMATE_OUTPUT climateOutput, int numYears, - SW_CLIMATE_AVERAGES climateAverages) { +void averageClimateAcrossYears(SW_CLIMATE_CALC *climateOutput, int numYears, + SW_CLIMATE_AVERAGES *climateAverages) { int month; @@ -131,7 +131,7 @@ void averageClimateAcrossYears(SW_CLIMATE_OUTPUT climateOutput, int numYears, */ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, - SW_CLIMATE_OUTPUT climateOutput) { + SW_CLIMATE_CALC *climateOutput) { int month, yearIndex, year, day, numDaysYear, numDaysMonth = Time_days_in_month(0), currMonDay; diff --git a/SW_Weather.h b/SW_Weather.h index 8f1473a8a..d2f56fb12 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -61,7 +61,7 @@ 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_OUTPUT; +} SW_CLIMATE_CALC; typedef struct { RealD *annualPPT_cm, *meanAnnualTemp_C, *meanMonthlyTempAnn, *maxMonthlyTempAnn, @@ -117,10 +117,10 @@ extern SW_WEATHER SW_Weather; void SW_WTH_setup(void); void SW_WTH_read(void); Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather); -void averageClimateAcrossYears(SW_CLIMATE_OUTPUT climateOutput, int numYears, - SW_CLIMATE_AVERAGES climateAverages); +void averageClimateAcrossYears(SW_CLIMATE_CALC *climateOutput, int numYears, + SW_CLIMATE_AVERAGES *climateAverages); void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, - SW_CLIMATE_OUTPUT climateOutput); + SW_CLIMATE_CALC *climateOutput); void findDriestQtr(double *meanTempDriestQuarter_C, int numYears, double **meanMonthlyTemp_C, double **meanMonthlyPPT_cm); void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, unsigned int n_years); From e09a2e079df712d1af5431e99266d8d593413aa1 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 21 Jul 2022 17:37:57 -0400 Subject: [PATCH 109/326] Deleted two unused variables in `SW_CLIMATE_AVERAGES` --- SW_Weather.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/SW_Weather.h b/SW_Weather.h index d2f56fb12..4f3b53edf 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -64,10 +64,9 @@ typedef struct { } SW_CLIMATE_CALC; typedef struct { - RealD *annualPPT_cm, *meanAnnualTemp_C, *meanMonthlyTempAnn, *maxMonthlyTempAnn, - *minMonthlyTempAnn, *meanMonthlyPPTAnn, *sdC4, *sdCheatgrass, *MAT_C, *MAP_cm, - *JulyPPTAnn_mm, *meanTempDriestQuarterAnn_C, *minTempFebruaryAnn_C, *ddAbove65F_degdayAnn, - *frostFreeAnn, *JulyMinTempAnn; + 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 { From 3afd50479a76fdfc8307ed1b5289afcef970d73b Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 21 Jul 2022 17:40:42 -0400 Subject: [PATCH 110/326] Code properly accesses pointers in climate functions --- SW_Weather.c | 95 +++++++++++++++++++++++++--------------------------- 1 file changed, 46 insertions(+), 49 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index c161a7e54..be8cee58c 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -94,30 +94,30 @@ void averageClimateAcrossYears(SW_CLIMATE_CALC *climateOutput, int numYears, int month; for(month = 0; month < MAX_MONTHS; month++) { - climateAverages.meanMonthlyTempAnn[month] = mean(climateOutput.meanMonthlyTemp_C[month], numYears); - climateAverages.maxMonthlyTempAnn[month] = mean(climateOutput.maxMonthlyTemp_C[month], numYears); - climateAverages.minMonthlyTempAnn[month] = mean(climateOutput.minMonthlyTemp_C[month], numYears); - climateAverages.meanMonthlyPPTAnn[month] = mean(climateOutput.monthlyPPT_cm[month], numYears); + climateAverages->meanMonthlyTempAnn[month] = mean(climateOutput->meanMonthlyTemp_C[month], numYears); + climateAverages->maxMonthlyTempAnn[month] = mean(climateOutput->maxMonthlyTemp_C[month], numYears); + climateAverages->minMonthlyTempAnn[month] = mean(climateOutput->minMonthlyTemp_C[month], numYears); + climateAverages->meanMonthlyPPTAnn[month] = mean(climateOutput->monthlyPPT_cm[month], numYears); } - *climateAverages.MAP_cm = mean(climateOutput.annualPPT_cm, numYears); - *climateAverages.MAT_C = mean(climateOutput.meanAnnualTemp_C, numYears); - *climateAverages.JulyPPTAnn_mm = mean(climateOutput.JulyPPT_mm, numYears); - *climateAverages.meanTempDriestQuarterAnn_C = mean(climateOutput.meanTempDriestQuarter_C, numYears); - *climateAverages.minTempFebruaryAnn_C = mean(climateOutput.minTempFebruary_C, numYears); - *climateAverages.ddAbove65F_degdayAnn = mean(climateOutput.ddAbove65F_degday, numYears); - *climateAverages.frostFreeAnn = mean(climateOutput.frostFreeDays_days, numYears); - *climateAverages.JulyMinTempAnn = mean(climateOutput.JulyMinTemp, numYears); + climateAverages->MAP_cm = mean(climateOutput->annualPPT_cm, numYears); + climateAverages->MAT_C = mean(climateOutput->meanAnnualTemp_C, numYears); + climateAverages->JulyPPTAnn_mm = mean(climateOutput->JulyPPT_mm, numYears); + climateAverages->meanTempDriestQuarterAnn_C = mean(climateOutput->meanTempDriestQuarter_C, numYears); + climateAverages->minTempFebruaryAnn_C = mean(climateOutput->minTempFebruary_C, numYears); + climateAverages->ddAbove65F_degdayAnn = mean(climateOutput->ddAbove65F_degday, numYears); + climateAverages->frostFreeAnn = mean(climateOutput->frostFreeDays_days, numYears); + climateAverages->JulyMinTempAnn = mean(climateOutput->JulyMinTemp, numYears); - // Calculate and set standard deviation of C4 variables (frostFreeDays is a running sd) - climateAverages.sdC4[0] = standardDeviation(climateOutput.JulyMinTemp, numYears); - climateAverages.sdC4[1] = standardDeviation(climateOutput.frostFreeDays_days, numYears); - climateAverages.sdC4[2] = standardDeviation(climateOutput.ddAbove65F_degday, numYears); + // Calculate and set standard deviation of C4 variables + climateAverages->sdC4[0] = standardDeviation(climateOutput->JulyMinTemp, numYears); + climateAverages->sdC4[1] = standardDeviation(climateOutput->frostFreeDays_days, numYears); + climateAverages->sdC4[2] = standardDeviation(climateOutput->ddAbove65F_degday, numYears); // Calculate and set the standard deviation of cheatgrass variables - climateAverages.sdCheatgrass[0] = standardDeviation(climateOutput.JulyPPT_mm, numYears); - climateAverages.sdCheatgrass[1] = standardDeviation(climateOutput.meanTempDriestQuarter_C, numYears); - climateAverages.sdCheatgrass[2] = standardDeviation(climateOutput.minTempFebruary_C, numYears); + climateAverages->sdCheatgrass[0] = standardDeviation(climateOutput->JulyPPT_mm, numYears); + climateAverages->sdCheatgrass[1] = standardDeviation(climateOutput->meanTempDriestQuarter_C, numYears); + climateAverages->sdCheatgrass[2] = standardDeviation(climateOutput->minTempFebruary_C, numYears); } /** @@ -140,16 +140,15 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, consecNonFrost, currentNonFrost; for(month = 0; month < MAX_MONTHS; month++) { - memset(climateOutput.meanMonthlyTemp_C[month], 0., sizeof(double) * numYears); - memset(climateOutput.maxMonthlyTemp_C[month], 0., sizeof(double) * numYears); - memset(climateOutput.minMonthlyTemp_C[month], 0., sizeof(double) * numYears); - memset(climateOutput.monthlyPPT_cm[month], 0., sizeof(double) * numYears); + memset(climateOutput->meanMonthlyTemp_C[month], 0., sizeof(double) * numYears); + memset(climateOutput->maxMonthlyTemp_C[month], 0., sizeof(double) * numYears); + memset(climateOutput->minMonthlyTemp_C[month], 0., sizeof(double) * numYears); + memset(climateOutput->monthlyPPT_cm[month], 0., sizeof(double) * numYears); } - memset(climateOutput.annualPPT_cm, 0., sizeof(double) * numYears); - memset(climateOutput.meanAnnualTemp_C, 0., sizeof(double) * numYears); - memset(climateOutput.minTempFebruary_C, 0., sizeof(double) * numYears); - memset(climateOutput.JulyPPT_mm, 0., sizeof(double) * numYears); - + memset(climateOutput->annualPPT_cm, 0., sizeof(double) * numYears); + memset(climateOutput->meanAnnualTemp_C, 0., sizeof(double) * numYears); + memset(climateOutput->minTempFebruary_C, 0., sizeof(double) * numYears); + memset(climateOutput->JulyPPT_mm, 0., sizeof(double) * numYears); for(yearIndex = 0; yearIndex < numYears; yearIndex++) { year = yearIndex + startYear; Time_new_year(year); @@ -164,13 +163,13 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, for(day = 0; day < numDaysYear; day++) { currMonDay++; - climateOutput.meanMonthlyTemp_C[month][yearIndex] += allHist[yearIndex]->temp_avg[day]; - climateOutput.maxMonthlyTemp_C[month][yearIndex] += allHist[yearIndex]->temp_max[day]; - climateOutput.minMonthlyTemp_C[month][yearIndex] += allHist[yearIndex]->temp_min[day]; - climateOutput.monthlyPPT_cm[month][yearIndex] += allHist[yearIndex]->ppt[day]; - - climateOutput.annualPPT_cm[yearIndex] += allHist[yearIndex]->ppt[day]; - climateOutput.meanAnnualTemp_C[yearIndex] += allHist[yearIndex]->temp_avg[day]; + climateOutput->meanMonthlyTemp_C[month][yearIndex] += allHist[yearIndex]->temp_avg[day]; + climateOutput->maxMonthlyTemp_C[month][yearIndex] += allHist[yearIndex]->temp_max[day]; + climateOutput->minMonthlyTemp_C[month][yearIndex] += allHist[yearIndex]->temp_min[day]; + climateOutput->monthlyPPT_cm[month][yearIndex] += allHist[yearIndex]->ppt[day]; + + climateOutput->annualPPT_cm[yearIndex] += allHist[yearIndex]->ppt[day]; + climateOutput->meanAnnualTemp_C[yearIndex] += allHist[yearIndex]->temp_avg[day]; currentTempMin = allHist[yearIndex]->temp_min[day]; currentTempMean = allHist[yearIndex]->temp_avg[day]; @@ -191,16 +190,16 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, } if(month == Feb) { - climateOutput.minTempFebruary_C[yearIndex] += allHist[yearIndex]->temp_min[day]; + climateOutput->minTempFebruary_C[yearIndex] += allHist[yearIndex]->temp_min[day]; } if(currMonDay == numDaysMonth) { // Take the average of the current months values for current year - climateOutput.meanMonthlyTemp_C[month][yearIndex] /= numDaysMonth; - climateOutput.maxMonthlyTemp_C[month][yearIndex] /= numDaysMonth; - climateOutput.minMonthlyTemp_C[month][yearIndex] /= numDaysMonth; + climateOutput->meanMonthlyTemp_C[month][yearIndex] /= numDaysMonth; + climateOutput->maxMonthlyTemp_C[month][yearIndex] /= numDaysMonth; + climateOutput->minMonthlyTemp_C[month][yearIndex] /= numDaysMonth; - if(month == Feb) climateOutput.minTempFebruary_C[yearIndex] /= numDaysMonth; + if(month == Feb) climateOutput->minTempFebruary_C[yearIndex] /= numDaysMonth; month++; numDaysMonth = Time_days_in_month(month % 12); @@ -211,19 +210,17 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, totalAbove65 += (currentTempMean > 0.0) ? currentTempMean : 0.; } - - climateOutput.JulyMinTemp[yearIndex] = currentJulyMin; - climateOutput.JulyPPT_mm[yearIndex] = JulyPPT; - climateOutput.ddAbove65F_degday[yearIndex] = totalAbove65; + climateOutput->JulyMinTemp[yearIndex] = currentJulyMin; + climateOutput->JulyPPT_mm[yearIndex] = JulyPPT; + 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.frostFreeDays_days[yearIndex] = (consecNonFrost > 0) ? consecNonFrost : currentNonFrost; - - climateOutput.meanAnnualTemp_C[yearIndex] /= numDaysYear; + climateOutput->frostFreeDays_days[yearIndex] = (consecNonFrost > 0) ? consecNonFrost : currentNonFrost; + climateOutput->meanAnnualTemp_C[yearIndex] /= numDaysYear; } - findDriestQtr(climateOutput.meanTempDriestQuarter_C, numYears, - climateOutput.meanMonthlyTemp_C, climateOutput.monthlyPPT_cm); + findDriestQtr(climateOutput->meanTempDriestQuarter_C, numYears, + climateOutput->meanMonthlyTemp_C, climateOutput->monthlyPPT_cm); } /** From 32205b44d58462d22db1a4032fe6a13329263c99 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 21 Jul 2022 17:45:49 -0400 Subject: [PATCH 111/326] Climate tests in `test_SW_Weather.cc` use new structs - Conformed code to use new structs - Added a loop to deallocate big chunk of arrays in tests for: * `calcSiteClimate()` and `averageClimateAcrossYears()` --- test/test_SW_Weather.cc | 580 +++++++++++++++++++--------------------- 1 file changed, 271 insertions(+), 309 deletions(-) diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 2e2e5b283..eec833fbb 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -140,151 +140,124 @@ namespace { TEST(AverageClimateAcrossYearsTest, FileAndValuesOfOne) { // This test relies on allHist from `SW_WEATHER` being already filled - - double meanMonthlyTempAnn[MAX_MONTHS]; - double maxMonthlyTempAnn[MAX_MONTHS]; - double minMonthlyTempAnn[MAX_MONTHS]; - double meanMonthlyPPTAnn[MAX_MONTHS]; - double JulyMinTemp[31]; - double frostFreeDays_days[31]; - double ddAbove65F_degday[31]; - double JulyPPT_mm[31]; - double meanTempDriestQuarter_C[31]; - double minTempFebruary_C[31]; - double sdCheatgrass[3]; - double sdC4[3]; - double annualPPT_cm[31]; - double meanAnnualTemp_C[31]; - - double MAP_cm; - double MAT_C; - double JulyPPTAnn_mm; - double meanTempDriestQuarterAnn_C; - double minTempFebruaryAnn_C; - double ddAbove65F_degdayAnn; - double frostFreeAnn; - double JulyMinTempAnn; - - double **monthlyPPT_cm; - monthlyPPT_cm = new double*[MAX_MONTHS]; - - double **meanMonthlyTemp_C = new double*[MAX_MONTHS]; - - double **minMonthlyTemp_C = new double*[MAX_MONTHS]; - - double **maxMonthlyTemp_C = new double*[MAX_MONTHS]; - + SW_CLIMATE_CALC climateOutput; + SW_CLIMATE_AVERAGES climateAverage; + + climateOutput.JulyMinTemp = new double[31]; // 31 = Number of years in the simulation + climateOutput.annualPPT_cm = new double[31]; + climateOutput.frostFreeDays_days = new double[31]; + climateOutput.ddAbove65F_degday = new double[31]; + climateOutput.JulyPPT_mm = new double[31]; + climateOutput.meanTempDriestQuarter_C = new double[31]; + climateOutput.minTempFebruary_C = new double[31]; + climateOutput.meanAnnualTemp_C = new double[31]; + climateOutput.monthlyPPT_cm = new double*[MAX_MONTHS]; + climateOutput.meanMonthlyTemp_C = new double*[MAX_MONTHS]; + climateOutput.minMonthlyTemp_C = new double*[MAX_MONTHS]; + climateOutput.maxMonthlyTemp_C = new double*[MAX_MONTHS]; + + climateAverage.meanMonthlyTempAnn = new double[MAX_MONTHS]; + climateAverage.maxMonthlyTempAnn = new double[MAX_MONTHS]; + climateAverage.minMonthlyTempAnn = new double[MAX_MONTHS]; + climateAverage.meanMonthlyPPTAnn = new double[MAX_MONTHS]; + climateAverage.sdCheatgrass = new double[3]; + climateAverage.sdC4 = new double[3]; + + double *freeArray[14] = {climateOutput.JulyMinTemp, climateOutput.annualPPT_cm, + climateOutput.frostFreeDays_days, climateOutput.ddAbove65F_degday, climateOutput.JulyPPT_mm, + climateOutput.meanTempDriestQuarter_C, climateOutput.minTempFebruary_C, climateOutput.meanAnnualTemp_C, + climateAverage.meanMonthlyTempAnn, climateAverage.maxMonthlyTempAnn, climateAverage.minMonthlyTempAnn, + climateAverage.meanMonthlyPPTAnn, climateAverage.sdCheatgrass, climateAverage.sdC4}; + for(int month = 0; month < MAX_MONTHS; month++) { - monthlyPPT_cm[month] = new double[31]; - meanMonthlyTemp_C[month] = new double[31]; - minMonthlyTemp_C[month] = new double[31]; - maxMonthlyTemp_C[month] = new double[31]; + climateOutput.monthlyPPT_cm[month] = new double[31]; + climateOutput.meanMonthlyTemp_C[month] = new double[31]; + climateOutput.minMonthlyTemp_C[month] = new double[31]; + climateOutput.maxMonthlyTemp_C[month] = new double[31]; for(int year = 0; year < 31; year++) { - - monthlyPPT_cm[month][year] = 0.; - meanMonthlyTemp_C[month][year] = 0.; - minMonthlyTemp_C[month][year] = 0.; - maxMonthlyTemp_C[month][year] = 0.; - annualPPT_cm[year] = 0.; - meanAnnualTemp_C[year] = 0.; + climateOutput.monthlyPPT_cm[month][year] = 0.; + climateOutput.meanMonthlyTemp_C[month][year] = 0.; + climateOutput.minMonthlyTemp_C[month][year] = 0.; + climateOutput.maxMonthlyTemp_C[month][year] = 0.; } } // 1980 is start year of the simulation - calcSiteClimate(SW_Weather.allHist, 31, 1980, meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, - monthlyPPT_cm, annualPPT_cm, meanAnnualTemp_C, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, - JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C); - - averageClimateAcrossYears(meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, - monthlyPPT_cm, 31, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, - JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C, annualPPT_cm, - meanAnnualTemp_C, meanMonthlyTempAnn, maxMonthlyTempAnn, minMonthlyTempAnn, - meanMonthlyPPTAnn, sdC4, sdCheatgrass, &MAT_C, &MAP_cm, &JulyPPTAnn_mm, - &meanTempDriestQuarterAnn_C, &minTempFebruaryAnn_C, &ddAbove65F_degdayAnn, - &frostFreeAnn, &JulyMinTempAnn); - - EXPECT_NEAR(meanMonthlyTempAnn[0], -9.325551, tol6); - EXPECT_NEAR(maxMonthlyTempAnn[0], -2.714381, tol6); - EXPECT_NEAR(minMonthlyTempAnn[0], -15.936722, tol6); - EXPECT_NEAR(meanMonthlyPPTAnn[0], 6.867419, tol6); - - EXPECT_NEAR(meanAnnualTemp_C[0], 4.524863, tol6); - EXPECT_NEAR(annualPPT_cm[0], 59.2700004, tol6); - EXPECT_NEAR(MAP_cm, 62.817419, tol6); - EXPECT_NEAR(MAT_C, 4.154009, tol6); - EXPECT_NEAR(JulyPPTAnn_mm, 35.729032, tol6); - EXPECT_NEAR(meanTempDriestQuarterAnn_C, 11.524859, tol6); - EXPECT_NEAR(minTempFebruaryAnn_C, -13.904599, tol6); - EXPECT_NEAR(ddAbove65F_degdayAnn, 21.168032, tol6); - EXPECT_NEAR(frostFreeAnn, 90.612903, tol6); - EXPECT_NEAR(JulyMinTempAnn, 3.078387, tol6); + calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput); + averageClimateAcrossYears(&climateOutput, 31, &climateAverage); + + EXPECT_NEAR(climateAverage.meanMonthlyTempAnn[0], -9.325551, tol6); + EXPECT_NEAR(climateAverage.maxMonthlyTempAnn[0], -2.714381, tol6); + EXPECT_NEAR(climateAverage.minMonthlyTempAnn[0], -15.936722, tol6); + EXPECT_NEAR(climateAverage.meanMonthlyPPTAnn[0], 6.867419, tol6); + + EXPECT_NEAR(climateAverage.MAP_cm, 62.817419, tol6); + EXPECT_NEAR(climateAverage.MAT_C, 4.154009, tol6); + EXPECT_NEAR(climateAverage.JulyPPTAnn_mm, 35.729032, tol6); + EXPECT_NEAR(climateAverage.meanTempDriestQuarterAnn_C, 11.524859, tol6); + EXPECT_NEAR(climateAverage.minTempFebruaryAnn_C, -13.904599, tol6); + EXPECT_NEAR(climateAverage.ddAbove65F_degdayAnn, 21.168032, tol6); + EXPECT_NEAR(climateAverage.frostFreeAnn, 90.612903, tol6); + EXPECT_NEAR(climateAverage.JulyMinTempAnn, 3.078387, tol6); // Standard deviation of C4 variables - EXPECT_NEAR(sdC4[0], 1.785535, tol6); - EXPECT_NEAR(sdC4[1], 14.091788, tol6); - EXPECT_NEAR(sdC4[2], 19.953560, tol6); + EXPECT_NEAR(climateAverage.sdC4[0], 1.785535, tol6); + EXPECT_NEAR(climateAverage.sdC4[1], 14.091788, tol6); + EXPECT_NEAR(climateAverage.sdC4[2], 19.953560, tol6); // Standard deviation of cheatgrass variables - EXPECT_NEAR(sdCheatgrass[0], 21.598367, tol6); - EXPECT_NEAR(sdCheatgrass[1], 7.171922, tol6); - EXPECT_NEAR(sdCheatgrass[2], 2.618434, tol6); + EXPECT_NEAR(climateAverage.sdCheatgrass[0], 21.598367, tol6); + EXPECT_NEAR(climateAverage.sdCheatgrass[1], 7.171922, tol6); + EXPECT_NEAR(climateAverage.sdCheatgrass[2], 2.618434, tol6); - for(int month = 0; month < MAX_MONTHS; month++) { - for(int year = 0; year < 31; year++) { - monthlyPPT_cm[month][year] = 1.; - meanMonthlyTemp_C[month][year] = 1.; - minMonthlyTemp_C[month][year] = 1.; - maxMonthlyTemp_C[month][year] = 1.; - annualPPT_cm[year] = 1.; - meanAnnualTemp_C[year] = 1.; + for(int year = 0; year < 31; year++) { + for(int month = 0; month < MAX_MONTHS; month++) { + climateOutput.monthlyPPT_cm[month][year] = 1.; + climateOutput.meanMonthlyTemp_C[month][year] = 1.; + climateOutput.minMonthlyTemp_C[month][year] = 1.; + climateOutput.maxMonthlyTemp_C[month][year] = 1.; } + climateOutput.annualPPT_cm[year] = 1.; + climateOutput.meanAnnualTemp_C[year] = 1.; } // Reset values for(int year = 0; year < 31; year++) { for(int month = 0; month < MAX_MONTHS; month++) { - monthlyPPT_cm[month][year] = 0.; - meanMonthlyTemp_C[month][year] = 0.; - minMonthlyTemp_C[month][year] = 0.; - maxMonthlyTemp_C[month][year] = 0.; + climateOutput.monthlyPPT_cm[month][year] = 0.; + climateOutput.meanMonthlyTemp_C[month][year] = 0.; + climateOutput.minMonthlyTemp_C[month][year] = 0.; + climateOutput.maxMonthlyTemp_C[month][year] = 0.; } - annualPPT_cm[year] = 0.; - meanAnnualTemp_C[year] = 0.; + climateOutput.annualPPT_cm[year] = 0.; + climateOutput.meanAnnualTemp_C[year] = 0.; } // Tests for one year of simulation - calcSiteClimate(SW_Weather.allHist, 1, 1980, meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, - monthlyPPT_cm, annualPPT_cm, meanAnnualTemp_C, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, - JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C); - - averageClimateAcrossYears(meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, - monthlyPPT_cm, 1, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, - JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C, annualPPT_cm, - meanAnnualTemp_C, meanMonthlyTempAnn, maxMonthlyTempAnn, minMonthlyTempAnn, - meanMonthlyPPTAnn, sdC4, sdCheatgrass, &MAT_C, &MAP_cm, &JulyPPTAnn_mm, - &meanTempDriestQuarterAnn_C, &minTempFebruaryAnn_C, &ddAbove65F_degdayAnn, - &frostFreeAnn, &JulyMinTempAnn); - - EXPECT_NEAR(meanMonthlyTempAnn[0], -8.432581, tol6); - EXPECT_NEAR(maxMonthlyTempAnn[0], -2.562581, tol6); - EXPECT_NEAR(minMonthlyTempAnn[0], -14.302581, tol6); - EXPECT_NEAR(meanMonthlyPPTAnn[0], 15.1400001, tol6); - EXPECT_NEAR(MAP_cm, 59.27, tol1); - EXPECT_NEAR(MAT_C, 4.524863, tol1); - EXPECT_NEAR(JulyPPTAnn_mm, 18.299999, tol6); - EXPECT_NEAR(meanTempDriestQuarterAnn_C, 0.936387, tol6); - EXPECT_NEAR(minTempFebruaryAnn_C, -12.822068, tol6); - EXPECT_NEAR(ddAbove65F_degdayAnn, 13.546000, tol6); - EXPECT_NEAR(frostFreeAnn, 92, tol6); - EXPECT_NEAR(JulyMinTempAnn, 2.809999, tol6); + calcSiteClimate(SW_Weather.allHist, 1, 1980, &climateOutput); + + averageClimateAcrossYears(&climateOutput, 1, &climateAverage); + + EXPECT_NEAR(climateAverage.meanMonthlyTempAnn[0], -8.432581, tol6); + EXPECT_NEAR(climateAverage.maxMonthlyTempAnn[0], -2.562581, tol6); + EXPECT_NEAR(climateAverage.minMonthlyTempAnn[0], -14.302581, tol6); + EXPECT_NEAR(climateAverage.meanMonthlyPPTAnn[0], 15.1400001, tol6); + EXPECT_NEAR(climateAverage.MAP_cm, 59.27, tol1); + EXPECT_NEAR(climateAverage.MAT_C, 4.524863, tol1); + EXPECT_NEAR(climateAverage.JulyPPTAnn_mm, 18.299999, tol6); + EXPECT_NEAR(climateAverage.meanTempDriestQuarterAnn_C, 0.936387, tol6); + EXPECT_NEAR(climateAverage.minTempFebruaryAnn_C, -12.822068, tol6); + EXPECT_NEAR(climateAverage.ddAbove65F_degdayAnn, 13.546000, tol6); + EXPECT_NEAR(climateAverage.frostFreeAnn, 92, tol6); + EXPECT_NEAR(climateAverage.JulyMinTempAnn, 2.809999, tol6); // Standard deviation of C4 variables of one year - EXPECT_TRUE(isnan(sdC4[0])); - EXPECT_TRUE(isnan(sdC4[1])); - EXPECT_TRUE(isnan(sdC4[2])); + EXPECT_TRUE(isnan(climateAverage.sdC4[0])); + EXPECT_TRUE(isnan(climateAverage.sdC4[1])); + EXPECT_TRUE(isnan(climateAverage.sdC4[2])); // Standard deviation of cheatgrass variables of one year - EXPECT_TRUE(isnan(sdCheatgrass[0])); - EXPECT_TRUE(isnan(sdCheatgrass[1])); - EXPECT_TRUE(isnan(sdCheatgrass[2])); + EXPECT_TRUE(isnan(climateAverage.sdCheatgrass[0])); + EXPECT_TRUE(isnan(climateAverage.sdCheatgrass[1])); + EXPECT_TRUE(isnan(climateAverage.sdCheatgrass[2])); for(int year = 0; year < 31; year++) { for(int day = 0; day < 366; day++) { @@ -296,143 +269,131 @@ namespace { } // Start of tests with all `allHist` inputs of 1 - calcSiteClimate(SW_Weather.allHist, 2, 1980, meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, - monthlyPPT_cm, annualPPT_cm, meanAnnualTemp_C, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, - JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C); - - averageClimateAcrossYears(meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, - monthlyPPT_cm, 2, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, - JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C, annualPPT_cm, - meanAnnualTemp_C, meanMonthlyTempAnn, maxMonthlyTempAnn, minMonthlyTempAnn, - meanMonthlyPPTAnn, sdC4, sdCheatgrass, &MAT_C, &MAP_cm, &JulyPPTAnn_mm, - &meanTempDriestQuarterAnn_C, &minTempFebruaryAnn_C, &ddAbove65F_degdayAnn, - &frostFreeAnn, &JulyMinTempAnn); - - EXPECT_DOUBLE_EQ(meanMonthlyTempAnn[0], 1.); - EXPECT_DOUBLE_EQ(maxMonthlyTempAnn[0], 1.); - EXPECT_DOUBLE_EQ(minMonthlyTempAnn[0], 1.); - EXPECT_DOUBLE_EQ(meanMonthlyPPTAnn[0], 31.); - EXPECT_DOUBLE_EQ(JulyPPTAnn_mm, 310.); - EXPECT_DOUBLE_EQ(meanTempDriestQuarterAnn_C, 1.); - EXPECT_DOUBLE_EQ(minTempFebruaryAnn_C, 1.); - EXPECT_DOUBLE_EQ(ddAbove65F_degdayAnn, 0.); - EXPECT_DOUBLE_EQ(frostFreeAnn, 365.5); - EXPECT_DOUBLE_EQ(JulyMinTempAnn, 1.); + calcSiteClimate(SW_Weather.allHist, 2, 1980, &climateOutput); + + averageClimateAcrossYears(&climateOutput, 2, &climateAverage); + + EXPECT_DOUBLE_EQ(climateAverage.meanMonthlyTempAnn[0], 1.); + EXPECT_DOUBLE_EQ(climateAverage.maxMonthlyTempAnn[0], 1.); + EXPECT_DOUBLE_EQ(climateAverage.minMonthlyTempAnn[0], 1.); + EXPECT_DOUBLE_EQ(climateAverage.meanMonthlyPPTAnn[0], 31.); + EXPECT_DOUBLE_EQ(climateAverage.JulyPPTAnn_mm, 310.); + EXPECT_DOUBLE_EQ(climateAverage.meanTempDriestQuarterAnn_C, 1.); + EXPECT_DOUBLE_EQ(climateAverage.minTempFebruaryAnn_C, 1.); + EXPECT_DOUBLE_EQ(climateAverage.ddAbove65F_degdayAnn, 0.); + EXPECT_DOUBLE_EQ(climateAverage.frostFreeAnn, 365.5); + EXPECT_DOUBLE_EQ(climateAverage.JulyMinTempAnn, 1.); // MAP_cm is expected to be 365.5 because we are running a leap year // and nonleap year where the number of days average to 365.5 - EXPECT_DOUBLE_EQ(MAP_cm, 365.5); - EXPECT_DOUBLE_EQ(MAT_C, 1.); + EXPECT_DOUBLE_EQ(climateAverage.MAP_cm, 365.5); + EXPECT_DOUBLE_EQ(climateAverage.MAT_C, 1.); // Standard deviation of C4 variables of one year - EXPECT_DOUBLE_EQ(sdC4[0], 0.); - EXPECT_NEAR(sdC4[1], .7071067, tol6); - EXPECT_DOUBLE_EQ(sdC4[2], 0.); + EXPECT_DOUBLE_EQ(climateAverage.sdC4[0], 0.); + EXPECT_NEAR(climateAverage.sdC4[1], .7071067, tol6); + EXPECT_DOUBLE_EQ(climateAverage.sdC4[2], 0.); // Standard deviation of cheatgrass variables of one year - EXPECT_DOUBLE_EQ(sdCheatgrass[0], 0.); - EXPECT_DOUBLE_EQ(sdCheatgrass[1], 0.); - EXPECT_DOUBLE_EQ(sdCheatgrass[2], 0.); + EXPECT_DOUBLE_EQ(climateAverage.sdCheatgrass[0], 0.); + EXPECT_DOUBLE_EQ(climateAverage.sdCheatgrass[1], 0.); + EXPECT_DOUBLE_EQ(climateAverage.sdCheatgrass[2], 0.); for(int month = 0; month < MAX_MONTHS; month++) { - delete[] monthlyPPT_cm[month]; - delete[] meanMonthlyTemp_C[month]; - delete[] minMonthlyTemp_C[month]; - delete[] maxMonthlyTemp_C[month]; + delete[] climateOutput.monthlyPPT_cm[month]; + delete[] climateOutput.meanMonthlyTemp_C[month]; + delete[] climateOutput.minMonthlyTemp_C[month]; + delete[] climateOutput.maxMonthlyTemp_C[month]; } - delete[] monthlyPPT_cm; - delete[] meanMonthlyTemp_C; - delete[] minMonthlyTemp_C; - delete[] maxMonthlyTemp_C; + delete[] climateOutput.monthlyPPT_cm; + delete[] climateOutput.meanMonthlyTemp_C; + delete[] climateOutput.minMonthlyTemp_C; + delete[] climateOutput.maxMonthlyTemp_C; + + // Free rest of allocated memory + for(int index = 0; index < 14; index++) { + free(freeArray[index]); + } } TEST(CalcSiteClimateTest, FileAndValuesOfOne) { - + // This test relies on allHist from `SW_WEATHER` being already filled - - double JulyMinTemp[31]; // 31 = Number of years in the simulation - double frostFreeDays_days[31]; - double ddAbove65F_degday[31]; - double JulyPPT_mm[31]; - double meanTempDriestQuarter_C[31]; - double minTempFebruary_C[31]; - double annualPPT_cm[31]; - double meanAnnualTemp_C[31]; - - double **monthlyPPT_cm; - monthlyPPT_cm = new double*[MAX_MONTHS]; - - double **meanMonthlyTemp_C; - meanMonthlyTemp_C = new double*[MAX_MONTHS]; - - double **minMonthlyTemp_C; - minMonthlyTemp_C = new double*[MAX_MONTHS]; - - double **maxMonthlyTemp_C; - maxMonthlyTemp_C = new double*[MAX_MONTHS]; - + + SW_CLIMATE_CALC climateOutput; + + climateOutput.JulyMinTemp = new double[31]; // 31 = Number of years in the simulation + climateOutput.annualPPT_cm = new double[31]; + climateOutput.frostFreeDays_days = new double[31]; + climateOutput.ddAbove65F_degday = new double[31]; + climateOutput.JulyPPT_mm = new double[31]; + climateOutput.meanTempDriestQuarter_C = new double[31]; + climateOutput.minTempFebruary_C = new double[31]; + climateOutput.meanAnnualTemp_C = new double[31]; + climateOutput.monthlyPPT_cm = new double*[MAX_MONTHS]; + climateOutput.meanMonthlyTemp_C = new double*[MAX_MONTHS]; + climateOutput.minMonthlyTemp_C = new double*[MAX_MONTHS]; + climateOutput.maxMonthlyTemp_C = new double*[MAX_MONTHS]; + + double *freeArray[9] = {climateOutput.JulyMinTemp, climateOutput.annualPPT_cm, + climateOutput.frostFreeDays_days, climateOutput.ddAbove65F_degday, climateOutput.JulyPPT_mm, + climateOutput.meanTempDriestQuarter_C, climateOutput.minTempFebruary_C, climateOutput.meanAnnualTemp_C}; + for(int month = 0; month < MAX_MONTHS; month++) { - monthlyPPT_cm[month] = new double[31]; - meanMonthlyTemp_C[month] = new double[31]; - minMonthlyTemp_C[month] = new double[31]; - maxMonthlyTemp_C[month] = new double[31]; - + climateOutput.monthlyPPT_cm[month] = new double[31]; + climateOutput.meanMonthlyTemp_C[month] = new double[31]; + climateOutput.minMonthlyTemp_C[month] = new double[31]; + climateOutput.maxMonthlyTemp_C[month] = new double[31]; for(int year = 0; year < 31; year++) { - - monthlyPPT_cm[month][year] = 0.; - meanMonthlyTemp_C[month][year] = 0.; - minMonthlyTemp_C[month][year] = 0.; - maxMonthlyTemp_C[month][year] = 0.; - annualPPT_cm[year] = 0.; - meanAnnualTemp_C[year] = 0.; - minTempFebruary_C[year] = 0.; + climateOutput.monthlyPPT_cm[month][year] = 0.; + climateOutput.meanMonthlyTemp_C[month][year] = 0.; + climateOutput.minMonthlyTemp_C[month][year] = 0.; + climateOutput.maxMonthlyTemp_C[month][year] = 0.; } } - + SW_WTH_read(); - + // 1980 is start year of the simulation - calcSiteClimate(SW_Weather.allHist, 31, 1980, meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, - monthlyPPT_cm, annualPPT_cm, meanAnnualTemp_C, JulyMinTemp, frostFreeDays_days, - ddAbove65F_degday, JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C); - + calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput); + // Average of average temperature of January in 1980 - EXPECT_NEAR(meanMonthlyTemp_C[0][0], -8.432581, tol6); - + EXPECT_NEAR(climateOutput.meanMonthlyTemp_C[0][0], -8.432581, tol6); + // Average of max temperature in Januaray 1980 - EXPECT_NEAR(maxMonthlyTemp_C[0][0], -2.562581, tol6); - + EXPECT_NEAR(climateOutput.maxMonthlyTemp_C[0][0], -2.562581, tol6); + // Average of min temperature in Januaray 1980 - EXPECT_NEAR(minMonthlyTemp_C[0][0], -14.302581, tol6); - + EXPECT_NEAR(climateOutput.minMonthlyTemp_C[0][0], -14.302581, tol6); + // Average January precipitation in 1980 - EXPECT_NEAR(monthlyPPT_cm[0][0], 15.14, tol6); - + EXPECT_NEAR(climateOutput.monthlyPPT_cm[0][0], 15.14, tol6); + // Average temperature of three driest month of first year - EXPECT_NEAR(meanTempDriestQuarter_C[0], .936387, tol6); - + EXPECT_NEAR(climateOutput.meanTempDriestQuarter_C[0], .936387, tol6); + // Average precipiation of first year of simulation - EXPECT_NEAR(annualPPT_cm[0], 59.27, tol6); - + EXPECT_NEAR(climateOutput.annualPPT_cm[0], 59.27, tol6); + // Average temperature of first year of simulation - EXPECT_NEAR(meanAnnualTemp_C[0], 4.5248633, tol6); - + EXPECT_NEAR(climateOutput.meanAnnualTemp_C[0], 4.5248633, tol6); + // First year's July minimum temperature - EXPECT_NEAR(JulyMinTemp[0], 2.810000, tol6); - + EXPECT_NEAR(climateOutput.JulyMinTemp[0], 2.810000, tol6); + // First year's number of most consecutive frost free days - EXPECT_EQ(frostFreeDays_days[0], 92); - + EXPECT_EQ(climateOutput.frostFreeDays_days[0], 92); + // Sum of all temperature above 65F (18.333C) in first year - EXPECT_NEAR(ddAbove65F_degday[0], 13.546000, tol6); - + EXPECT_NEAR(climateOutput.ddAbove65F_degday[0], 13.546000, tol6); + // Total precipitation in July of first year - EXPECT_NEAR(JulyPPT_mm[0], 18.300000, tol6); - + EXPECT_NEAR(climateOutput.JulyPPT_mm[0], 18.300000, tol6); + // Smallest temperature in all February first year - EXPECT_NEAR(minTempFebruary_C[0], -12.822069, tol6); - + EXPECT_NEAR(climateOutput.minTempFebruary_C[0], -12.822069, tol6); + for(int year = 0; year < 2; year++) { for(int day = 0; day < 366; day++) { SW_Weather.allHist[year]->temp_max[day] = 1.; @@ -441,152 +402,153 @@ namespace { SW_Weather.allHist[year]->ppt[day] = 1.; } } - - calcSiteClimate(SW_Weather.allHist, 2, 1980, meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, - monthlyPPT_cm, annualPPT_cm, meanAnnualTemp_C, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, - JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C); - + + calcSiteClimate(SW_Weather.allHist, 2, 1980, &climateOutput); + // Start of leap year tests (startYear = 1980) - + // Average of average temperature of January in 1980 - EXPECT_DOUBLE_EQ(meanMonthlyTemp_C[0][0], 1.); - + EXPECT_DOUBLE_EQ(climateOutput.meanMonthlyTemp_C[0][0], 1.); + // Average of max temperature in Januaray 1980 - EXPECT_DOUBLE_EQ(maxMonthlyTemp_C[0][0], 1.); - + EXPECT_DOUBLE_EQ(climateOutput.maxMonthlyTemp_C[0][0], 1.); + // Average of min temperature in Januaray 1980 - EXPECT_DOUBLE_EQ(minMonthlyTemp_C[0][0], 1.); - + EXPECT_DOUBLE_EQ(climateOutput.minMonthlyTemp_C[0][0], 1.); + // Average January precipitation in 1980 - EXPECT_DOUBLE_EQ(monthlyPPT_cm[0][0], 31.); - + EXPECT_DOUBLE_EQ(climateOutput.monthlyPPT_cm[0][0], 31.); + // Average temperature of three driest month of first year - EXPECT_DOUBLE_EQ(meanTempDriestQuarter_C[0], 1.); - + EXPECT_DOUBLE_EQ(climateOutput.meanTempDriestQuarter_C[0], 1.); + // Average precipiation of first year of simulation - EXPECT_DOUBLE_EQ(annualPPT_cm[0], 366.); - + EXPECT_DOUBLE_EQ(climateOutput.annualPPT_cm[0], 366.); + // Average temperature of first year of simulation - EXPECT_DOUBLE_EQ(meanAnnualTemp_C[0], 1.); - + EXPECT_DOUBLE_EQ(climateOutput.meanAnnualTemp_C[0], 1.); + // First year's July minimum temperature - EXPECT_DOUBLE_EQ(JulyMinTemp[0], 1.); - + EXPECT_DOUBLE_EQ(climateOutput.JulyMinTemp[0], 1.); + // First year's number of most consecutive frost free days - EXPECT_DOUBLE_EQ(frostFreeDays_days[0], 366); - + EXPECT_DOUBLE_EQ(climateOutput.frostFreeDays_days[0], 366); + // Sum of all temperature above 65F (18.333C) in first year - EXPECT_DOUBLE_EQ(ddAbove65F_degday[0], 0); - + EXPECT_DOUBLE_EQ(climateOutput.ddAbove65F_degday[0], 0); + // Total precipitation in July of first year - EXPECT_DOUBLE_EQ(JulyPPT_mm[0], 310.); - + EXPECT_DOUBLE_EQ(climateOutput.JulyPPT_mm[0], 310.); + // Smallest temperature in all February first year - EXPECT_NEAR(minTempFebruary_C[0], 1., tol6); - + EXPECT_NEAR(climateOutput.minTempFebruary_C[0], 1., tol6); + // Start of nonleap year tests (startYear = 1981) - - calcSiteClimate(SW_Weather.allHist, 2, 1981, meanMonthlyTemp_C, maxMonthlyTemp_C, minMonthlyTemp_C, - monthlyPPT_cm, annualPPT_cm, meanAnnualTemp_C, JulyMinTemp, frostFreeDays_days, ddAbove65F_degday, - JulyPPT_mm, meanTempDriestQuarter_C, minTempFebruary_C); - + + calcSiteClimate(SW_Weather.allHist, 2, 1981, &climateOutput); + // Average of average temperature of January in 1981 - EXPECT_DOUBLE_EQ(meanMonthlyTemp_C[0][0], 1.); - + EXPECT_DOUBLE_EQ(climateOutput.meanMonthlyTemp_C[0][0], 1.); + // Average of max temperature in Januaray 1981 - EXPECT_DOUBLE_EQ(maxMonthlyTemp_C[0][0], 1.); - + EXPECT_DOUBLE_EQ(climateOutput.maxMonthlyTemp_C[0][0], 1.); + // Average of min temperature in Januaray 1981 - EXPECT_DOUBLE_EQ(minMonthlyTemp_C[0][0], 1.); - + EXPECT_DOUBLE_EQ(climateOutput.minMonthlyTemp_C[0][0], 1.); + // Average January precipitation in 1980 - EXPECT_DOUBLE_EQ(monthlyPPT_cm[0][0], 31.); - + EXPECT_DOUBLE_EQ(climateOutput.monthlyPPT_cm[0][0], 31.); + // Average temperature of three driest month of first year - EXPECT_DOUBLE_EQ(meanTempDriestQuarter_C[0], 1.); - + EXPECT_DOUBLE_EQ(climateOutput.meanTempDriestQuarter_C[0], 1.); + // Average precipiation of first year of simulation - EXPECT_DOUBLE_EQ(annualPPT_cm[0], 365.); - + EXPECT_DOUBLE_EQ(climateOutput.annualPPT_cm[0], 365.); + // Average temperature of first year of simulation - EXPECT_DOUBLE_EQ(meanAnnualTemp_C[0], 1.); - + EXPECT_DOUBLE_EQ(climateOutput.meanAnnualTemp_C[0], 1.); + // First year's July minimum temperature - EXPECT_DOUBLE_EQ(JulyMinTemp[0], 1.); - + EXPECT_DOUBLE_EQ(climateOutput.JulyMinTemp[0], 1.); + // First year's number of most consecutive frost free days - EXPECT_DOUBLE_EQ(frostFreeDays_days[0], 365); - + EXPECT_DOUBLE_EQ(climateOutput.frostFreeDays_days[0], 365); + // Sum of all temperature above 65F (18.333C) in first year - EXPECT_DOUBLE_EQ(ddAbove65F_degday[0], 0); - + EXPECT_DOUBLE_EQ(climateOutput.ddAbove65F_degday[0], 0); + // Total precipitation in July of first year - EXPECT_DOUBLE_EQ(JulyPPT_mm[0], 310.); - + EXPECT_DOUBLE_EQ(climateOutput.JulyPPT_mm[0], 310.); + // Smallest temperature in all February first year - EXPECT_NEAR(minTempFebruary_C[0], 1., tol6); - + EXPECT_NEAR(climateOutput.minTempFebruary_C[0], 1., tol6); + for(int month = 0; month < MAX_MONTHS; month++) { - delete[] monthlyPPT_cm[month]; - delete[] meanMonthlyTemp_C[month]; - delete[] minMonthlyTemp_C[month]; - delete[] maxMonthlyTemp_C[month]; + delete[] climateOutput.monthlyPPT_cm[month]; + delete[] climateOutput.meanMonthlyTemp_C[month]; + delete[] climateOutput.minMonthlyTemp_C[month]; + delete[] climateOutput.maxMonthlyTemp_C[month]; } - - delete[] monthlyPPT_cm; - delete[] meanMonthlyTemp_C; - delete[] minMonthlyTemp_C; - delete[] maxMonthlyTemp_C; - + + delete[] climateOutput.monthlyPPT_cm; + delete[] climateOutput.meanMonthlyTemp_C; + delete[] climateOutput.minMonthlyTemp_C; + delete[] climateOutput.maxMonthlyTemp_C; + + // Free rest of allocated memory + for(int index = 0; index < 9; index++) { + free(freeArray[index]); + } + } TEST(AverageTemperatureOfDriestQuarterTest, OneAndTwoYear) { - + 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 - + double **monthlyPPT_cm; monthlyPPT_cm = new double*[MAX_MONTHS]; - + double **meanMonthlyTemp_C = new double*[MAX_MONTHS]; - + for(int month = 0; month < MAX_MONTHS; month++) { monthlyPPT_cm[month] = new double[2]; meanMonthlyTemp_C[month] = new double[2]; for(int year = 0; year < 2; year++) { - + monthlyPPT_cm[month][year] = monthlyPPT[month]; meanMonthlyTemp_C[month][year] = monthlyTemp[month]; } } // 1980 is start year of the simulation findDriestQtr(result, 1, meanMonthlyTemp_C, monthlyPPT_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_DOUBLE_EQ(result[0], 1.4333333333333333); - + findDriestQtr(result, 2, meanMonthlyTemp_C, monthlyPPT_cm); - + EXPECT_DOUBLE_EQ(result[0], 1.4333333333333333); - + for(int month = 0; month < MAX_MONTHS; month++) { delete[] monthlyPPT_cm[month]; delete[] meanMonthlyTemp_C[month]; } - + delete[] monthlyPPT_cm; delete[] meanMonthlyTemp_C; - + } TEST(WeatherReadTest, Initialization) { - + SW_WTH_read(); - + EXPECT_FLOAT_EQ(SW_Weather.allHist[0]->temp_max[0], -.52); - + } From d4f03508846fd189cbb85a2affb27ebc47839e88 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 22 Jul 2022 08:31:07 -0400 Subject: [PATCH 112/326] New `allocateAllWeather()` to allocate memory for `allHist` * will be called by `rSOILWAT2` directly --- SW_Weather.c | 24 +++++++++++++++++------- SW_Weather.h | 1 + 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 5f66672ee..68e64156e 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -290,6 +290,22 @@ void SW_WTH_deconstruct(void) deallocateAllWeather(); } + +/** + @brief Allocate memory for `allHist` of `SW_Weather` based on `n_years` +*/ +void allocateAllWeather(void) { + unsigned int year; + + SW_Weather.allHist = (SW_WEATHER_HIST **)malloc(sizeof(SW_WEATHER_HIST *) * SW_Weather.n_years); + + for (year = 0; year < SW_Weather.n_years; year++) { + + SW_Weather.allHist[year] = (SW_WEATHER_HIST *)malloc(sizeof(SW_WEATHER_HIST)); + } +} + + /** @brief Helper function to SW_WTH_deconstruct to deallocate allHist array. */ @@ -490,15 +506,9 @@ void SW_WTH_read(void) { #else SW_Weather.n_years = SW_Model.endyr - SW_Model.startyr + 1; #endif - unsigned int year; // Allocate new `allHist` (based on current `SW_Weather.n_years`) - SW_Weather.allHist = (SW_WEATHER_HIST **)malloc(sizeof(SW_WEATHER_HIST *) * SW_Weather.n_years); - - for(year = 0; year < SW_Weather.n_years; year++) { - - SW_Weather.allHist[year] = (SW_WEATHER_HIST *)malloc(sizeof(SW_WEATHER_HIST)); - } + allocateAllWeather(); readAllWeather(SW_Weather.allHist, SW_Model.startyr, SW_Weather.n_years); diff --git a/SW_Weather.h b/SW_Weather.h index 8a50be8be..a55dd520a 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -106,6 +106,7 @@ void SW_WTH_setup(void); void SW_WTH_read(void); Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather); void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, unsigned int n_years); +void allocateAllWeather(void); void deallocateAllWeather(void); void _clear_hist_weather(SW_WEATHER_HIST *yearWeather); void SW_WTH_init_run(void); From 5768a803adfd1254c2679587cae0b149c398a2c1 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 22 Jul 2022 08:54:05 -0400 Subject: [PATCH 113/326] Split `readAllWeather()` into read-impute-scale - `readAllWeather()` now only obtains data from file * previously, `readAllWeather()` was looping through each day to (1) obtain data from file, (2) impute missing values (e.g., using the weather generator, and (3) apply additive/multiplicative scaling parameters * previously, incorrect values were produced if scaling parameters and weather generator were both active (scaling must occur after the weather generator, but the previous version applied the scaling to yesterday's precipitation which was used as input to the weather generator) --> the new version fixes this by separating the steps - new `imputeMissingWeather()` uses the weather generator or last-value-carried forward imputation (now, with a cap) to impute missing values - new `scaleAllWeather()` applies additive/multiplicative scaling parameters * `rSOILWAT2` will utilize `imputeMissingWeather()` and `scaleAllWeather()` directly --- SW_Weather.c | 376 +++++++++++++++++++++++++++++++++++---------------- SW_Weather.h | 15 ++ 2 files changed, 278 insertions(+), 113 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 68e64156e..355e8d7cc 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -97,129 +97,253 @@ static void _update_yesterday(void) { wn->rain[Yesterday] = wn->rain[Today]; } + + +/* =================================================== */ +/* Global Function Definitions */ +/* --------------------------------------------------- */ + + + /** @brief Reads in all weather data through all years and stores them in global SW_Weather's `allHist` - - 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 - + @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 - - @note Function requires SW_MKV_today, SW_Weather.scale_temp_max and SW_Weather.scale_temp_min - - */ +*/ void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, unsigned int n_years) { - - int monthDays, month, currentMonDays, year; + + int year; unsigned int yearIndex, numDaysYear, day; - - double yesterdayPPT = 0., yesterdayMin = 0., yesterdayMax = 0.; - - Bool weth_found = swFALSE, no_missing = swTRUE; - + + Bool weth_found = swFALSE; + for(yearIndex = 0; yearIndex < n_years; yearIndex++) { - year = yearIndex + startYear; - Time_new_year(year); - numDaysYear = Time_get_lastdoy_y(year); - month = Jan; - monthDays = Time_days_in_month(month); - currentMonDays = 0; - - if(!SW_Weather.use_weathergenerator_only) { + + if(SW_Weather.use_weathergenerator_only) { + // Set to missing for call to `imputeMissingWeather()` + _clear_hist_weather(allHist[yearIndex]); + + } else { + year = yearIndex + startYear; + weth_found = _read_weather_hist(year, allHist[yearIndex]); - } - for(day = 0; day < numDaysYear; day++) { - if(currentMonDays == monthDays) { - month++; - monthDays = Time_days_in_month(month % 12); - currentMonDays = 0; - } - currentMonDays++; - /* --------------------------------------------------- */ - /* 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. - */ - if (!weth_found) { - if(SW_Weather.use_weathergenerator) { - // no weather input file for current year ==> use 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 { - LogError(logfp, LOGWARN, "Weather was not found and user specification " - "for using weather generator is off. Cannot generate " - "weather for day %d of year %d\n", day, year); - } - - } else { - // weather input file for current year available - - no_missing = (Bool) (!missing(allHist[yearIndex]->temp_max[day]) && - !missing(allHist[yearIndex]->temp_min[day]) && - !missing(allHist[yearIndex]->ppt[day])); - if(!no_missing) { - // some of today's values are missing - - if (SW_Weather.use_weathergenerator) { - // if weather generator is turned on then use it for all values - allHist[yearIndex]->ppt[day] = yesterdayPPT; - SW_MKV_today(day, &allHist[yearIndex]->temp_max[day], - &allHist[yearIndex]->temp_min[day], &allHist[yearIndex]->ppt[day]); - - } else { - // impute missing values with 0 for precipitation and - // with LOCF for temperature (i.e., last-observation-carried-forward) - allHist[yearIndex]->temp_max[day] = (!missing(allHist[yearIndex]->temp_max[day])) ? - allHist[yearIndex]->temp_max[day] : yesterdayMax; - - allHist[yearIndex]->temp_min[day] = (!missing(allHist[yearIndex]->temp_min[day])) ? - allHist[yearIndex]->temp_min[day] : yesterdayMin; - - allHist[yearIndex]->ppt[day] = (!missing(allHist[yearIndex]->ppt[day])) ? - allHist[yearIndex]->ppt[day] : 0.; - } - } + + // Calculate average air temperature + if (weth_found) { + Time_new_year(year); + numDaysYear = Time_get_lastdoy_y(year); + + for (day = 0; day < numDaysYear; day++) { + 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.; + } + } } - /* scale the weather according to monthly factors */ - allHist[yearIndex]->temp_max[day] += SW_Weather.scale_temp_max[month]; - allHist[yearIndex]->temp_min[day] += SW_Weather.scale_temp_min[month]; - allHist[yearIndex]->temp_avg[day] = (allHist[yearIndex]->temp_max[day] + - allHist[yearIndex]->temp_min[day]) / 2.; - - allHist[yearIndex]->ppt[day] *= SW_Weather.scale_precip[month]; - - yesterdayPPT = allHist[yearIndex]->ppt[day]; - yesterdayMax = allHist[yearIndex]->temp_max[day]; - yesterdayMin = allHist[yearIndex]->temp_min[day]; } - } } -/* =================================================== */ -/* Global Function Definitions */ -/* --------------------------------------------------- */ +/** + @brief Apply temperature and precipitation 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 [-] + + @note Daily average air temperature is re-calculated after scaling + minimum and maximum air temperature. + + @note `scaleAllWeather()` assumes that `allHist` is free of missing values. +*/ +void scaleAllWeather( + SW_WEATHER_HIST **allHist, + int startYear, + unsigned int n_years, + double *scale_temp_max, + double *scale_temp_min, + double *scale_precip +) { + + 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); + numDaysYear = Time_get_lastdoy_y(year); + + for (day = 0; day < numDaysYear; day++) { + month = doy2month(day + 1); + + /* scale weather with monthly factors */ + allHist[yearIndex]->temp_max[day] += scale_temp_max[month]; + allHist[yearIndex]->temp_min[day] += scale_temp_min[month]; + allHist[yearIndex]->ppt[day] *= scale_precip[month]; + + /* re-calculate average air temperature */ + allHist[yearIndex]->temp_avg[day] = + (allHist[yearIndex]->temp_max[day] + allHist[yearIndex]->temp_min[day]) / 2.; + } + } + } +} + + +/** + @brief Impute missing weather values + + 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 (values correspond to missing) + 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 used 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 + + If the weather generator is turned off, then up to five days per calendar + year are imputed + - precipitation is set to `0` + - minimum and maximum temperature are imputed based on `LOCF` + (last value carried forward) + + If more than `nMaxLOCF` days per calendar year are missing and the weather + generator is turned off, then an error is thrown. + + @note `SW_MKV_today()` is called if `useWeatherGenerator` + which 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] useWeatherGenerator Impute by weather generator (if `TRUE`) + or by `LOCF` (for temperature) and zeros (for precipitation) (if `FALSE`). + @param[in] nMaxLOCF Maximum number of missing days per year (e.g., 5) + before imputation by `LOCF` throws an error. +*/ +void imputeMissingWeather( + SW_WEATHER_HIST **allHist, + int startYear, + unsigned int n_years, + Bool useWeatherGenerator, + unsigned int nMaxLOCF +) { + + int year; + unsigned int yearIndex, numDaysYear, day, iMissing; + + double yesterdayPPT = 0., yesterdayMin = 0., yesterdayMax = 0.; + + Bool any_missing, missing_Tmax, missing_Tmin, missing_PPT; + + + for (yearIndex = 0; yearIndex < n_years; yearIndex++) { + year = yearIndex + startYear; + Time_new_year(year); + 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]); + + any_missing = (Bool) (missing_Tmax || missing_Tmin || missing_PPT); + + if (any_missing) { + // some of today's values are missing + + if (useWeatherGenerator) { + // if weather generator is turned on then use it for all values + allHist[yearIndex]->ppt[day] = yesterdayPPT; + SW_MKV_today( + day, + &allHist[yearIndex]->temp_max[day], + &allHist[yearIndex]->temp_min[day], + &allHist[yearIndex]->ppt[day] + ); + + } else { + // impute missing temperature based on + // LOCF (last-observation-carried-forward) + allHist[yearIndex]->temp_max[day] = missing_Tmax ? + yesterdayMax : + allHist[yearIndex]->temp_max[day]; + + allHist[yearIndex]->temp_min[day] = missing_Tmin ? + yesterdayMin : + allHist[yearIndex]->temp_min[day]; + + // impute missing precipitation with 0 + 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 > nMaxLOCF) { + LogError( + logfp, + LOGFATAL, + "imputeMissingWeather(): more than %d days missing in year %d " + "and weather generator turned off.\n", + nMaxLOCF, + 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]; + yesterdayMax = allHist[yearIndex]->temp_max[day]; + yesterdayMin = allHist[yearIndex]->temp_min[day]; + } + } +} /** @@ -230,8 +354,12 @@ void _clear_hist_weather(SW_WEATHER_HIST *yearWeather) { /* --------------------------------------------------- */ TimeInt d; - for (d = 0; d < MAX_DAYS; d++) - yearWeather->ppt[d] = yearWeather->temp_max[d] = yearWeather->temp_min[d] = SW_MISSING; + 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; + } } @@ -312,16 +440,16 @@ void allocateAllWeather(void) { void deallocateAllWeather(void) { unsigned int year; - + if(!isnull(SW_Weather.allHist)) { for(year = 0; year < SW_Weather.n_years; year++) { free(SW_Weather.allHist[year]); } - + free(SW_Weather.allHist); SW_Weather.allHist = NULL; } - + } /** @@ -510,8 +638,30 @@ void SW_WTH_read(void) { // Allocate new `allHist` (based on current `SW_Weather.n_years`) allocateAllWeather(); + + // Read daily meteorological input from disk readAllWeather(SW_Weather.allHist, SW_Model.startyr, SW_Weather.n_years); - + + + // Impute missing values + imputeMissingWeather( + SW_Weather.allHist, + SW_Model.startyr, + SW_Weather.n_years, + (Bool) SW_Weather.use_weathergenerator, + 3 // nMaxLOCF + ); + + + // Scale with monthly additive/multiplicative parameters + scaleAllWeather( + SW_Weather.allHist, + SW_Model.startyr, + SW_Weather.n_years, + SW_Weather.scale_temp_max, + SW_Weather.scale_temp_min, + SW_Weather.scale_precip + ); } diff --git a/SW_Weather.h b/SW_Weather.h index a55dd520a..c7c4ca361 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -106,6 +106,21 @@ void SW_WTH_setup(void); void SW_WTH_read(void); Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather); void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, unsigned int n_years); +void scaleAllWeather( + SW_WEATHER_HIST **allHist, + int startYear, + unsigned int n_years, + double *scale_temp_max, + double *scale_temp_min, + double *scale_precip +); +void imputeMissingWeather( + SW_WEATHER_HIST **allHist, + int startYear, + unsigned int n_years, + Bool useWeatherGenerator, + unsigned int nMaxLOCF +); void allocateAllWeather(void); void deallocateAllWeather(void); void _clear_hist_weather(SW_WEATHER_HIST *yearWeather); From 148a7caba59905bd25efe7c13a2e6ccb26864446 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 22 Jul 2022 08:56:49 -0400 Subject: [PATCH 114/326] Update `CheckMissingForMissingYear` for new `imputeMissingWeather()` - `SW_WTH_read()` - which calls `imputeMissingWeather()` - no longer passes through missing weather values if weather generator is turned off completely; instead an error is thrown that `nMaxLOCF` is reached -> convert to death test --- test/test_SW_Weather.cc | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 546c6f5b6..cd916cef9 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -127,24 +127,22 @@ namespace { } - TEST(ReadAllWeatherTest, CheckMissingForMissingYear) { + TEST(ReadAllWeatherDeathTest, TooManyMissingAndNoWeatherGenerator) { - int day; - // Change directory to get input files with some missing data + // Change to directory without input files strcpy(SW_Weather.name_prefix, "Input/data_weather_nonexisting/weath"); SW_Weather.use_weathergenerator = swFALSE; SW_Weather.use_weathergenerator_only = swFALSE; - + SW_Model.startyr = 1981; SW_Model.endyr = 1981; - - SW_WTH_read(); - // Check everyday's value and test if it's `MISSING` - for(day = 0; day < 365; day++) { - EXPECT_TRUE(missing(SW_Weather.allHist[0]->temp_max[day])); - } + // Error: too many missing values and weather generator turned off + EXPECT_DEATH_IF_SUPPORTED( + SW_WTH_read(), + "" + ); Reset_SOILWAT2_after_UnitTest(); From a40886adfdadabe729cdcc2413462daee3f035d0 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 22 Jul 2022 12:21:59 -0400 Subject: [PATCH 115/326] Added documentation for new weather related structs --- SW_Weather.h | 46 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/SW_Weather.h b/SW_Weather.h index 4f3b53edf..e0a91b3ee 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -57,16 +57,50 @@ typedef struct { snowRunoff, surfaceRunoff, surfaceRunon, soil_inf, et, aet, pet, surfaceAvg, surfaceMax, surfaceMin; } SW_WEATHER_OUTPUTS; +/** + A structure holding all variables that are output to the function `calcSiteClimate()` and is input to the function `averageClimateAcrossYears()` + + @note Arrays are to be of size `numYears` which is variable (31 years when testing) + @note Arrays hold values for all simulation years + */ 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; + RealD **meanMonthlyTemp_C, /**< 2D array containing monthly mean average daily air temperature (deg;C)*/ + **maxMonthlyTemp_C, /**< 2D array containing monthly mean max daily air temperature (deg;C)*/ + **minMonthlyTemp_C, /**< 2D array containing monthly mean min daily air temperature (deg;C)*/ + **monthlyPPT_cm, /**< 2D array containing monthly amount precipitation (cm)*/ + *annualPPT_cm, /**< Array containing annual precipitation amount [cm]*/ + *meanAnnualTemp_C, /**< Array containing annual mean temperatures [C]*/ + *JulyMinTemp, /**< Array containing minimum July temperatures [C] */ + *frostFreeDays_days, /**< Array containing the maximum consecutive days in a year without frost*/ + *ddAbove65F_degday, /**< Array containing the amount of degree days [C x day] above 65 F */ + *JulyPPT_mm, /**< Array containing July precipitation amount (mm) for all years */ + *meanTempDriestQuarter_C, /**< Array containing the average temperature of the driest quarter of the year*/ + *minTempFebruary_C; /**< Array containing the mean minimum temperature in february*/ } SW_CLIMATE_CALC; +/** + A structure holding all variables that are output to the function `averageClimateAcrossYears()` + + @note If a description mentions a size, it is not variable and has to be the specified size + @note Averages are taken across all simulation years + */ typedef struct { - RealD *meanMonthlyTempAnn, *maxMonthlyTempAnn, *minMonthlyTempAnn, *meanMonthlyPPTAnn, - *sdC4, *sdCheatgrass, MAT_C, MAP_cm, JulyPPTAnn_mm, meanTempDriestQuarterAnn_C, minTempFebruaryAnn_C, - ddAbove65F_degdayAnn, frostFreeAnn, JulyMinTempAnn; + RealD *meanMonthlyTempAnn, /**< Array of size MAX_MONTHS containing sum of monthly mean temperatures*/ + *maxMonthlyTempAnn, /**< Array of size MAX_MONTHS containing sum of monthly maximum temperatures*/ + *minMonthlyTempAnn, /**< Array of size MAX_MONTHS containing sum of monthly minimum temperatures*/ + *meanMonthlyPPTAnn, /**< Array of size MAX_MONTHS containing sum of monthly mean precipitation*/ + *sdC4, /**< Array of size three holding the standard deviations of minimum July temperature (0), + frost free days (1), number of days above 65F (2)*/ + *sdCheatgrass, /**< Array of size three holding the standard deviations of July precipitation (0), mean + temperature of dry quarter (1), mean minimum temperature of February (2)*/ + MAT_C, /**< Value containing the average of yearly temperatures*/ + MAP_cm, /**< Value containing the average of yearly precipitation*/ + JulyPPTAnn_mm, /**< Value containing average of July precipitation (mm)*/ + meanTempDriestQuarterAnn_C, /**< Value containing average of mean temperatures in the driest quarters of years*/ + minTempFebruaryAnn_C, /**< Value containing average of minimum temperatures in February*/ + ddAbove65F_degdayAnn, /**< Value containing average of total degrees above 65F (18.33C) throughout the year*/ + frostFreeAnn, /**< Value containing average of most consectutive days in a year without frost*/ + JulyMinTempAnn; /**< Value containing the average of lowest temperature in July*/ } SW_CLIMATE_AVERAGES; typedef struct { From 53a6608309fab2f4d78619dccda2c993d4b88973 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 22 Jul 2022 14:06:06 -0400 Subject: [PATCH 116/326] Clarified tests for functionality of climate variables * some re-grouping of existing tests and expectation with the intent to clarify checks and simplify maintenance * combined "CalcSiteClimateTest" and "AverageClimateAcrossYearsTest" into one test suite "ClimateVariableTest" with three tests "ClimateFromDefaultWeather", "ClimateFromOneYearWeather", "ClimateFromConstantWeather" -- because `averageClimateAcrossYears() cannot be used reasonably without `calcSiteClimate()` and because the expectations are very similar to each other * renamed "AverageTemperatureOfDriestQuarterTest.OneAndTwoYear" to "ClimateVariableTest.AverageTemperatureOfDriestQuarterTest" * removed code blocks that re-initialized `climateOutput` -- because `calcSiteClimate()` no longer depends on them being zeroed since commit 36a0cb4b114b2de32737618d72b7854e6cf72efa * replaced integers with months for clarity (e.g., `Jan` instead of 0, `Feb` instead of 1, etc.) * ClimateFromDefaultWeather ** added explanation why mean annual temperature differs slightly from rSOILWAT2 (which does long-term daily average instead of long-term annual average -- as done correctly here) * ClimateFromOneYearWeather ** added expectations to clarify that values aggregated across one year are identical to values from that one year * ClimateFromConstantWeather ** added some additional expectations to check for correct leap year averaging/summing ** allocate/deallocate memory for a local `allHist` variable to avoid modifying (which had required a call to `Reset_SOILWAT2_after_UnitTest()`) * AverageTemperatureOfDriestQuarterTest: ** added expectation for a case with more than one driest quarters (the first occurring driest quarter is used) --- test/test_SW_Weather.cc | 590 ++++++++++++++++++++++++---------------- 1 file changed, 352 insertions(+), 238 deletions(-) diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index eec833fbb..2c0194194 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -137,12 +137,14 @@ namespace { Reset_SOILWAT2_after_UnitTest(); } - TEST(AverageClimateAcrossYearsTest, FileAndValuesOfOne) { - + + TEST(ClimateVariableTest, ClimateFromDefaultWeather) { + // This test relies on allHist from `SW_WEATHER` being already filled SW_CLIMATE_CALC climateOutput; SW_CLIMATE_AVERAGES climateAverage; - + + // Allocate memory climateOutput.JulyMinTemp = new double[31]; // 31 = Number of years in the simulation climateOutput.annualPPT_cm = new double[31]; climateOutput.frostFreeDays_days = new double[31]; @@ -174,137 +176,259 @@ namespace { climateOutput.meanMonthlyTemp_C[month] = new double[31]; climateOutput.minMonthlyTemp_C[month] = new double[31]; climateOutput.maxMonthlyTemp_C[month] = new double[31]; - for(int year = 0; year < 31; year++) { - climateOutput.monthlyPPT_cm[month][year] = 0.; - climateOutput.meanMonthlyTemp_C[month][year] = 0.; - climateOutput.minMonthlyTemp_C[month][year] = 0.; - climateOutput.maxMonthlyTemp_C[month][year] = 0.; - } } - // 1980 is start year of the simulation + + + // ------ 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) + // ```{r} + // rSOILWAT2::calc_SiteClimate( + // weatherList = rSOILWAT2::get_WeatherHistory( + // rSOILWAT2::sw_exampleData + // )[1], + // do_C4vars = TRUE, + // do_Cheatgrass_ClimVars = TRUE + // ) + // ``` + calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput); + + EXPECT_NEAR(climateOutput.meanMonthlyTemp_C[Jan][0], -8.432581, tol6); + EXPECT_NEAR(climateOutput.maxMonthlyTemp_C[Jan][0], -2.562581, tol6); + EXPECT_NEAR(climateOutput.minMonthlyTemp_C[Jan][0], -14.302581, tol6); + EXPECT_NEAR(climateOutput.monthlyPPT_cm[Jan][0], 15.1400001, tol6); + EXPECT_NEAR(climateOutput.annualPPT_cm[0], 59.27, tol1); + EXPECT_NEAR(climateOutput.meanAnnualTemp_C[0], 4.524863, tol1); + + // Climate variables used for C4 grass cover + // (stdev of one value is undefined) + EXPECT_NEAR(climateOutput.JulyMinTemp[0], 2.809999, tol6); + EXPECT_NEAR(climateOutput.frostFreeDays_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.JulyPPT_mm[0], 18.299999, tol6); + EXPECT_NEAR(climateOutput.meanTempDriestQuarter_C[0], 0.936387, tol6); + EXPECT_NEAR(climateOutput.minTempFebruary_C[0], -12.822068, tol6); + + + // --- Long-term variables (aggregated across years) ------ + // Expect identical output to rSOILWAT2 (e.g., v5.3.1) + // ```{r} + // rSOILWAT2::calc_SiteClimate( + // weatherList = rSOILWAT2::get_WeatherHistory( + // rSOILWAT2::sw_exampleData + // ), + // do_C4vars = TRUE, + // do_Cheatgrass_ClimVars = TRUE + // ) + // ``` + averageClimateAcrossYears(&climateOutput, 31, &climateAverage); - - EXPECT_NEAR(climateAverage.meanMonthlyTempAnn[0], -9.325551, tol6); - EXPECT_NEAR(climateAverage.maxMonthlyTempAnn[0], -2.714381, tol6); - EXPECT_NEAR(climateAverage.minMonthlyTempAnn[0], -15.936722, tol6); - EXPECT_NEAR(climateAverage.meanMonthlyPPTAnn[0], 6.867419, tol6); - + + EXPECT_NEAR(climateAverage.meanMonthlyTempAnn[Jan], -9.325551, tol6); + EXPECT_NEAR(climateAverage.maxMonthlyTempAnn[Jan], -2.714381, tol6); + EXPECT_NEAR(climateAverage.minMonthlyTempAnn[Jan], -15.936722, tol6); + EXPECT_NEAR(climateAverage.meanMonthlyPPTAnn[Jan], 6.867419, tol6); + EXPECT_NEAR(climateAverage.MAP_cm, 62.817419, tol6); + // Note: rSOILWAT2 v5.3.1 returns incorrect MAT_C = 4.153896 + // which is long-term daily average (but not long-term annual average) EXPECT_NEAR(climateAverage.MAT_C, 4.154009, tol6); - EXPECT_NEAR(climateAverage.JulyPPTAnn_mm, 35.729032, tol6); - EXPECT_NEAR(climateAverage.meanTempDriestQuarterAnn_C, 11.524859, tol6); - EXPECT_NEAR(climateAverage.minTempFebruaryAnn_C, -13.904599, tol6); - EXPECT_NEAR(climateAverage.ddAbove65F_degdayAnn, 21.168032, tol6); - EXPECT_NEAR(climateAverage.frostFreeAnn, 90.612903, tol6); + + // Climate variables used for C4 grass cover EXPECT_NEAR(climateAverage.JulyMinTempAnn, 3.078387, tol6); - - // Standard deviation of C4 variables + EXPECT_NEAR(climateAverage.frostFreeAnn, 90.612903, tol6); + EXPECT_NEAR(climateAverage.ddAbove65F_degdayAnn, 21.168032, tol6); + EXPECT_NEAR(climateAverage.sdC4[0], 1.785535, tol6); EXPECT_NEAR(climateAverage.sdC4[1], 14.091788, tol6); EXPECT_NEAR(climateAverage.sdC4[2], 19.953560, tol6); - - // Standard deviation of cheatgrass variables + + // Climate variables used for cheatgrass cover + EXPECT_NEAR(climateAverage.JulyPPTAnn_mm, 35.729032, tol6); + EXPECT_NEAR(climateAverage.meanTempDriestQuarterAnn_C, 11.524859, tol6); + EXPECT_NEAR(climateAverage.minTempFebruaryAnn_C, -13.904599, tol6); + EXPECT_NEAR(climateAverage.sdCheatgrass[0], 21.598367, tol6); EXPECT_NEAR(climateAverage.sdCheatgrass[1], 7.171922, tol6); EXPECT_NEAR(climateAverage.sdCheatgrass[2], 2.618434, tol6); - - for(int year = 0; year < 31; year++) { - for(int month = 0; month < MAX_MONTHS; month++) { - climateOutput.monthlyPPT_cm[month][year] = 1.; - climateOutput.meanMonthlyTemp_C[month][year] = 1.; - climateOutput.minMonthlyTemp_C[month][year] = 1.; - climateOutput.maxMonthlyTemp_C[month][year] = 1.; - } - climateOutput.annualPPT_cm[year] = 1.; - climateOutput.meanAnnualTemp_C[year] = 1.; + + + // ------ Reset and deallocate + for(int month = 0; month < MAX_MONTHS; month++) { + delete[] climateOutput.monthlyPPT_cm[month]; + delete[] climateOutput.meanMonthlyTemp_C[month]; + delete[] climateOutput.minMonthlyTemp_C[month]; + delete[] climateOutput.maxMonthlyTemp_C[month]; } - - // Reset values - for(int year = 0; year < 31; year++) { - for(int month = 0; month < MAX_MONTHS; month++) { - climateOutput.monthlyPPT_cm[month][year] = 0.; - climateOutput.meanMonthlyTemp_C[month][year] = 0.; - climateOutput.minMonthlyTemp_C[month][year] = 0.; - climateOutput.maxMonthlyTemp_C[month][year] = 0.; - } - climateOutput.annualPPT_cm[year] = 0.; - climateOutput.meanAnnualTemp_C[year] = 0.; + + delete[] climateOutput.monthlyPPT_cm; + delete[] climateOutput.meanMonthlyTemp_C; + delete[] climateOutput.minMonthlyTemp_C; + delete[] climateOutput.maxMonthlyTemp_C; + + // Free rest of allocated memory + for(int index = 0; index < 14; index++) { + free(freeArray[index]); } - // Tests for one year of simulation + } + + + + TEST(ClimateVariableTest, ClimateFromOneYearWeather) { + + // This test relies on allHist from `SW_WEATHER` being already filled + SW_CLIMATE_CALC climateOutput; + SW_CLIMATE_AVERAGES climateAverage; + + // Allocate memory + climateOutput.JulyMinTemp = new double[1]; // 1 = Number of years in the simulation + climateOutput.annualPPT_cm = new double[1]; + climateOutput.frostFreeDays_days = new double[1]; + climateOutput.ddAbove65F_degday = new double[1]; + climateOutput.JulyPPT_mm = new double[1]; + climateOutput.meanTempDriestQuarter_C = new double[1]; + climateOutput.minTempFebruary_C = new double[1]; + climateOutput.meanAnnualTemp_C = new double[1]; + climateOutput.monthlyPPT_cm = new double*[MAX_MONTHS]; + climateOutput.meanMonthlyTemp_C = new double*[MAX_MONTHS]; + climateOutput.minMonthlyTemp_C = new double*[MAX_MONTHS]; + climateOutput.maxMonthlyTemp_C = new double*[MAX_MONTHS]; + + climateAverage.meanMonthlyTempAnn = new double[MAX_MONTHS]; + climateAverage.maxMonthlyTempAnn = new double[MAX_MONTHS]; + climateAverage.minMonthlyTempAnn = new double[MAX_MONTHS]; + climateAverage.meanMonthlyPPTAnn = new double[MAX_MONTHS]; + climateAverage.sdCheatgrass = new double[3]; + climateAverage.sdC4 = new double[3]; + + double *freeArray[14] = {climateOutput.JulyMinTemp, climateOutput.annualPPT_cm, + climateOutput.frostFreeDays_days, climateOutput.ddAbove65F_degday, climateOutput.JulyPPT_mm, + climateOutput.meanTempDriestQuarter_C, climateOutput.minTempFebruary_C, climateOutput.meanAnnualTemp_C, + climateAverage.meanMonthlyTempAnn, climateAverage.maxMonthlyTempAnn, climateAverage.minMonthlyTempAnn, + climateAverage.meanMonthlyPPTAnn, climateAverage.sdCheatgrass, climateAverage.sdC4}; + + for(int month = 0; month < MAX_MONTHS; month++) { + climateOutput.monthlyPPT_cm[month] = new double[1]; + climateOutput.meanMonthlyTemp_C[month] = new double[1]; + climateOutput.minMonthlyTemp_C[month] = new double[1]; + climateOutput.maxMonthlyTemp_C[month] = new double[1]; + } + + // ------ Check climate variables for one year of default weather ------ + + // Expect identical output to rSOILWAT2 (e.g., v5.3.1) + // ```{r} + // rSOILWAT2::calc_SiteClimate( + // weatherList = rSOILWAT2::get_WeatherHistory( + // rSOILWAT2::sw_exampleData + // )[1], + // do_C4vars = TRUE, + // do_Cheatgrass_ClimVars = TRUE + // ) + // ``` + calcSiteClimate(SW_Weather.allHist, 1, 1980, &climateOutput); - averageClimateAcrossYears(&climateOutput, 1, &climateAverage); - - EXPECT_NEAR(climateAverage.meanMonthlyTempAnn[0], -8.432581, tol6); - EXPECT_NEAR(climateAverage.maxMonthlyTempAnn[0], -2.562581, tol6); - EXPECT_NEAR(climateAverage.minMonthlyTempAnn[0], -14.302581, tol6); - EXPECT_NEAR(climateAverage.meanMonthlyPPTAnn[0], 15.1400001, tol6); + + // Expect that aggregated values across one year are identical + // to values of that one year + EXPECT_DOUBLE_EQ( + climateAverage.meanMonthlyTempAnn[Jan], + climateOutput.meanMonthlyTemp_C[Jan][0] + ); + EXPECT_DOUBLE_EQ( + climateAverage.maxMonthlyTempAnn[Jan], + climateOutput.maxMonthlyTemp_C[Jan][0] + ); + EXPECT_DOUBLE_EQ( + climateAverage.minMonthlyTempAnn[Jan], + climateOutput.minMonthlyTemp_C[Jan][0] + ); + EXPECT_DOUBLE_EQ( + climateAverage.meanMonthlyPPTAnn[Jan], + climateOutput.monthlyPPT_cm[Jan][0] + ); + EXPECT_DOUBLE_EQ( + climateAverage.MAP_cm, + climateOutput.annualPPT_cm[0] + ); + EXPECT_DOUBLE_EQ( + climateAverage.MAT_C, + climateOutput.meanAnnualTemp_C[0] + ); + + // Climate variables used for C4 grass cover + EXPECT_DOUBLE_EQ( + climateAverage.JulyMinTempAnn, + climateOutput.JulyMinTemp[0] + ); + EXPECT_DOUBLE_EQ( + climateAverage.frostFreeAnn, + climateOutput.frostFreeDays_days[0] + ); + EXPECT_DOUBLE_EQ( + climateAverage.ddAbove65F_degdayAnn, + climateOutput.ddAbove65F_degday[0] + ); + + // Climate variables used for cheatgrass cover + EXPECT_DOUBLE_EQ( + climateAverage.JulyPPTAnn_mm, + climateOutput.JulyPPT_mm[0] + ); + EXPECT_DOUBLE_EQ( + climateAverage.meanTempDriestQuarterAnn_C, + climateOutput.meanTempDriestQuarter_C[0] + ); + EXPECT_DOUBLE_EQ( + climateAverage.minTempFebruaryAnn_C, + climateOutput.minTempFebruary_C[0] + ); + + + EXPECT_NEAR(climateAverage.meanMonthlyTempAnn[Jan], -8.432581, tol6); + EXPECT_NEAR(climateAverage.maxMonthlyTempAnn[Jan], -2.562581, tol6); + EXPECT_NEAR(climateAverage.minMonthlyTempAnn[Jan], -14.302581, tol6); + EXPECT_NEAR(climateAverage.meanMonthlyPPTAnn[Jan], 15.1400001, tol6); EXPECT_NEAR(climateAverage.MAP_cm, 59.27, tol1); EXPECT_NEAR(climateAverage.MAT_C, 4.524863, tol1); - EXPECT_NEAR(climateAverage.JulyPPTAnn_mm, 18.299999, tol6); - EXPECT_NEAR(climateAverage.meanTempDriestQuarterAnn_C, 0.936387, tol6); - EXPECT_NEAR(climateAverage.minTempFebruaryAnn_C, -12.822068, tol6); - EXPECT_NEAR(climateAverage.ddAbove65F_degdayAnn, 13.546000, tol6); - EXPECT_NEAR(climateAverage.frostFreeAnn, 92, tol6); + + // Climate variables used for C4 grass cover + // (stdev of one value is undefined) EXPECT_NEAR(climateAverage.JulyMinTempAnn, 2.809999, tol6); - - // Standard deviation of C4 variables of one year + EXPECT_NEAR(climateAverage.frostFreeAnn, 92, tol6); + EXPECT_NEAR(climateAverage.ddAbove65F_degdayAnn, 13.546000, tol6); EXPECT_TRUE(isnan(climateAverage.sdC4[0])); EXPECT_TRUE(isnan(climateAverage.sdC4[1])); EXPECT_TRUE(isnan(climateAverage.sdC4[2])); - // Standard deviation of cheatgrass variables of one year + // Climate variables used for cheatgrass cover + // (stdev of one value is undefined) + EXPECT_NEAR(climateAverage.JulyPPTAnn_mm, 18.299999, tol6); + EXPECT_NEAR(climateAverage.meanTempDriestQuarterAnn_C, 0.936387, tol6); + EXPECT_NEAR(climateAverage.minTempFebruaryAnn_C, -12.822068, tol6); EXPECT_TRUE(isnan(climateAverage.sdCheatgrass[0])); EXPECT_TRUE(isnan(climateAverage.sdCheatgrass[1])); EXPECT_TRUE(isnan(climateAverage.sdCheatgrass[2])); - - for(int year = 0; year < 31; year++) { - for(int day = 0; day < 366; day++) { - SW_Weather.allHist[year]->temp_max[day] = 1.; - SW_Weather.allHist[year]->temp_min[day] = 1.; - SW_Weather.allHist[year]->temp_avg[day] = 1.; - SW_Weather.allHist[year]->ppt[day] = 1.; - } - } - // Start of tests with all `allHist` inputs of 1 - calcSiteClimate(SW_Weather.allHist, 2, 1980, &climateOutput); - - averageClimateAcrossYears(&climateOutput, 2, &climateAverage); - - EXPECT_DOUBLE_EQ(climateAverage.meanMonthlyTempAnn[0], 1.); - EXPECT_DOUBLE_EQ(climateAverage.maxMonthlyTempAnn[0], 1.); - EXPECT_DOUBLE_EQ(climateAverage.minMonthlyTempAnn[0], 1.); - EXPECT_DOUBLE_EQ(climateAverage.meanMonthlyPPTAnn[0], 31.); - EXPECT_DOUBLE_EQ(climateAverage.JulyPPTAnn_mm, 310.); - EXPECT_DOUBLE_EQ(climateAverage.meanTempDriestQuarterAnn_C, 1.); - EXPECT_DOUBLE_EQ(climateAverage.minTempFebruaryAnn_C, 1.); - EXPECT_DOUBLE_EQ(climateAverage.ddAbove65F_degdayAnn, 0.); - EXPECT_DOUBLE_EQ(climateAverage.frostFreeAnn, 365.5); - EXPECT_DOUBLE_EQ(climateAverage.JulyMinTempAnn, 1.); - // MAP_cm is expected to be 365.5 because we are running a leap year - // and nonleap year where the number of days average to 365.5 - EXPECT_DOUBLE_EQ(climateAverage.MAP_cm, 365.5); - EXPECT_DOUBLE_EQ(climateAverage.MAT_C, 1.); - - // Standard deviation of C4 variables of one year - EXPECT_DOUBLE_EQ(climateAverage.sdC4[0], 0.); - EXPECT_NEAR(climateAverage.sdC4[1], .7071067, tol6); - EXPECT_DOUBLE_EQ(climateAverage.sdC4[2], 0.); - // Standard deviation of cheatgrass variables of one year - EXPECT_DOUBLE_EQ(climateAverage.sdCheatgrass[0], 0.); - EXPECT_DOUBLE_EQ(climateAverage.sdCheatgrass[1], 0.); - EXPECT_DOUBLE_EQ(climateAverage.sdCheatgrass[2], 0.); - + // ------ Reset and deallocate for(int month = 0; month < MAX_MONTHS; month++) { delete[] climateOutput.monthlyPPT_cm[month]; delete[] climateOutput.meanMonthlyTemp_C[month]; delete[] climateOutput.minMonthlyTemp_C[month]; delete[] climateOutput.maxMonthlyTemp_C[month]; } - + delete[] climateOutput.monthlyPPT_cm; delete[] climateOutput.meanMonthlyTemp_C; delete[] climateOutput.minMonthlyTemp_C; @@ -314,175 +438,133 @@ namespace { for(int index = 0; index < 14; index++) { free(freeArray[index]); } - + } - TEST(CalcSiteClimateTest, FileAndValuesOfOne) { - // This test relies on allHist from `SW_WEATHER` being already filled + TEST(ClimateVariableTest, ClimateFromConstantWeather) { SW_CLIMATE_CALC climateOutput; - - climateOutput.JulyMinTemp = new double[31]; // 31 = Number of years in the simulation - climateOutput.annualPPT_cm = new double[31]; - climateOutput.frostFreeDays_days = new double[31]; - climateOutput.ddAbove65F_degday = new double[31]; - climateOutput.JulyPPT_mm = new double[31]; - climateOutput.meanTempDriestQuarter_C = new double[31]; - climateOutput.minTempFebruary_C = new double[31]; - climateOutput.meanAnnualTemp_C = new double[31]; + SW_CLIMATE_AVERAGES climateAverage; + SW_WEATHER_HIST **allHist; + + // Allocate memory + climateOutput.JulyMinTemp = new double[2]; + climateOutput.annualPPT_cm = new double[2]; + climateOutput.frostFreeDays_days = new double[2]; + climateOutput.ddAbove65F_degday = new double[2]; + climateOutput.JulyPPT_mm = new double[2]; + climateOutput.meanTempDriestQuarter_C = new double[2]; + climateOutput.minTempFebruary_C = new double[2]; + climateOutput.meanAnnualTemp_C = new double[2]; climateOutput.monthlyPPT_cm = new double*[MAX_MONTHS]; climateOutput.meanMonthlyTemp_C = new double*[MAX_MONTHS]; climateOutput.minMonthlyTemp_C = new double*[MAX_MONTHS]; climateOutput.maxMonthlyTemp_C = new double*[MAX_MONTHS]; - - double *freeArray[9] = {climateOutput.JulyMinTemp, climateOutput.annualPPT_cm, - climateOutput.frostFreeDays_days, climateOutput.ddAbove65F_degday, climateOutput.JulyPPT_mm, - climateOutput.meanTempDriestQuarter_C, climateOutput.minTempFebruary_C, climateOutput.meanAnnualTemp_C}; - for(int month = 0; month < MAX_MONTHS; month++) { - climateOutput.monthlyPPT_cm[month] = new double[31]; - climateOutput.meanMonthlyTemp_C[month] = new double[31]; - climateOutput.minMonthlyTemp_C[month] = new double[31]; - climateOutput.maxMonthlyTemp_C[month] = new double[31]; - for(int year = 0; year < 31; year++) { - climateOutput.monthlyPPT_cm[month][year] = 0.; - climateOutput.meanMonthlyTemp_C[month][year] = 0.; - climateOutput.minMonthlyTemp_C[month][year] = 0.; - climateOutput.maxMonthlyTemp_C[month][year] = 0.; - } - } - - SW_WTH_read(); - - // 1980 is start year of the simulation - calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput); - - // Average of average temperature of January in 1980 - EXPECT_NEAR(climateOutput.meanMonthlyTemp_C[0][0], -8.432581, tol6); - - // Average of max temperature in Januaray 1980 - EXPECT_NEAR(climateOutput.maxMonthlyTemp_C[0][0], -2.562581, tol6); - - // Average of min temperature in Januaray 1980 - EXPECT_NEAR(climateOutput.minMonthlyTemp_C[0][0], -14.302581, tol6); - - // Average January precipitation in 1980 - EXPECT_NEAR(climateOutput.monthlyPPT_cm[0][0], 15.14, tol6); - - // Average temperature of three driest month of first year - EXPECT_NEAR(climateOutput.meanTempDriestQuarter_C[0], .936387, tol6); + climateAverage.meanMonthlyTempAnn = new double[MAX_MONTHS]; + climateAverage.maxMonthlyTempAnn = new double[MAX_MONTHS]; + climateAverage.minMonthlyTempAnn = new double[MAX_MONTHS]; + climateAverage.meanMonthlyPPTAnn = new double[MAX_MONTHS]; + climateAverage.sdCheatgrass = new double[3]; + climateAverage.sdC4 = new double[3]; - // Average precipiation of first year of simulation - EXPECT_NEAR(climateOutput.annualPPT_cm[0], 59.27, tol6); + double *freeArray[14] = {climateOutput.JulyMinTemp, climateOutput.annualPPT_cm, + climateOutput.frostFreeDays_days, climateOutput.ddAbove65F_degday, climateOutput.JulyPPT_mm, + climateOutput.meanTempDriestQuarter_C, climateOutput.minTempFebruary_C, climateOutput.meanAnnualTemp_C, + climateAverage.meanMonthlyTempAnn, climateAverage.maxMonthlyTempAnn, climateAverage.minMonthlyTempAnn, + climateAverage.meanMonthlyPPTAnn, climateAverage.sdCheatgrass, climateAverage.sdC4}; - // Average temperature of first year of simulation - EXPECT_NEAR(climateOutput.meanAnnualTemp_C[0], 4.5248633, tol6); + for(int month = 0; month < MAX_MONTHS; month++) { + climateOutput.monthlyPPT_cm[month] = new double[2]; + climateOutput.meanMonthlyTemp_C[month] = new double[2]; + climateOutput.minMonthlyTemp_C[month] = new double[2]; + climateOutput.maxMonthlyTemp_C[month] = new double[2]; + } - // First year's July minimum temperature - EXPECT_NEAR(climateOutput.JulyMinTemp[0], 2.810000, tol6); + allHist = (SW_WEATHER_HIST **)malloc(sizeof(SW_WEATHER_HIST *) * 2); - // First year's number of most consecutive frost free days - EXPECT_EQ(climateOutput.frostFreeDays_days[0], 92); + for (int year = 0; year < 2; year++) { + allHist[year] = (SW_WEATHER_HIST *)malloc(sizeof(SW_WEATHER_HIST)); + } - // Sum of all temperature above 65F (18.333C) in first year - EXPECT_NEAR(climateOutput.ddAbove65F_degday[0], 13.546000, tol6); - // Total precipitation in July of first year - EXPECT_NEAR(climateOutput.JulyPPT_mm[0], 18.300000, tol6); + // ------ Check climate variables for constant weather = 1 ------ + // Years 1980 (leap) + 1981 (nonleap) - // Smallest temperature in all February first year - EXPECT_NEAR(climateOutput.minTempFebruary_C[0], -12.822069, tol6); + // 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++) { - SW_Weather.allHist[year]->temp_max[day] = 1.; - SW_Weather.allHist[year]->temp_min[day] = 1.; - SW_Weather.allHist[year]->temp_avg[day] = 1.; - SW_Weather.allHist[year]->ppt[day] = 1.; + allHist[year]->temp_max[day] = 1.; + allHist[year]->temp_min[day] = 1.; + allHist[year]->temp_avg[day] = 1.; + allHist[year]->ppt[day] = 1.; } } - calcSiteClimate(SW_Weather.allHist, 2, 1980, &climateOutput); - - // Start of leap year tests (startYear = 1980) - - // Average of average temperature of January in 1980 - EXPECT_DOUBLE_EQ(climateOutput.meanMonthlyTemp_C[0][0], 1.); + // --- Annual time-series of climate variables ------ + calcSiteClimate(allHist, 2, 1980, &climateOutput); - // Average of max temperature in Januaray 1980 - EXPECT_DOUBLE_EQ(climateOutput.maxMonthlyTemp_C[0][0], 1.); - - // Average of min temperature in Januaray 1980 - EXPECT_DOUBLE_EQ(climateOutput.minMonthlyTemp_C[0][0], 1.); - - // Average January precipitation in 1980 - EXPECT_DOUBLE_EQ(climateOutput.monthlyPPT_cm[0][0], 31.); - - // Average temperature of three driest month of first year - EXPECT_DOUBLE_EQ(climateOutput.meanTempDriestQuarter_C[0], 1.); - - // Average precipiation of first year of simulation + EXPECT_DOUBLE_EQ(climateOutput.meanMonthlyTemp_C[Jan][0], 1.); + EXPECT_DOUBLE_EQ(climateOutput.maxMonthlyTemp_C[Jan][0], 1.); + EXPECT_DOUBLE_EQ(climateOutput.minMonthlyTemp_C[Jan][0], 1.); + EXPECT_DOUBLE_EQ(climateOutput.monthlyPPT_cm[Jan][0], 31.); + EXPECT_DOUBLE_EQ(climateOutput.monthlyPPT_cm[Feb][0], 29.); + EXPECT_DOUBLE_EQ(climateOutput.monthlyPPT_cm[Feb][1], 28.); EXPECT_DOUBLE_EQ(climateOutput.annualPPT_cm[0], 366.); - - // Average temperature of first year of simulation + EXPECT_DOUBLE_EQ(climateOutput.annualPPT_cm[1], 365.); EXPECT_DOUBLE_EQ(climateOutput.meanAnnualTemp_C[0], 1.); - // First year's July minimum temperature + // Climate variables used for C4 grass cover + // (stdev of one value is undefined) EXPECT_DOUBLE_EQ(climateOutput.JulyMinTemp[0], 1.); + EXPECT_DOUBLE_EQ(climateOutput.frostFreeDays_days[0], 366.); + EXPECT_DOUBLE_EQ(climateOutput.frostFreeDays_days[1], 365.); + EXPECT_DOUBLE_EQ(climateOutput.ddAbove65F_degday[0], 0.); - // First year's number of most consecutive frost free days - EXPECT_DOUBLE_EQ(climateOutput.frostFreeDays_days[0], 366); - // Sum of all temperature above 65F (18.333C) in first year - EXPECT_DOUBLE_EQ(climateOutput.ddAbove65F_degday[0], 0); - - // Total precipitation in July of first year + // Climate variables used for cheatgrass cover + // (stdev of one value is undefined) EXPECT_DOUBLE_EQ(climateOutput.JulyPPT_mm[0], 310.); - - // Smallest temperature in all February first year - EXPECT_NEAR(climateOutput.minTempFebruary_C[0], 1., tol6); - - // Start of nonleap year tests (startYear = 1981) - - calcSiteClimate(SW_Weather.allHist, 2, 1981, &climateOutput); - - // Average of average temperature of January in 1981 - EXPECT_DOUBLE_EQ(climateOutput.meanMonthlyTemp_C[0][0], 1.); - - // Average of max temperature in Januaray 1981 - EXPECT_DOUBLE_EQ(climateOutput.maxMonthlyTemp_C[0][0], 1.); - - // Average of min temperature in Januaray 1981 - EXPECT_DOUBLE_EQ(climateOutput.minMonthlyTemp_C[0][0], 1.); - - // Average January precipitation in 1980 - EXPECT_DOUBLE_EQ(climateOutput.monthlyPPT_cm[0][0], 31.); - - // Average temperature of three driest month of first year EXPECT_DOUBLE_EQ(climateOutput.meanTempDriestQuarter_C[0], 1.); + EXPECT_DOUBLE_EQ(climateOutput.minTempFebruary_C[0], 1.); - // Average precipiation of first year of simulation - EXPECT_DOUBLE_EQ(climateOutput.annualPPT_cm[0], 365.); - // Average temperature of first year of simulation - EXPECT_DOUBLE_EQ(climateOutput.meanAnnualTemp_C[0], 1.); - - // First year's July minimum temperature - EXPECT_DOUBLE_EQ(climateOutput.JulyMinTemp[0], 1.); + // --- Long-term variables (aggregated across years) ------ + averageClimateAcrossYears(&climateOutput, 2, &climateAverage); - // First year's number of most consecutive frost free days - EXPECT_DOUBLE_EQ(climateOutput.frostFreeDays_days[0], 365); + EXPECT_DOUBLE_EQ(climateAverage.meanMonthlyTempAnn[Jan], 1.); + EXPECT_DOUBLE_EQ(climateAverage.maxMonthlyTempAnn[Jan], 1.); + EXPECT_DOUBLE_EQ(climateAverage.minMonthlyTempAnn[Jan], 1.); + EXPECT_DOUBLE_EQ(climateAverage.meanMonthlyPPTAnn[Jan], 31.); + EXPECT_DOUBLE_EQ(climateAverage.meanMonthlyPPTAnn[Feb], 28.5); + EXPECT_DOUBLE_EQ(climateAverage.meanMonthlyPPTAnn[Dec], 31); + EXPECT_DOUBLE_EQ(climateAverage.MAP_cm, 365.5); + EXPECT_DOUBLE_EQ(climateAverage.MAT_C, 1.); - // Sum of all temperature above 65F (18.333C) in first year - EXPECT_DOUBLE_EQ(climateOutput.ddAbove65F_degday[0], 0); + // Climate variables used for C4 grass cover + EXPECT_DOUBLE_EQ(climateAverage.JulyMinTempAnn, 1.); + EXPECT_DOUBLE_EQ(climateAverage.frostFreeAnn, 365.5); + EXPECT_DOUBLE_EQ(climateAverage.ddAbove65F_degdayAnn, 0.); + EXPECT_DOUBLE_EQ(climateAverage.sdC4[0], 0.); + EXPECT_NEAR(climateAverage.sdC4[1], .7071067, tol6); // sd(366, 365) + EXPECT_DOUBLE_EQ(climateAverage.sdC4[2], 0.); - // Total precipitation in July of first year - EXPECT_DOUBLE_EQ(climateOutput.JulyPPT_mm[0], 310.); + // Climate variables used for cheatgrass cover + EXPECT_DOUBLE_EQ(climateAverage.JulyPPTAnn_mm, 310.); + EXPECT_DOUBLE_EQ(climateAverage.meanTempDriestQuarterAnn_C, 1.); + EXPECT_DOUBLE_EQ(climateAverage.minTempFebruaryAnn_C, 1.); + EXPECT_DOUBLE_EQ(climateAverage.sdCheatgrass[0], 0.); + EXPECT_DOUBLE_EQ(climateAverage.sdCheatgrass[1], 0.); + EXPECT_DOUBLE_EQ(climateAverage.sdCheatgrass[2], 0.); - // Smallest temperature in all February first year - EXPECT_NEAR(climateOutput.minTempFebruary_C[0], 1., tol6); + // ------ Reset and deallocate for(int month = 0; month < MAX_MONTHS; month++) { delete[] climateOutput.monthlyPPT_cm[month]; delete[] climateOutput.meanMonthlyTemp_C[month]; @@ -494,45 +576,77 @@ namespace { delete[] climateOutput.meanMonthlyTemp_C; delete[] climateOutput.minMonthlyTemp_C; delete[] climateOutput.maxMonthlyTemp_C; - + // Free rest of allocated memory - for(int index = 0; index < 9; index++) { + for(int index = 0; index < 14; index++) { free(freeArray[index]); } + for (int year = 0; year < 2; year++) { + free(allHist[year]); + } + free(allHist); + } - TEST(AverageTemperatureOfDriestQuarterTest, OneAndTwoYear) { + + 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 **monthlyPPT_cm; monthlyPPT_cm = new double*[MAX_MONTHS]; double **meanMonthlyTemp_C = new double*[MAX_MONTHS]; - for(int month = 0; month < MAX_MONTHS; month++) { + for(month = 0; month < MAX_MONTHS; month++) { monthlyPPT_cm[month] = new double[2]; meanMonthlyTemp_C[month] = new double[2]; - for(int year = 0; year < 2; year++) { + for(year = 0; year < 2; year++) { monthlyPPT_cm[month][year] = monthlyPPT[month]; meanMonthlyTemp_C[month][year] = monthlyTemp[month]; } } - // 1980 is start year of the simulation + + + // ------ Test for one year ------ findDriestQtr(result, 1, meanMonthlyTemp_C, monthlyPPT_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_DOUBLE_EQ(result[0], 1.4333333333333333); + EXPECT_NEAR(result[0], 1.4333333333333333, tol9); + + // ------ Test for two years ------ findDriestQtr(result, 2, meanMonthlyTemp_C, monthlyPPT_cm); - EXPECT_DOUBLE_EQ(result[0], 1.4333333333333333); + 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++) { + monthlyPPT_cm[month][year] = 1.; + } + } + + findDriestQtr(result, 1, meanMonthlyTemp_C, monthlyPPT_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[] monthlyPPT_cm[month]; delete[] meanMonthlyTemp_C[month]; From dbc01f661a278cf8bd97d8be9bfbf8bee83fd35a Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Mon, 25 Jul 2022 23:30:29 -0400 Subject: [PATCH 117/326] Modified new structs description and names - Modified types of new structs as followed: * SW_CLIMATE_CALC -> SW_CLIMATE_YEARS * SW_CLIMATE_AVERAGES -> SW_CLIMATE_CLIM - Updated description of the two structs to be more specific/descriptive --- SW_Weather.c | 10 +++++----- SW_Weather.h | 26 +++++++++++++++----------- test/test_SW_Weather.cc | 12 ++++++------ 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index bf315bd16..0761f5ece 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -84,12 +84,12 @@ static char *MyFileName; @param[in] climateOutput Structure of type SW_CLIMATE_OUTPUT that holds all output from `calcSiteClimate()` @param[in] numYears Calendar year corresponding to first year of `allHist` - @param[out] climateAverages Structure of type SW_CLIMATE_AVERAGES that holds averages and + @param[out] climateAverages Structure of type SW_CLIMATE_CLIM that holds averages and standard deviations output by `averageClimateAcrossYears()` */ -void averageClimateAcrossYears(SW_CLIMATE_CALC *climateOutput, int numYears, - SW_CLIMATE_AVERAGES *climateAverages) { +void averageClimateAcrossYears(SW_CLIMATE_YEARLY *climateOutput, int numYears, + SW_CLIMATE_CLIM *climateAverages) { int month; @@ -126,12 +126,12 @@ void averageClimateAcrossYears(SW_CLIMATE_CALC *climateOutput, int numYears, @param[in] allHist Array containing all historical data of a site @param[in] numYears Number of years represented by `allHist` @param[in] startYear Calendar year corresponding to first year of `allHist` - @param[out] climateOutput Structure of type SW_CLIMATE_AVERAGES that holds averages and + @param[out] climateOutput Structure of type SW_CLIMATE_CLIM that holds averages and standard deviations output by `averageClimateAcrossYears()` */ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, - SW_CLIMATE_CALC *climateOutput) { + SW_CLIMATE_YEARLY *climateOutput) { int month, yearIndex, year, day, numDaysYear, numDaysMonth = Time_days_in_month(0), currMonDay; diff --git a/SW_Weather.h b/SW_Weather.h index e0a91b3ee..676f14468 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -58,10 +58,12 @@ typedef struct { } SW_WEATHER_OUTPUTS; /** - A structure holding all variables that are output to the function `calcSiteClimate()` and is input to the function `averageClimateAcrossYears()` + @brief Annual time-series of climate variables - @note Arrays are to be of size `numYears` which is variable (31 years when testing) - @note Arrays hold values for all simulation years + 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 **meanMonthlyTemp_C, /**< 2D array containing monthly mean average daily air temperature (deg;C)*/ @@ -79,10 +81,11 @@ typedef struct { } SW_CLIMATE_CALC; /** - A structure holding all variables that are output to the function `averageClimateAcrossYears()` + @brief A structure holding all variables that are output to the function `averageClimateAcrossYears()` #SW_CLIMATE_YEARLY - @note If a description mentions a size, it is not variable and has to be the specified size - @note Averages are taken across all simulation years + @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 *meanMonthlyTempAnn, /**< Array of size MAX_MONTHS containing sum of monthly mean temperatures*/ @@ -102,6 +105,7 @@ typedef struct { frostFreeAnn, /**< Value containing average of most consectutive days in a year without frost*/ JulyMinTempAnn; /**< Value containing the average of lowest temperature in July*/ } SW_CLIMATE_AVERAGES; +} SW_CLIMATE_CLIM; typedef struct { @@ -150,12 +154,12 @@ extern SW_WEATHER SW_Weather; void SW_WTH_setup(void); void SW_WTH_read(void); Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather); -void averageClimateAcrossYears(SW_CLIMATE_CALC *climateOutput, int numYears, - SW_CLIMATE_AVERAGES *climateAverages); +void averageClimateAcrossYears(SW_CLIMATE_YEARLY *climateOutput, int numYears, + SW_CLIMATE_CLIM *climateAverages); void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, - SW_CLIMATE_CALC *climateOutput); -void findDriestQtr(double *meanTempDriestQuarter_C, int numYears, double **meanMonthlyTemp_C, - double **meanMonthlyPPT_cm); + SW_CLIMATE_YEARLY *climateOutput); +void findDriestQtr(double *meanTempDriestQtr_C, int numYears, double **meanTempMon_C, + double **meanPPTMon_cm); void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, unsigned int n_years); void deallocateAllWeather(void); void _clear_hist_weather(SW_WEATHER_HIST *yearWeather); diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 2c0194194..ef3058cf6 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -141,8 +141,8 @@ namespace { TEST(ClimateVariableTest, ClimateFromDefaultWeather) { // This test relies on allHist from `SW_WEATHER` being already filled - SW_CLIMATE_CALC climateOutput; - SW_CLIMATE_AVERAGES climateAverage; + SW_CLIMATE_YEARLY climateOutput; + SW_CLIMATE_CLIM climateAverage; // Allocate memory climateOutput.JulyMinTemp = new double[31]; // 31 = Number of years in the simulation @@ -286,8 +286,8 @@ namespace { TEST(ClimateVariableTest, ClimateFromOneYearWeather) { // This test relies on allHist from `SW_WEATHER` being already filled - SW_CLIMATE_CALC climateOutput; - SW_CLIMATE_AVERAGES climateAverage; + SW_CLIMATE_YEARLY climateOutput; + SW_CLIMATE_CLIM climateAverage; // Allocate memory climateOutput.JulyMinTemp = new double[1]; // 1 = Number of years in the simulation @@ -444,8 +444,8 @@ namespace { TEST(ClimateVariableTest, ClimateFromConstantWeather) { - SW_CLIMATE_CALC climateOutput; - SW_CLIMATE_AVERAGES climateAverage; + SW_CLIMATE_YEARLY climateOutput; + SW_CLIMATE_CLIM climateAverages; SW_WEATHER_HIST **allHist; // Allocate memory From 9b900938bd931d3fc2c6df3ec6547d045fd64769 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Mon, 25 Jul 2022 23:37:15 -0400 Subject: [PATCH 118/326] Renamed variables in two new climate structs - Renamed variables in SW_CLIMATE_YEARLY to make them shorter and easier to read - Renamed variables in SW_CLIMATE_CLIM to match names in SW_CLIMATE_YEARLY - Descriptions of variables remain mostly the same --- SW_Weather.c | 108 +++++++-------- SW_Weather.h | 53 ++++---- test/test_SW_Weather.cc | 287 ++++++++++++++++++---------------------- 3 files changed, 211 insertions(+), 237 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 0761f5ece..3664523ef 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -94,30 +94,30 @@ void averageClimateAcrossYears(SW_CLIMATE_YEARLY *climateOutput, int numYears, int month; for(month = 0; month < MAX_MONTHS; month++) { - climateAverages->meanMonthlyTempAnn[month] = mean(climateOutput->meanMonthlyTemp_C[month], numYears); - climateAverages->maxMonthlyTempAnn[month] = mean(climateOutput->maxMonthlyTemp_C[month], numYears); - climateAverages->minMonthlyTempAnn[month] = mean(climateOutput->minMonthlyTemp_C[month], numYears); - climateAverages->meanMonthlyPPTAnn[month] = mean(climateOutput->monthlyPPT_cm[month], numYears); + 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); } - climateAverages->MAP_cm = mean(climateOutput->annualPPT_cm, numYears); - climateAverages->MAT_C = mean(climateOutput->meanAnnualTemp_C, numYears); - climateAverages->JulyPPTAnn_mm = mean(climateOutput->JulyPPT_mm, numYears); - climateAverages->meanTempDriestQuarterAnn_C = mean(climateOutput->meanTempDriestQuarter_C, numYears); - climateAverages->minTempFebruaryAnn_C = mean(climateOutput->minTempFebruary_C, numYears); - climateAverages->ddAbove65F_degdayAnn = mean(climateOutput->ddAbove65F_degday, numYears); - climateAverages->frostFreeAnn = mean(climateOutput->frostFreeDays_days, numYears); - climateAverages->JulyMinTempAnn = mean(climateOutput->JulyMinTemp, numYears); + climateAverages->PPT_cm = mean(climateOutput->PPT_cm, numYears); + climateAverages->meanTemp_C = mean(climateOutput->meanTemp_C, numYears); + climateAverages->PPTJuly_mm = mean(climateOutput->PPTJuly_mm, numYears); + climateAverages->meanTempDriestQtr_C = mean(climateOutput->meanTempDriestQtr_C, numYears); + climateAverages->minTempFeb_C = mean(climateOutput->minTempFeb_C, numYears); + climateAverages->ddAbove65F_degday = mean(climateOutput->ddAbove65F_degday, numYears); + climateAverages->frostFree_days = mean(climateOutput->frostFree_days, numYears); + climateAverages->minTempJuly_C = mean(climateOutput->minTempJuly_C, numYears); // Calculate and set standard deviation of C4 variables - climateAverages->sdC4[0] = standardDeviation(climateOutput->JulyMinTemp, numYears); - climateAverages->sdC4[1] = standardDeviation(climateOutput->frostFreeDays_days, numYears); + climateAverages->sdC4[0] = standardDeviation(climateOutput->minTempJuly_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->JulyPPT_mm, numYears); - climateAverages->sdCheatgrass[1] = standardDeviation(climateOutput->meanTempDriestQuarter_C, numYears); - climateAverages->sdCheatgrass[2] = standardDeviation(climateOutput->minTempFebruary_C, numYears); + climateAverages->sdCheatgrass[0] = standardDeviation(climateOutput->PPTJuly_mm, numYears); + climateAverages->sdCheatgrass[1] = standardDeviation(climateOutput->meanTempDriestQtr_C, numYears); + climateAverages->sdCheatgrass[2] = standardDeviation(climateOutput->minTempFeb_C, numYears); } /** @@ -140,15 +140,15 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, consecNonFrost, currentNonFrost; for(month = 0; month < MAX_MONTHS; month++) { - memset(climateOutput->meanMonthlyTemp_C[month], 0., sizeof(double) * numYears); - memset(climateOutput->maxMonthlyTemp_C[month], 0., sizeof(double) * numYears); - memset(climateOutput->minMonthlyTemp_C[month], 0., sizeof(double) * numYears); - memset(climateOutput->monthlyPPT_cm[month], 0., sizeof(double) * numYears); + 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->annualPPT_cm, 0., sizeof(double) * numYears); - memset(climateOutput->meanAnnualTemp_C, 0., sizeof(double) * numYears); - memset(climateOutput->minTempFebruary_C, 0., sizeof(double) * numYears); - memset(climateOutput->JulyPPT_mm, 0., sizeof(double) * numYears); + memset(climateOutput->PPT_cm, 0., sizeof(double) * numYears); + memset(climateOutput->meanTemp_C, 0., sizeof(double) * numYears); + memset(climateOutput->minTempFeb_C, 0., sizeof(double) * numYears); + memset(climateOutput->minTempJuly_C, 0., sizeof(double) * numYears); for(yearIndex = 0; yearIndex < numYears; yearIndex++) { year = yearIndex + startYear; Time_new_year(year); @@ -163,13 +163,13 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, for(day = 0; day < numDaysYear; day++) { currMonDay++; - climateOutput->meanMonthlyTemp_C[month][yearIndex] += allHist[yearIndex]->temp_avg[day]; - climateOutput->maxMonthlyTemp_C[month][yearIndex] += allHist[yearIndex]->temp_max[day]; - climateOutput->minMonthlyTemp_C[month][yearIndex] += allHist[yearIndex]->temp_min[day]; - climateOutput->monthlyPPT_cm[month][yearIndex] += allHist[yearIndex]->ppt[day]; + 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->annualPPT_cm[yearIndex] += allHist[yearIndex]->ppt[day]; - climateOutput->meanAnnualTemp_C[yearIndex] += allHist[yearIndex]->temp_avg[day]; + climateOutput->PPT_cm[yearIndex] += allHist[yearIndex]->ppt[day]; + climateOutput->meanTemp_C[yearIndex] += allHist[yearIndex]->temp_avg[day]; currentTempMin = allHist[yearIndex]->temp_min[day]; currentTempMean = allHist[yearIndex]->temp_avg[day]; @@ -190,16 +190,16 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, } if(month == Feb) { - climateOutput->minTempFebruary_C[yearIndex] += allHist[yearIndex]->temp_min[day]; + climateOutput->minTempFeb_C[yearIndex] += allHist[yearIndex]->temp_min[day]; } if(currMonDay == numDaysMonth) { // Take the average of the current months values for current year - climateOutput->meanMonthlyTemp_C[month][yearIndex] /= numDaysMonth; - climateOutput->maxMonthlyTemp_C[month][yearIndex] /= numDaysMonth; - climateOutput->minMonthlyTemp_C[month][yearIndex] /= numDaysMonth; + climateOutput->meanTempMon_C[month][yearIndex] /= numDaysMonth; + climateOutput->maxTempMon_C[month][yearIndex] /= numDaysMonth; + climateOutput->minTempMon_C[month][yearIndex] /= numDaysMonth; - if(month == Feb) climateOutput->minTempFebruary_C[yearIndex] /= numDaysMonth; + if(month == Feb) climateOutput->minTempFeb_C[yearIndex] /= numDaysMonth; month++; numDaysMonth = Time_days_in_month(month % 12); @@ -210,17 +210,17 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, totalAbove65 += (currentTempMean > 0.0) ? currentTempMean : 0.; } - climateOutput->JulyMinTemp[yearIndex] = currentJulyMin; - climateOutput->JulyPPT_mm[yearIndex] = JulyPPT; + climateOutput->minTempJuly_C[yearIndex] = currentJulyMin; + climateOutput->PPTJuly_mm[yearIndex] = JulyPPT; 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->frostFreeDays_days[yearIndex] = (consecNonFrost > 0) ? consecNonFrost : currentNonFrost; - climateOutput->meanAnnualTemp_C[yearIndex] /= numDaysYear; + climateOutput->frostFree_days[yearIndex] = (consecNonFrost > 0) ? consecNonFrost : currentNonFrost; + climateOutput->meanTemp_C[yearIndex] /= numDaysYear; } - findDriestQtr(climateOutput->meanTempDriestQuarter_C, numYears, - climateOutput->meanMonthlyTemp_C, climateOutput->monthlyPPT_cm); + findDriestQtr(climateOutput->meanTempDriestQtr_C, numYears, + climateOutput->meanTempMon_C, climateOutput->PPTMon_cm); } /** @@ -228,14 +228,14 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, @param[in] numYears Number of years represented within simulation @param[in] startYear Calendar year corresponding to first year of simulation - @param[out] meanMonthlyTemp_C 2D array containing monthly means average daily air temperature (deg;C) with + @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] monthlyPPT_cm 2D array containing monthly amount precipitation (cm) with dimensions + @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 - @param[out] meanTempDriestQuarter_C Array of size numYears holding the average temperature of the driest quarter of the year for every year + @param[out] meanTempDriestQtr_C Array of size numYears holding the average temperature of the driest quarter of the year for every year */ -void findDriestQtr(double *meanTempDriestQuarter_C, int numYears, double **meanMonthlyTemp_C, - double **monthlyPPT_cm) { +void findDriestQtr(double *meanTempDriestQtr_C, int numYears, double **meanTempMon_C, + double **PPTMon_cm) { int yearIndex, month, prevMonth, nextMonth; @@ -251,13 +251,13 @@ void findDriestQtr(double *meanTempDriestQuarter_C, int numYears, double **meanM prevMonth = (month == 0) ? 11 : month - 1; nextMonth = (month == 11) ? 0 : month + 1; - currentQtrPPT = (monthlyPPT_cm[prevMonth][yearIndex]) + - (monthlyPPT_cm[month][yearIndex]) + - (monthlyPPT_cm[nextMonth][yearIndex]); + currentQtrPPT = (PPTMon_cm[prevMonth][yearIndex]) + + (PPTMon_cm[month][yearIndex]) + + (PPTMon_cm[nextMonth][yearIndex]); - currentQtrTemp = (meanMonthlyTemp_C[prevMonth][yearIndex]) + - (meanMonthlyTemp_C[month][yearIndex]) + - (meanMonthlyTemp_C[nextMonth][yearIndex]); + currentQtrTemp = (meanTempMon_C[prevMonth][yearIndex]) + + (meanTempMon_C[month][yearIndex]) + + (meanTempMon_C[nextMonth][yearIndex]); if(currentQtrPPT < driestThreeMonPPT) { driestMeanTemp = currentQtrTemp; @@ -266,7 +266,7 @@ void findDriestQtr(double *meanTempDriestQuarter_C, int numYears, double **meanM } - meanTempDriestQuarter_C[yearIndex] = driestMeanTemp / 3; + meanTempDriestQtr_C[yearIndex] = driestMeanTemp / 3; } } diff --git a/SW_Weather.h b/SW_Weather.h index 676f14468..c257d4f2b 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -66,19 +66,21 @@ typedef struct { @note Number of years is variable and determined at runtime. */ typedef struct { - RealD **meanMonthlyTemp_C, /**< 2D array containing monthly mean average daily air temperature (deg;C)*/ - **maxMonthlyTemp_C, /**< 2D array containing monthly mean max daily air temperature (deg;C)*/ - **minMonthlyTemp_C, /**< 2D array containing monthly mean min daily air temperature (deg;C)*/ - **monthlyPPT_cm, /**< 2D array containing monthly amount precipitation (cm)*/ - *annualPPT_cm, /**< Array containing annual precipitation amount [cm]*/ - *meanAnnualTemp_C, /**< Array containing annual mean temperatures [C]*/ - *JulyMinTemp, /**< Array containing minimum July temperatures [C] */ - *frostFreeDays_days, /**< Array containing the maximum consecutive days in a year without frost*/ - *ddAbove65F_degday, /**< Array containing the amount of degree days [C x day] above 65 F */ - *JulyPPT_mm, /**< Array containing July precipitation amount (mm) for all years */ - *meanTempDriestQuarter_C, /**< Array containing the average temperature of the driest quarter of the year*/ - *minTempFebruary_C; /**< Array containing the mean minimum temperature in february*/ -} SW_CLIMATE_CALC; + RealD **PPTMon_cm, /**< 2D array containing monthly amount precipitation (cm)*/ + *PPT_cm, /**< Array containing annual precipitation amount [cm]*/ + *PPTJuly_mm, /**< Array containing July precipitation amount (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 temperature [C] of the driest quarter of the year*/ + *minTempFeb_C, /**< Array containing the mean daily minimum temperature in February [C] */ + *minTempJuly_C, /**< Array containing minimum July temperatures [C] */ + + *frostFree_days, /**< Array containing the maximum consecutive days [-] without frost*/ + *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 @@ -88,23 +90,22 @@ typedef struct { represents different variables, see `averageClimateAcrossYears()`. */ typedef struct { - RealD *meanMonthlyTempAnn, /**< Array of size MAX_MONTHS containing sum of monthly mean temperatures*/ - *maxMonthlyTempAnn, /**< Array of size MAX_MONTHS containing sum of monthly maximum temperatures*/ - *minMonthlyTempAnn, /**< Array of size MAX_MONTHS containing sum of monthly minimum temperatures*/ - *meanMonthlyPPTAnn, /**< Array of size MAX_MONTHS containing sum of monthly mean precipitation*/ + RealD *meanTempMon_C, /**< Array of size MAX_MONTHS containing sum of monthly mean temperatures*/ + *maxTempMon_C, /**< Array of size MAX_MONTHS containing sum of monthly maximum temperatures*/ + *minTempMon_C, /**< Array of size MAX_MONTHS containing sum of monthly minimum temperatures*/ + *PPTMon_cm, /**< Array of size MAX_MONTHS containing sum of monthly mean precipitation*/ *sdC4, /**< Array of size three holding the standard deviations of minimum July temperature (0), frost free days (1), number of days above 65F (2)*/ *sdCheatgrass, /**< Array of size three holding the standard deviations of July precipitation (0), mean temperature of dry quarter (1), mean minimum temperature of February (2)*/ - MAT_C, /**< Value containing the average of yearly temperatures*/ - MAP_cm, /**< Value containing the average of yearly precipitation*/ - JulyPPTAnn_mm, /**< Value containing average of July precipitation (mm)*/ - meanTempDriestQuarterAnn_C, /**< Value containing average of mean temperatures in the driest quarters of years*/ - minTempFebruaryAnn_C, /**< Value containing average of minimum temperatures in February*/ - ddAbove65F_degdayAnn, /**< Value containing average of total degrees above 65F (18.33C) throughout the year*/ - frostFreeAnn, /**< Value containing average of most consectutive days in a year without frost*/ - JulyMinTempAnn; /**< Value containing the average of lowest temperature in July*/ -} SW_CLIMATE_AVERAGES; + meanTemp_C, /**< Value containing the average of yearly temperatures*/ + PPT_cm, /**< Value containing the average of yearly precipitation*/ + PPTJuly_mm, /**< Value containing average of July precipitation (mm)*/ + meanTempDriestQtr_C, /**< Value containing average of mean temperatures in the driest quarters of years*/ + minTempFeb_C, /**< Value containing average of minimum temperatures in February*/ + ddAbove65F_degday, /**< Value containing average of total degrees above 65F (18.33C) throughout the year*/ + frostFree_days, /**< Value containing average of most consectutive days in a year without frost*/ + minTempJuly_C; /**< Value containing the average of lowest temperature in July*/ } SW_CLIMATE_CLIM; typedef struct { diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index ef3058cf6..baa61d4a5 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -142,41 +142,14 @@ namespace { // This test relies on allHist from `SW_WEATHER` being already filled SW_CLIMATE_YEARLY climateOutput; - SW_CLIMATE_CLIM climateAverage; - - // Allocate memory - climateOutput.JulyMinTemp = new double[31]; // 31 = Number of years in the simulation - climateOutput.annualPPT_cm = new double[31]; - climateOutput.frostFreeDays_days = new double[31]; - climateOutput.ddAbove65F_degday = new double[31]; - climateOutput.JulyPPT_mm = new double[31]; - climateOutput.meanTempDriestQuarter_C = new double[31]; - climateOutput.minTempFebruary_C = new double[31]; - climateOutput.meanAnnualTemp_C = new double[31]; - climateOutput.monthlyPPT_cm = new double*[MAX_MONTHS]; - climateOutput.meanMonthlyTemp_C = new double*[MAX_MONTHS]; - climateOutput.minMonthlyTemp_C = new double*[MAX_MONTHS]; - climateOutput.maxMonthlyTemp_C = new double*[MAX_MONTHS]; - - climateAverage.meanMonthlyTempAnn = new double[MAX_MONTHS]; - climateAverage.maxMonthlyTempAnn = new double[MAX_MONTHS]; - climateAverage.minMonthlyTempAnn = new double[MAX_MONTHS]; - climateAverage.meanMonthlyPPTAnn = new double[MAX_MONTHS]; - climateAverage.sdCheatgrass = new double[3]; - climateAverage.sdC4 = new double[3]; + SW_CLIMATE_CLIM climateAverages; - double *freeArray[14] = {climateOutput.JulyMinTemp, climateOutput.annualPPT_cm, - climateOutput.frostFreeDays_days, climateOutput.ddAbove65F_degday, climateOutput.JulyPPT_mm, - climateOutput.meanTempDriestQuarter_C, climateOutput.minTempFebruary_C, climateOutput.meanAnnualTemp_C, - climateAverage.meanMonthlyTempAnn, climateAverage.maxMonthlyTempAnn, climateAverage.minMonthlyTempAnn, - climateAverage.meanMonthlyPPTAnn, climateAverage.sdCheatgrass, climateAverage.sdC4}; + int deallocate = 0; + int allocate = 1; - for(int month = 0; month < MAX_MONTHS; month++) { - climateOutput.monthlyPPT_cm[month] = new double[31]; - climateOutput.meanMonthlyTemp_C[month] = new double[31]; - climateOutput.minMonthlyTemp_C[month] = new double[31]; - climateOutput.maxMonthlyTemp_C[month] = new double[31]; - } + // Allocate memory + // 31 = number of years used in test + allocDeallocClimateStructs(allocate, 31, &climateOutput, &climateAverages); // ------ Check climate variables for default weather ------ @@ -198,25 +171,25 @@ namespace { calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput); - EXPECT_NEAR(climateOutput.meanMonthlyTemp_C[Jan][0], -8.432581, tol6); - EXPECT_NEAR(climateOutput.maxMonthlyTemp_C[Jan][0], -2.562581, tol6); - EXPECT_NEAR(climateOutput.minMonthlyTemp_C[Jan][0], -14.302581, tol6); - EXPECT_NEAR(climateOutput.monthlyPPT_cm[Jan][0], 15.1400001, tol6); - EXPECT_NEAR(climateOutput.annualPPT_cm[0], 59.27, tol1); - EXPECT_NEAR(climateOutput.meanAnnualTemp_C[0], 4.524863, tol1); + 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_NEAR(climateOutput.JulyMinTemp[0], 2.809999, tol6); - EXPECT_NEAR(climateOutput.frostFreeDays_days[0], 92, tol6); + EXPECT_NEAR(climateOutput.minTempJuly_C[0], 2.809999, tol6); + 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.JulyPPT_mm[0], 18.299999, tol6); - EXPECT_NEAR(climateOutput.meanTempDriestQuarter_C[0], 0.936387, tol6); - EXPECT_NEAR(climateOutput.minTempFebruary_C[0], -12.822068, tol6); + EXPECT_NEAR(climateOutput.PPTJuly_mm[0], 18.299999, tol6); + EXPECT_NEAR(climateOutput.meanTempDriestQtr_C[0], 0.936387, tol6); + EXPECT_NEAR(climateOutput.minTempFeb_C[0], -12.822068, tol6); // --- Long-term variables (aggregated across years) ------ @@ -231,35 +204,35 @@ namespace { // ) // ``` - averageClimateAcrossYears(&climateOutput, 31, &climateAverage); + averageClimateAcrossYears(&climateOutput, 31, &climateAverages); - EXPECT_NEAR(climateAverage.meanMonthlyTempAnn[Jan], -9.325551, tol6); - EXPECT_NEAR(climateAverage.maxMonthlyTempAnn[Jan], -2.714381, tol6); - EXPECT_NEAR(climateAverage.minMonthlyTempAnn[Jan], -15.936722, tol6); - EXPECT_NEAR(climateAverage.meanMonthlyPPTAnn[Jan], 6.867419, tol6); + 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(climateAverage.MAP_cm, 62.817419, tol6); - // Note: rSOILWAT2 v5.3.1 returns incorrect MAT_C = 4.153896 + 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(climateAverage.MAT_C, 4.154009, tol6); + EXPECT_NEAR(climateAverages.meanTemp_C, 4.154009, tol6); // Climate variables used for C4 grass cover - EXPECT_NEAR(climateAverage.JulyMinTempAnn, 3.078387, tol6); - EXPECT_NEAR(climateAverage.frostFreeAnn, 90.612903, tol6); - EXPECT_NEAR(climateAverage.ddAbove65F_degdayAnn, 21.168032, tol6); + EXPECT_NEAR(climateAverages.minTempJuly_C, 3.078387, tol6); + EXPECT_NEAR(climateAverages.frostFree_days, 90.612903, tol6); + EXPECT_NEAR(climateAverages.ddAbove65F_degday, 21.168032, tol6); - EXPECT_NEAR(climateAverage.sdC4[0], 1.785535, tol6); - EXPECT_NEAR(climateAverage.sdC4[1], 14.091788, tol6); - EXPECT_NEAR(climateAverage.sdC4[2], 19.953560, 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(climateAverage.JulyPPTAnn_mm, 35.729032, tol6); - EXPECT_NEAR(climateAverage.meanTempDriestQuarterAnn_C, 11.524859, tol6); - EXPECT_NEAR(climateAverage.minTempFebruaryAnn_C, -13.904599, tol6); + EXPECT_NEAR(climateAverages.PPTJuly_mm, 35.729032, tol6); + EXPECT_NEAR(climateAverages.meanTempDriestQtr_C, 11.524859, tol6); + EXPECT_NEAR(climateAverages.minTempFeb_C, -13.904599, tol6); - EXPECT_NEAR(climateAverage.sdCheatgrass[0], 21.598367, tol6); - EXPECT_NEAR(climateAverage.sdCheatgrass[1], 7.171922, tol6); - EXPECT_NEAR(climateAverage.sdCheatgrass[2], 2.618434, 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 @@ -337,88 +310,88 @@ namespace { // ``` calcSiteClimate(SW_Weather.allHist, 1, 1980, &climateOutput); - averageClimateAcrossYears(&climateOutput, 1, &climateAverage); + averageClimateAcrossYears(&climateOutput, 1, &climateAverages); // Expect that aggregated values across one year are identical // to values of that one year EXPECT_DOUBLE_EQ( - climateAverage.meanMonthlyTempAnn[Jan], - climateOutput.meanMonthlyTemp_C[Jan][0] + climateAverages.meanTempMon_C[Jan], + climateOutput.meanTempMon_C[Jan][0] ); EXPECT_DOUBLE_EQ( - climateAverage.maxMonthlyTempAnn[Jan], - climateOutput.maxMonthlyTemp_C[Jan][0] + climateAverages.maxTempMon_C[Jan], + climateOutput.maxTempMon_C[Jan][0] ); EXPECT_DOUBLE_EQ( - climateAverage.minMonthlyTempAnn[Jan], - climateOutput.minMonthlyTemp_C[Jan][0] + climateAverages.minTempMon_C[Jan], + climateOutput.minTempMon_C[Jan][0] ); EXPECT_DOUBLE_EQ( - climateAverage.meanMonthlyPPTAnn[Jan], - climateOutput.monthlyPPT_cm[Jan][0] + climateAverages.PPTMon_cm[Jan], + climateOutput.PPTMon_cm[Jan][0] ); EXPECT_DOUBLE_EQ( - climateAverage.MAP_cm, - climateOutput.annualPPT_cm[0] + climateAverages.PPT_cm, + climateOutput.PPT_cm[0] ); EXPECT_DOUBLE_EQ( - climateAverage.MAT_C, - climateOutput.meanAnnualTemp_C[0] + climateAverages.meanTemp_C, + climateOutput.meanTemp_C[0] ); // Climate variables used for C4 grass cover EXPECT_DOUBLE_EQ( - climateAverage.JulyMinTempAnn, - climateOutput.JulyMinTemp[0] + climateAverages.minTempJuly_C, + climateOutput.minTempJuly_C[0] ); EXPECT_DOUBLE_EQ( - climateAverage.frostFreeAnn, - climateOutput.frostFreeDays_days[0] + climateAverages.frostFree_days, + climateOutput.frostFree_days[0] ); EXPECT_DOUBLE_EQ( - climateAverage.ddAbove65F_degdayAnn, + climateAverages.ddAbove65F_degday, climateOutput.ddAbove65F_degday[0] ); // Climate variables used for cheatgrass cover EXPECT_DOUBLE_EQ( - climateAverage.JulyPPTAnn_mm, - climateOutput.JulyPPT_mm[0] + climateAverages.PPTJuly_mm, + climateOutput.PPTJuly_mm[0] ); EXPECT_DOUBLE_EQ( - climateAverage.meanTempDriestQuarterAnn_C, - climateOutput.meanTempDriestQuarter_C[0] + climateAverages.meanTempDriestQtr_C, + climateOutput.meanTempDriestQtr_C[0] ); EXPECT_DOUBLE_EQ( - climateAverage.minTempFebruaryAnn_C, - climateOutput.minTempFebruary_C[0] + climateAverages.minTempFeb_C, + climateOutput.minTempFeb_C[0] ); - EXPECT_NEAR(climateAverage.meanMonthlyTempAnn[Jan], -8.432581, tol6); - EXPECT_NEAR(climateAverage.maxMonthlyTempAnn[Jan], -2.562581, tol6); - EXPECT_NEAR(climateAverage.minMonthlyTempAnn[Jan], -14.302581, tol6); - EXPECT_NEAR(climateAverage.meanMonthlyPPTAnn[Jan], 15.1400001, tol6); - EXPECT_NEAR(climateAverage.MAP_cm, 59.27, tol1); - EXPECT_NEAR(climateAverage.MAT_C, 4.524863, tol1); + 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_NEAR(climateAverage.JulyMinTempAnn, 2.809999, tol6); - EXPECT_NEAR(climateAverage.frostFreeAnn, 92, tol6); - EXPECT_NEAR(climateAverage.ddAbove65F_degdayAnn, 13.546000, tol6); - EXPECT_TRUE(isnan(climateAverage.sdC4[0])); - EXPECT_TRUE(isnan(climateAverage.sdC4[1])); - EXPECT_TRUE(isnan(climateAverage.sdC4[2])); + EXPECT_NEAR(climateAverages.minTempJuly_C, 2.809999, tol6); + 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(climateAverage.JulyPPTAnn_mm, 18.299999, tol6); - EXPECT_NEAR(climateAverage.meanTempDriestQuarterAnn_C, 0.936387, tol6); - EXPECT_NEAR(climateAverage.minTempFebruaryAnn_C, -12.822068, tol6); - EXPECT_TRUE(isnan(climateAverage.sdCheatgrass[0])); - EXPECT_TRUE(isnan(climateAverage.sdCheatgrass[1])); - EXPECT_TRUE(isnan(climateAverage.sdCheatgrass[2])); + EXPECT_NEAR(climateAverages.PPTJuly_mm, 18.299999, tol6); + EXPECT_NEAR(climateAverages.meanTempDriestQtr_C, 0.936387, tol6); + EXPECT_NEAR(climateAverages.minTempFeb_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 @@ -510,58 +483,58 @@ namespace { // --- Annual time-series of climate variables ------ calcSiteClimate(allHist, 2, 1980, &climateOutput); - EXPECT_DOUBLE_EQ(climateOutput.meanMonthlyTemp_C[Jan][0], 1.); - EXPECT_DOUBLE_EQ(climateOutput.maxMonthlyTemp_C[Jan][0], 1.); - EXPECT_DOUBLE_EQ(climateOutput.minMonthlyTemp_C[Jan][0], 1.); - EXPECT_DOUBLE_EQ(climateOutput.monthlyPPT_cm[Jan][0], 31.); - EXPECT_DOUBLE_EQ(climateOutput.monthlyPPT_cm[Feb][0], 29.); - EXPECT_DOUBLE_EQ(climateOutput.monthlyPPT_cm[Feb][1], 28.); - EXPECT_DOUBLE_EQ(climateOutput.annualPPT_cm[0], 366.); - EXPECT_DOUBLE_EQ(climateOutput.annualPPT_cm[1], 365.); - EXPECT_DOUBLE_EQ(climateOutput.meanAnnualTemp_C[0], 1.); + 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.JulyMinTemp[0], 1.); - EXPECT_DOUBLE_EQ(climateOutput.frostFreeDays_days[0], 366.); - EXPECT_DOUBLE_EQ(climateOutput.frostFreeDays_days[1], 365.); + EXPECT_DOUBLE_EQ(climateOutput.minTempJuly_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.JulyPPT_mm[0], 310.); - EXPECT_DOUBLE_EQ(climateOutput.meanTempDriestQuarter_C[0], 1.); - EXPECT_DOUBLE_EQ(climateOutput.minTempFebruary_C[0], 1.); + EXPECT_DOUBLE_EQ(climateOutput.PPTJuly_mm[0], 310.); + EXPECT_DOUBLE_EQ(climateOutput.meanTempDriestQtr_C[0], 1.); + EXPECT_DOUBLE_EQ(climateOutput.minTempFeb_C[0], 1.); // --- Long-term variables (aggregated across years) ------ - averageClimateAcrossYears(&climateOutput, 2, &climateAverage); + averageClimateAcrossYears(&climateOutput, 2, &climateAverages); - EXPECT_DOUBLE_EQ(climateAverage.meanMonthlyTempAnn[Jan], 1.); - EXPECT_DOUBLE_EQ(climateAverage.maxMonthlyTempAnn[Jan], 1.); - EXPECT_DOUBLE_EQ(climateAverage.minMonthlyTempAnn[Jan], 1.); - EXPECT_DOUBLE_EQ(climateAverage.meanMonthlyPPTAnn[Jan], 31.); - EXPECT_DOUBLE_EQ(climateAverage.meanMonthlyPPTAnn[Feb], 28.5); - EXPECT_DOUBLE_EQ(climateAverage.meanMonthlyPPTAnn[Dec], 31); - EXPECT_DOUBLE_EQ(climateAverage.MAP_cm, 365.5); - EXPECT_DOUBLE_EQ(climateAverage.MAT_C, 1.); + 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(climateAverage.JulyMinTempAnn, 1.); - EXPECT_DOUBLE_EQ(climateAverage.frostFreeAnn, 365.5); - EXPECT_DOUBLE_EQ(climateAverage.ddAbove65F_degdayAnn, 0.); - EXPECT_DOUBLE_EQ(climateAverage.sdC4[0], 0.); - EXPECT_NEAR(climateAverage.sdC4[1], .7071067, tol6); // sd(366, 365) - EXPECT_DOUBLE_EQ(climateAverage.sdC4[2], 0.); + EXPECT_DOUBLE_EQ(climateAverages.minTempJuly_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(climateAverage.JulyPPTAnn_mm, 310.); - EXPECT_DOUBLE_EQ(climateAverage.meanTempDriestQuarterAnn_C, 1.); - EXPECT_DOUBLE_EQ(climateAverage.minTempFebruaryAnn_C, 1.); - EXPECT_DOUBLE_EQ(climateAverage.sdCheatgrass[0], 0.); - EXPECT_DOUBLE_EQ(climateAverage.sdCheatgrass[1], 0.); - EXPECT_DOUBLE_EQ(climateAverage.sdCheatgrass[2], 0.); + EXPECT_DOUBLE_EQ(climateAverages.PPTJuly_mm, 310.); + EXPECT_DOUBLE_EQ(climateAverages.meanTempDriestQtr_C, 1.); + EXPECT_DOUBLE_EQ(climateAverages.minTempFeb_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 @@ -598,24 +571,24 @@ namespace { int month, year; - double **monthlyPPT_cm; - monthlyPPT_cm = new double*[MAX_MONTHS]; + double **PPTMon_cm; + PPTMon_cm = new double*[MAX_MONTHS]; - double **meanMonthlyTemp_C = new double*[MAX_MONTHS]; + double **meanTempMon_C = new double*[MAX_MONTHS]; for(month = 0; month < MAX_MONTHS; month++) { - monthlyPPT_cm[month] = new double[2]; - meanMonthlyTemp_C[month] = new double[2]; + PPTMon_cm[month] = new double[2]; + meanTempMon_C[month] = new double[2]; for(year = 0; year < 2; year++) { - monthlyPPT_cm[month][year] = monthlyPPT[month]; - meanMonthlyTemp_C[month][year] = monthlyTemp[month]; + PPTMon_cm[month][year] = monthlyPPT[month]; + meanTempMon_C[month][year] = monthlyTemp[month]; } } // ------ Test for one year ------ - findDriestQtr(result, 1, meanMonthlyTemp_C, monthlyPPT_cm); + findDriestQtr(result, 1, 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 @@ -623,7 +596,7 @@ namespace { // ------ Test for two years ------ - findDriestQtr(result, 2, meanMonthlyTemp_C, monthlyPPT_cm); + findDriestQtr(result, 2, meanTempMon_C, PPTMon_cm); EXPECT_NEAR(result[0], 1.4333333333333333, tol9); EXPECT_NEAR(result[1], 1.4333333333333333, tol9); @@ -633,11 +606,11 @@ namespace { // ------ Test when there are multiple driest quarters ------ for (month = 0; month < MAX_MONTHS; month++) { for(year = 0; year < 2; year++) { - monthlyPPT_cm[month][year] = 1.; + PPTMon_cm[month][year] = 1.; } } - findDriestQtr(result, 1, meanMonthlyTemp_C, monthlyPPT_cm); + findDriestQtr(result, 1, meanTempMon_C, PPTMon_cm); // Expect that the driest quarter that occurs first // among all driest quarters is used @@ -648,12 +621,12 @@ namespace { // ------ Clean up for(int month = 0; month < MAX_MONTHS; month++) { - delete[] monthlyPPT_cm[month]; - delete[] meanMonthlyTemp_C[month]; + delete[] PPTMon_cm[month]; + delete[] meanTempMon_C[month]; } - delete[] monthlyPPT_cm; - delete[] meanMonthlyTemp_C; + delete[] PPTMon_cm; + delete[] meanTempMon_C; } From f699dd2c93a09b9cc6b99c1f999ba1b7edbe9db2 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Mon, 25 Jul 2022 23:40:45 -0400 Subject: [PATCH 119/326] Added new function `allocDeallocClimateStructs()` - New function to allocate and deallocate two new climate structs - Call to new function in tests - Removed unnecessary code (allocations/deallocations of struct members) --- SW_Weather.c | 64 ++++++++++++++++++++ SW_Weather.h | 2 + test/test_SW_Weather.cc | 126 +++++----------------------------------- 3 files changed, 79 insertions(+), 113 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 3664523ef..4c736d4b8 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -773,6 +773,70 @@ Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather) { return swTRUE; } +void allocDeallocClimateStructs(int action, int numYears, SW_CLIMATE_YEARLY *climateOutput, + SW_CLIMATE_CLIM *climateAverages) { + + int deallocate = 0, month; + + if(action == deallocate) { + + free(climateOutput->PPT_cm); + free(climateOutput->PPTJuly_mm); + free(climateOutput->meanTemp_C); + free(climateOutput->meanTempDriestQtr_C); + free(climateOutput->minTempFeb_C); + free(climateOutput->minTempJuly_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); + } else { + 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->PPTJuly_mm = (double *)malloc(sizeof(double) * numYears); + climateOutput->meanTemp_C = (double *)malloc(sizeof(double) * numYears); + climateOutput->meanTempDriestQtr_C = (double *)malloc(sizeof(double) * numYears); + climateOutput->minTempFeb_C = (double *)malloc(sizeof(double) * numYears); + climateOutput->minTempJuly_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); + } + +} + #ifdef DEBUG_MEM #include "myMemory.h" /*======================================================*/ diff --git a/SW_Weather.h b/SW_Weather.h index c257d4f2b..e749cc80d 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -161,6 +161,8 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, SW_CLIMATE_YEARLY *climateOutput); void findDriestQtr(double *meanTempDriestQtr_C, int numYears, double **meanTempMon_C, double **meanPPTMon_cm); +void allocDeallocClimateStructs(int action, int numYears, SW_CLIMATE_YEARLY *climateOutput, + SW_CLIMATE_CLIM *climateAverages); void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, unsigned int n_years); void deallocateAllWeather(void); void _clear_hist_weather(SW_WEATHER_HIST *yearWeather); diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index baa61d4a5..f79ae6376 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -236,22 +236,7 @@ namespace { // ------ Reset and deallocate - for(int month = 0; month < MAX_MONTHS; month++) { - delete[] climateOutput.monthlyPPT_cm[month]; - delete[] climateOutput.meanMonthlyTemp_C[month]; - delete[] climateOutput.minMonthlyTemp_C[month]; - delete[] climateOutput.maxMonthlyTemp_C[month]; - } - - delete[] climateOutput.monthlyPPT_cm; - delete[] climateOutput.meanMonthlyTemp_C; - delete[] climateOutput.minMonthlyTemp_C; - delete[] climateOutput.maxMonthlyTemp_C; - - // Free rest of allocated memory - for(int index = 0; index < 14; index++) { - free(freeArray[index]); - } + allocDeallocClimateStructs(deallocate, 31, &climateOutput, &climateAverages); } @@ -260,41 +245,14 @@ namespace { // This test relies on allHist from `SW_WEATHER` being already filled SW_CLIMATE_YEARLY climateOutput; - SW_CLIMATE_CLIM climateAverage; + SW_CLIMATE_CLIM climateAverages; + + int deallocate = 0; + int allocate = 1; // Allocate memory - climateOutput.JulyMinTemp = new double[1]; // 1 = Number of years in the simulation - climateOutput.annualPPT_cm = new double[1]; - climateOutput.frostFreeDays_days = new double[1]; - climateOutput.ddAbove65F_degday = new double[1]; - climateOutput.JulyPPT_mm = new double[1]; - climateOutput.meanTempDriestQuarter_C = new double[1]; - climateOutput.minTempFebruary_C = new double[1]; - climateOutput.meanAnnualTemp_C = new double[1]; - climateOutput.monthlyPPT_cm = new double*[MAX_MONTHS]; - climateOutput.meanMonthlyTemp_C = new double*[MAX_MONTHS]; - climateOutput.minMonthlyTemp_C = new double*[MAX_MONTHS]; - climateOutput.maxMonthlyTemp_C = new double*[MAX_MONTHS]; - - climateAverage.meanMonthlyTempAnn = new double[MAX_MONTHS]; - climateAverage.maxMonthlyTempAnn = new double[MAX_MONTHS]; - climateAverage.minMonthlyTempAnn = new double[MAX_MONTHS]; - climateAverage.meanMonthlyPPTAnn = new double[MAX_MONTHS]; - climateAverage.sdCheatgrass = new double[3]; - climateAverage.sdC4 = new double[3]; - - double *freeArray[14] = {climateOutput.JulyMinTemp, climateOutput.annualPPT_cm, - climateOutput.frostFreeDays_days, climateOutput.ddAbove65F_degday, climateOutput.JulyPPT_mm, - climateOutput.meanTempDriestQuarter_C, climateOutput.minTempFebruary_C, climateOutput.meanAnnualTemp_C, - climateAverage.meanMonthlyTempAnn, climateAverage.maxMonthlyTempAnn, climateAverage.minMonthlyTempAnn, - climateAverage.meanMonthlyPPTAnn, climateAverage.sdCheatgrass, climateAverage.sdC4}; - - for(int month = 0; month < MAX_MONTHS; month++) { - climateOutput.monthlyPPT_cm[month] = new double[1]; - climateOutput.meanMonthlyTemp_C[month] = new double[1]; - climateOutput.minMonthlyTemp_C[month] = new double[1]; - climateOutput.maxMonthlyTemp_C[month] = new double[1]; - } + // 1 = number of years used in test + allocDeallocClimateStructs(allocate, 1, &climateOutput, &climateAverages); // ------ Check climate variables for one year of default weather ------ @@ -395,22 +353,7 @@ namespace { // ------ Reset and deallocate - for(int month = 0; month < MAX_MONTHS; month++) { - delete[] climateOutput.monthlyPPT_cm[month]; - delete[] climateOutput.meanMonthlyTemp_C[month]; - delete[] climateOutput.minMonthlyTemp_C[month]; - delete[] climateOutput.maxMonthlyTemp_C[month]; - } - - delete[] climateOutput.monthlyPPT_cm; - delete[] climateOutput.meanMonthlyTemp_C; - delete[] climateOutput.minMonthlyTemp_C; - delete[] climateOutput.maxMonthlyTemp_C; - - // Free rest of allocated memory - for(int index = 0; index < 14; index++) { - free(freeArray[index]); - } + allocDeallocClimateStructs(deallocate, 1, &climateOutput, &climateAverages); } @@ -421,39 +364,11 @@ namespace { SW_CLIMATE_CLIM climateAverages; SW_WEATHER_HIST **allHist; + int allocate = 1; + int deallocate = 0; + // Allocate memory - climateOutput.JulyMinTemp = new double[2]; - climateOutput.annualPPT_cm = new double[2]; - climateOutput.frostFreeDays_days = new double[2]; - climateOutput.ddAbove65F_degday = new double[2]; - climateOutput.JulyPPT_mm = new double[2]; - climateOutput.meanTempDriestQuarter_C = new double[2]; - climateOutput.minTempFebruary_C = new double[2]; - climateOutput.meanAnnualTemp_C = new double[2]; - climateOutput.monthlyPPT_cm = new double*[MAX_MONTHS]; - climateOutput.meanMonthlyTemp_C = new double*[MAX_MONTHS]; - climateOutput.minMonthlyTemp_C = new double*[MAX_MONTHS]; - climateOutput.maxMonthlyTemp_C = new double*[MAX_MONTHS]; - - climateAverage.meanMonthlyTempAnn = new double[MAX_MONTHS]; - climateAverage.maxMonthlyTempAnn = new double[MAX_MONTHS]; - climateAverage.minMonthlyTempAnn = new double[MAX_MONTHS]; - climateAverage.meanMonthlyPPTAnn = new double[MAX_MONTHS]; - climateAverage.sdCheatgrass = new double[3]; - climateAverage.sdC4 = new double[3]; - - double *freeArray[14] = {climateOutput.JulyMinTemp, climateOutput.annualPPT_cm, - climateOutput.frostFreeDays_days, climateOutput.ddAbove65F_degday, climateOutput.JulyPPT_mm, - climateOutput.meanTempDriestQuarter_C, climateOutput.minTempFebruary_C, climateOutput.meanAnnualTemp_C, - climateAverage.meanMonthlyTempAnn, climateAverage.maxMonthlyTempAnn, climateAverage.minMonthlyTempAnn, - climateAverage.meanMonthlyPPTAnn, climateAverage.sdCheatgrass, climateAverage.sdC4}; - - for(int month = 0; month < MAX_MONTHS; month++) { - climateOutput.monthlyPPT_cm[month] = new double[2]; - climateOutput.meanMonthlyTemp_C[month] = new double[2]; - climateOutput.minMonthlyTemp_C[month] = new double[2]; - climateOutput.maxMonthlyTemp_C[month] = new double[2]; - } + allocDeallocClimateStructs(allocate, 2, &climateOutput, &climateAverages); allHist = (SW_WEATHER_HIST **)malloc(sizeof(SW_WEATHER_HIST *) * 2); @@ -538,22 +453,7 @@ namespace { // ------ Reset and deallocate - for(int month = 0; month < MAX_MONTHS; month++) { - delete[] climateOutput.monthlyPPT_cm[month]; - delete[] climateOutput.meanMonthlyTemp_C[month]; - delete[] climateOutput.minMonthlyTemp_C[month]; - delete[] climateOutput.maxMonthlyTemp_C[month]; - } - - delete[] climateOutput.monthlyPPT_cm; - delete[] climateOutput.meanMonthlyTemp_C; - delete[] climateOutput.minMonthlyTemp_C; - delete[] climateOutput.maxMonthlyTemp_C; - - // Free rest of allocated memory - for(int index = 0; index < 14; index++) { - free(freeArray[index]); - } + allocDeallocClimateStructs(deallocate, 2, &climateOutput, &climateAverages); for (int year = 0; year < 2; year++) { free(allHist[year]); From b16045f28c95d9c1fee2cebbe88fea2f1409d9af Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 26 Jul 2022 16:40:17 -0400 Subject: [PATCH 120/326] Resolved merge conflicts --- SW_Weather.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/SW_Weather.h b/SW_Weather.h index e749cc80d..1a736f258 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -108,6 +108,18 @@ typedef struct { minTempJuly_C; /**< Value containing the average of lowest temperature in July*/ } 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 From eefe749fca9107249c0a14d0f924d40c8e4dd60f Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Tue, 26 Jul 2022 17:22:58 -0400 Subject: [PATCH 121/326] Generalize handling of missing values in forcing (weather) data 1) new "method" to select how missing forcing (weather) data is handled -> "generateWeatherMethod" (int) replaces "use_weathergenerator" (true/false) * pass through missing values * LOCF (temp) + 0 (ppt) * weather generator (previously, `use_weathergenerator`) -> no changes to the user-face (but user gained ability to select LOCF+0) -> this should make it easier to add additional weather generator methods in the future 2) rename `imputeMissingWeather()` to `generateMissingWeather()` -- clarify that the emphasis is on weather data generation * argument "method" replaces "useWeatherGenerator" * separate method 0 (pass through) from 1 (LOCF+0) 3) `scaleAllWeather() * does no longer assume that weather values are not missing * now handles missing values correctly 4) `SW_WTH_new_day()` * now checks (again) that no weather values are missing during simulation --- SW_Control.c | 2 +- SW_Markov.c | 35 ++++++-- SW_Weather.c | 164 +++++++++++++++++++++++++----------- SW_Weather.h | 23 +++-- test/test_SW_Weather.cc | 28 +++--- test/test_WaterBalance.cc | 10 +-- testing/Input/weathsetup.in | 1 + 7 files changed, 180 insertions(+), 83 deletions(-) diff --git a/SW_Control.c b/SW_Control.c index 20836c9e4..1ee1c4878 100644 --- a/SW_Control.c +++ b/SW_Control.c @@ -259,7 +259,7 @@ 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'"); diff --git a/SW_Markov.c b/SW_Markov.c index 9cc2432aa..2a0425195 100644 --- a/SW_Markov.c +++ b/SW_Markov.c @@ -292,6 +292,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 @@ -330,8 +339,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 @@ -520,14 +531,22 @@ 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) + ); } } diff --git a/SW_Weather.c b/SW_Weather.c index 355e8d7cc..356cd701f 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -123,7 +123,7 @@ void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, unsigned int n_yea for(yearIndex = 0; yearIndex < n_years; yearIndex++) { if(SW_Weather.use_weathergenerator_only) { - // Set to missing for call to `imputeMissingWeather()` + // Set to missing for call to `generateMissingWeather()` _clear_hist_weather(allHist[yearIndex]); } else { @@ -169,7 +169,7 @@ void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, unsigned int n_yea @note Daily average air temperature is re-calculated after scaling minimum and maximum air temperature. - @note `scaleAllWeather()` assumes that `allHist` is free of missing values. + @note Missing values in `allHist` remain unchanged. */ void scaleAllWeather( SW_WEATHER_HIST **allHist, @@ -204,14 +204,26 @@ void scaleAllWeather( for (day = 0; day < numDaysYear; day++) { month = doy2month(day + 1); - /* scale weather with monthly factors */ - allHist[yearIndex]->temp_max[day] += scale_temp_max[month]; - allHist[yearIndex]->temp_min[day] += scale_temp_min[month]; - allHist[yearIndex]->ppt[day] *= scale_precip[month]; + 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]; + } /* re-calculate average air temperature */ - allHist[yearIndex]->temp_avg[day] = - (allHist[yearIndex]->temp_max[day] + allHist[yearIndex]->temp_min[day]) / 2.; + 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.; + } } } } @@ -219,50 +231,52 @@ void scaleAllWeather( /** - @brief Impute missing weather values + @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 - by a weather generator (which has separate input requirements). + 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 missing) + 1. Some individual days are missing (values correspond to #SW_MISSING) 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 used exclusively: + Available methods to generate weather: + 1. Pass through (`method` = 0) + 2. Imputation by last-value-carried forward "LOCF" (`method` = 1) + - for minimum and maximum temperature + - precipitation is set to 0 + - error if more than `optLOCF_nMax` days per calendar year are missing + 3. First-order Markov weather generator (`method` = 2) + + SOILWAT2 may be set up such that weather is generated exclusively + (i.e., without an attempt to read data from files on disk): - 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 - If the weather generator is turned off, then up to five days per calendar - year are imputed - - precipitation is set to `0` - - minimum and maximum temperature are imputed based on `LOCF` - (last value carried forward) - - If more than `nMaxLOCF` days per calendar year are missing and the weather - generator is turned off, then an error is thrown. - @note `SW_MKV_today()` is called if `useWeatherGenerator` - which requires that appropriate structures are initialized. + @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] useWeatherGenerator Impute by weather generator (if `TRUE`) - or by `LOCF` (for temperature) and zeros (for precipitation) (if `FALSE`). - @param[in] nMaxLOCF Maximum number of missing days per year (e.g., 5) + @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 imputeMissingWeather( +void generateMissingWeather( SW_WEATHER_HIST **allHist, int startYear, unsigned int n_years, - Bool useWeatherGenerator, - unsigned int nMaxLOCF + unsigned int method, + unsigned int optLOCF_nMax ) { int year; @@ -273,6 +287,22 @@ void imputeMissingWeather( Bool any_missing, missing_Tmax, missing_Tmin, missing_PPT; + // 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; Time_new_year(year); @@ -289,8 +319,8 @@ void imputeMissingWeather( if (any_missing) { // some of today's values are missing - if (useWeatherGenerator) { - // if weather generator is turned on then use it for all values + if (method == 2) { + // Weather generator allHist[yearIndex]->ppt[day] = yesterdayPPT; SW_MKV_today( day, @@ -299,9 +329,8 @@ void imputeMissingWeather( &allHist[yearIndex]->ppt[day] ); - } else { - // impute missing temperature based on - // LOCF (last-observation-carried-forward) + } else if (method == 1) { + // LOCF (temp) + 0 (PPT) allHist[yearIndex]->temp_max[day] = missing_Tmax ? yesterdayMax : allHist[yearIndex]->temp_max[day]; @@ -310,7 +339,6 @@ void imputeMissingWeather( yesterdayMin : allHist[yearIndex]->temp_min[day]; - // impute missing precipitation with 0 allHist[yearIndex]->ppt[day] = missing_PPT ? 0. : allHist[yearIndex]->ppt[day]; @@ -319,13 +347,13 @@ void imputeMissingWeather( // Throw an error if too many values per calendar year are missing iMissing++; - if (iMissing > nMaxLOCF) { + if (iMissing > optLOCF_nMax) { LogError( logfp, LOGFATAL, - "imputeMissingWeather(): more than %d days missing in year %d " + "generateMissingWeather(): more than %u days missing in year %u " "and weather generator turned off.\n", - nMaxLOCF, + optLOCF_nMax, year ); } @@ -412,9 +440,10 @@ void SW_WTH_deconstruct(void) } } - if (SW_Weather.use_weathergenerator) { + if (SW_Weather.generateWeatherMethod == 2) { SW_MKV_deconstruct(); } + deallocateAllWeather(); } @@ -510,6 +539,19 @@ void SW_WTH_new_day(void) { #endif /* get the daily weather from allHist */ + if ( + missing(w->allHist[year]->temp_avg[day]) || + missing(w->allHist[year]->ppt[day]) + ) { + LogError( + logfp, + LOGFATAL, + "Missing weather data (day %u - %u) during simulation.", + SW_Model.year, + SW_Model.doy + ); + } + wn->temp_max[Today] = w->allHist[year]->temp_max[day]; wn->temp_min[Today] = w->allHist[year]->temp_min[day]; wn->ppt[Today] = w->allHist[year]->ppt[day]; @@ -557,11 +599,39 @@ void SW_WTH_setup(void) { 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); + 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; @@ -607,7 +677,7 @@ void SW_WTH_setup(void) { 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) { + if (SW_Weather.generateWeatherMethod != 2 && SW_Model.startyr < w->yr.first) { LogError( logfp, LOGFATAL, @@ -644,12 +714,12 @@ void SW_WTH_read(void) { // Impute missing values - imputeMissingWeather( + generateMissingWeather( SW_Weather.allHist, SW_Model.startyr, SW_Weather.n_years, - (Bool) SW_Weather.use_weathergenerator, - 3 // nMaxLOCF + SW_Weather.generateWeatherMethod, + 3 // optLOCF_nMax (TODO: make this user input) ); diff --git a/SW_Weather.h b/SW_Weather.h index c7c4ca361..b622f890a 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -61,12 +61,17 @@ typedef struct { 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; + 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`) + RealD pct_snowdrift, pct_snowRunoff; unsigned int n_years; SW_TIMES yr; @@ -114,12 +119,12 @@ void scaleAllWeather( double *scale_temp_min, double *scale_precip ); -void imputeMissingWeather( +void generateMissingWeather( SW_WEATHER_HIST **allHist, int startYear, unsigned int n_years, - Bool useWeatherGenerator, - unsigned int nMaxLOCF + unsigned int method, + unsigned int optLOCF_nMax ); void allocateAllWeather(void); void deallocateAllWeather(void); diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index cd916cef9..d492219cd 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -55,15 +55,16 @@ namespace { } TEST(ReadAllWeatherTest, SomeMissingValuesDays) { - - SW_Weather.use_weathergenerator = swTRUE; - SW_MKV_setup(); - + + 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(); - + // With the use of 1980's missing values, test a few days of the year // to make sure they are filled using the weather generator EXPECT_TRUE(!missing(SW_Weather.allHist[0]->temp_max[0])); @@ -80,10 +81,11 @@ namespace { TEST(ReadAllWeatherTest, SomeMissingValuesYears) { int year, day; - SW_Weather.use_weathergenerator = swTRUE; - + 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; @@ -105,8 +107,8 @@ namespace { TEST(ReadAllWeatherTest, WeatherGeneratorOnly) { int year, day; - - SW_Weather.use_weathergenerator = swTRUE; + + SW_Weather.generateWeatherMethod = 2; SW_Weather.use_weathergenerator_only = swTRUE; SW_MKV_setup(); @@ -127,13 +129,13 @@ namespace { } - TEST(ReadAllWeatherDeathTest, TooManyMissingAndNoWeatherGenerator) { + TEST(ReadAllWeatherDeathTest, TooManyMissingForLOCF) { // Change to directory without input files strcpy(SW_Weather.name_prefix, "Input/data_weather_nonexisting/weath"); - SW_Weather.use_weathergenerator = swFALSE; - SW_Weather.use_weathergenerator_only = swFALSE; + // Set LOCF (temp) + 0 (PPT) method + SW_Weather.generateWeatherMethod = 1; SW_Model.startyr = 1981; SW_Model.endyr = 1981; diff --git a/test/test_WaterBalance.cc b/test/test_WaterBalance.cc index 8e7746a23..edb14d3a4 100644 --- a/test/test_WaterBalance.cc +++ b/test/test_WaterBalance.cc @@ -112,7 +112,7 @@ namespace { int i; // Turn on Markov weather generator (and turn off use of historical weather) - SW_Weather.use_weathergenerator = swTRUE; + SW_Weather.generateWeatherMethod = 2; SW_Weather.use_weathergenerator_only = swTRUE; // Read Markov weather generator input files (they are not normally read) @@ -143,14 +143,14 @@ namespace { 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(); + 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(); diff --git a/testing/Input/weathsetup.in b/testing/Input/weathsetup.in index 55ddd9730..d2fc38347 100755 --- a/testing/Input/weathsetup.in +++ b/testing/Input/weathsetup.in @@ -10,6 +10,7 @@ 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 -1 # first year to begin historical weather # if -1, then use first year of simulation (see `years.in`) From d5d8f7a94ecdfb67804d4c9f12aa9ec72ce5bf40 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Wed, 27 Jul 2022 16:25:28 -0400 Subject: [PATCH 122/326] Fixed missed 'Today' in `SW_WTH_new_day()` from commit --- SW_Weather.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 5e985ce51..0347da338 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -718,9 +718,9 @@ void SW_WTH_new_day(void) { ); } - wn->temp_max[Today] = w->allHist[year]->temp_max[day]; - wn->temp_min[Today] = w->allHist[year]->temp_min[day]; - wn->ppt[Today] = w->allHist[year]->ppt[day]; + wn->temp_max = w->allHist[year]->temp_max[day]; + wn->temp_min = w->allHist[year]->temp_min[day]; + wn->ppt = w->allHist[year]->ppt[day]; wn->temp_avg = w->allHist[year]->temp_avg[day]; From 6a9e4b051ce3dde4d26c2ca1f10bff7eb929e88e Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 28 Jul 2022 14:05:32 -0400 Subject: [PATCH 123/326] Created vegetation estimation functions in `SW_VegProd.c` - Created wrapper--`estimateVegetationFromClimate()`--that handles: * Climate calculation/averaging * Vegetation estimation * Sets vegetation values - Created function `esimatePotNatVegComposition()` to estimate vegetation - Created two helper functions for `esimatePotNatVegComposition()`: * `uniqueIndices()` and `cutZeroInf()` - `cutZeroInf()` makes sure the entered value is zero or above - `uniqueIndices()` finds unique indices that are in need of estimation --- SW_VegProd.c | 454 +++++++++++++++++++++++++++++++++++++++++++++++++++ SW_VegProd.h | 8 + 2 files changed, 462 insertions(+) diff --git a/SW_VegProd.c b/SW_VegProd.c index 8fb85d35e..ede8471fe 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -843,3 +843,457 @@ void get_critical_rank(void){ End of rank_SWPcrits ----------------------------------------------------------*/ } + +/** + @brief Wrapper function for estimating natural vegetation. First, climate is calculated and averaged, then values are estimated + + @param[in] veg 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 + */ + +void estimateVegetationFromClimate(SW_VEGPROD *veg, int startYear, int endYear) { + + int numYears = endYear - startYear + 1, deallocate = 0, allocate = 1; + + 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, + SW_MISSING, SW_MISSING, SW_MISSING, SW_MISSING}; + + double SumGrassesFraction = SW_MISSING, C4Variables[3], grassOutput[3], + RelAbundanceL0[8], RelAbundanceL1[5]; + + Bool fillEmptyWithBareGround = swTRUE, inNorth = swTRUE, warnExtrapolation = swTRUE; + + // Allocate climate structs' memory + allocDeallocClimateStructs(allocate, numYears, &climateOutput, &climateAverages); + + calcSiteClimate(SW_Weather.allHist, numYears, startYear, &climateOutput); + + averageClimateAcrossYears(&climateOutput, numYears, &climateAverages); + + C4Variables[0] = climateAverages.minTempJuly_C; + C4Variables[1] = climateAverages.ddAbove65F_degday; + C4Variables[2] = climateAverages.frostFree_days; + + esimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, + climateAverages.PPTMon_cm, coverValues, SumGrassesFraction, C4Variables, + fillEmptyWithBareGround, inNorth, warnExtrapolation, + grassOutput, RelAbundanceL0, RelAbundanceL1); + + // Deallocate climate structs' memory + allocDeallocClimateStructs(deallocate, numYears, &climateOutput, &climateAverages); + +} + +/** + @brief Using the data calculated in `calcSiteCliamte()`, estimates the natural vegetation composition of a site + + @param[in] meanTemp_C Value containing the average of yearly temperatures [C] + @param[in] PPT_cm Value containing the average of yearly precipitation [cm] + @param[in] meanTempMon_C Array of size MAX_MONTHS containing sum of 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 starting values for the function to start with + @param[in] SumGrassesFraction Value holding sum of grass if user would like it to be fixed + @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] inNorth 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[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 a process that is specified in Teeri JA, Stowe LG (1976) and equations from Paruelo & Lauenroth (1996) + */ + +void esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTempMon_C[], + double PPTMon_cm[], double inputValues[], double SumGrassesFraction, double C4Variables[], + Bool fillEmptyWithBareGround, Bool inNorth, Bool warnExtrapolation, double *grassOutput, + double *RelAbundanceL0, double *RelAbundanceL1) { + + int nTypes = 8, idsToEstimateGrass[3], 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, frostFreeDays = 1, degreeAbove65 = 2, + estimIndices[5] = {succIndex, forbIndex, C3Index, C4Index, shrubIndex}, + estimIndicesNotNA = 0, grassesEstim[3], overallEstim[nTypes], iFixed[nTypes], + iFixedSize = 0, isetIndices[3] = {grassAnn, treeIndex, bareGround}, estimIndicesSize = 0; + + double inputSum = 0, totalSumGrasses = 0., inputSumGrasses = 0., tempDiffJanJul, + summerMAP = 0., winterMAP = 0., C4Species = 0., C3Grassland, C3Shrubland, estimGrassSum = 0, + finalVegSum = 0., estimCoverSum = 0., tempSumGrasses = 0., estimCover[nTypes], iFixSum = 0.; + + Bool fixGrasses, fixBareGround = (Bool) (inputValues[bareGround] != SW_MISSING); + + // Initialize estimCover and overallEstim + for(index = 0; index < nTypes; index++) { + if(inputValues[index] != SW_MISSING) { + iFixed[iFixedSize] = index; + iFixedSize++; + } else { + estimIndices[estimIndicesSize] = index; + estimIndicesSize++; + } + estimCover[index] = 0.; + } + + fixGrasses = (Bool) (inputValues[grassAnn] != SW_MISSING && inputValues[C3Index] != SW_MISSING + && inputValues[C3Index] != SW_MISSING); + + // Check if grasses are fixed + if(fixGrasses) { + // Set SumGrassesFraction + // If SumGrassesFraction < 0, set to zero, otherwise keep at value + SumGrassesFraction = (SumGrassesFraction < 0) ? 0 : SumGrassesFraction; + // Get sum of input grass values and set to inputSumGrasses + for(index = C3Index; index < grassAnn; index++) { + if(inputValues[index] != SW_MISSING) inputSumGrasses += inputValues[index]; + } + + // Get totalSumGrasses + totalSumGrasses = SumGrassesFraction - inputSumGrasses; + + // Check if totalSumGrasses is less than zero + if(totalSumGrasses < 0){ + // Throw error and stop + } + // Find indices to estimate related to grass (i.e., C3, C4 and annual grasses) + for(index = C3Index; index < grassAnn; index++) { + if(inputValues[index] == SW_MISSING) { + 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 + estimCover[grassesEstim[index]] = 0.; + } + } + } + + uniqueIndices(inputValues, isetIndices, estimIndices, 3, estimIndicesSize, iFixed, &iFixedSize); + + // Loop through inputValues + for(index = 0; index < nTypes; index++) { + // Check if the element isn't missing + if(inputValues[index] != SW_MISSING) { + // Add value to inputSum + inputSum += inputValues[index]; + } else { + // Put index in overallEstim + overallEstim[overallEstimSize] = index; + // Incrememnt overallEstim index + overallEstimSize++; + } + } + + // Check if number of elements to estimate is less than or equal to 1 + 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`) + estimCover[bareGround] = 1 - (inputSum - estimCover[bareGround]); + } else if(inputSum < 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 - inputSum; + } + } 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) + if(inNorth) { + for(index = 0; index < 3; index++) { + winterMonths[index] = (index + 11) % 12; + summerMonths[index] = (index + 5); + summerMAP += PPTMon_cm[summerMonths[index]] * 10; + winterMAP += PPTMon_cm[winterMonths[index]] * 10; + } + } else { + for(index = 0; index < 3; index++) { + summerMonths[index] = (index + 11) % 12; + winterMonths[index] = (index + 5); + summerMAP += PPTMon_cm[summerMonths[index]] * 10; + winterMAP += PPTMon_cm[winterMonths[index]] * 10; + } + } + } + // Set summer and winter precipitations in mm + summerMAP /= (PPT_cm * 10); + winterMAP /= (PPT_cm * 10); + + // Get the difference between July and Janurary + tempDiffJanJul = 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 + // TODO: Check to see if we can add a check to see if C4Variables is sufficient + if(C4Variables != NULL) { + if(C4Variables[frostFreeDays] <= 0) { + C4Species = 0; + } else { + C4Species = ((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 = 1.1905 - .02909 * meanTemp_C + .1781 * log(winterMAP) - .2383; + C3Shrubland = 1.1905 - .02909 * meanTemp_C + .1781 * log(winterMAP) - .2383 * 2; + + if(C3Grassland < 0.) C3Grassland = 0.; + if(C3Shrubland < 0.) C3Shrubland = 0.; + } + + if(estimCover[shrubIndex] != SW_MISSING) { + estimCover[C3Index] = C3Shrubland; + } else { + estimCover[C3Index] = C3Grassland; + } + + // Paruelo & Lauenroth (1996): forb climate-relationship: + if(PPT_cm * 10 < 1 || meanTemp_C <= 0) { + estimCover[forbIndex] = SW_MISSING; + } 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] = SW_MISSING; + } else { + estimCover[succIndex] = cutZeroInf(-1 + ((1.20246 * pow(tempDiffJanJul, -.0689)) * (pow(winterMAP, -.0322)))); + } + } + + for(index = 0; index < 5; index++) { + if(estimCover[estimIndices[index]] == SW_MISSING) { + estimCover[estimIndices[index]] = 0.; + } else { + estimIndicesNotNA++; + } + } + + 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(fixGrasses && totalSumGrasses > 0) { + for(index = 0; index < grassEstimSize; index++) { + estimGrassSum += estimCover[idsToEstimateGrass[index]]; + } + 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 / estimGrassSum); + } + LogError(logfp, LOGWARN, "'estimate_PotNatVeg_composition': " + "Total grass cover set, but no grass cover estimated; " + "requested cover evenly divided among grass types."); + } + + // TODO: FIX + if(fixGrasses) { + //uniqueIndices(iFixed, &iFixedSize, iFixed, grassIndices, iFixedSize, 3); + //uniqueIndices(estimIndices, &idsToEstimateGrass) + } + + for(index = 0; index < nTypes; index++) { + finalVegSum += estimCover[index]; + } + + if(!EQ(finalVegSum, 1.)) { + estimCoverSum = mean(estimCover, nTypes) * nTypes; + + for(index = 0; index < iFixedSize; index++) { + iFixSum += estimCover[iFixed[index]]; + } + + if(estimCoverSum > 0) { + for(index = 0; index < estimIndicesSize; index++) { + estimCover[estimIndices[index]] *= (1 - iFixSum) / estimCoverSum; + } + } else { + if(fillEmptyWithBareGround && !fixBareGround) { + inputValues[index] = 1.; + for(index = 0; index < nTypes - 1; index++) { + inputValues[index] -= inputValues[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."); + } + } + } + + grassOutput[0] = estimCover[C3Index]; + grassOutput[1] = estimCover[C4Index]; + grassOutput[2] = estimCover[grassAnn]; + + tempSumGrasses = mean(grassOutput, 3) * 3; + if(tempSumGrasses > 0) { + for(index = 0; index < 3; index++) { + grassOutput[index] /= tempSumGrasses; + } + } + + for(index = 0; index < nTypes; index++) { + RelAbundanceL0[index] = estimCover[index]; + } + + RelAbundanceL1[0] = estimCover[treeIndex]; + RelAbundanceL1[1] = estimCover[shrubIndex]; + RelAbundanceL1[2] = estimCover[forbIndex] + estimCover[succIndex]; + RelAbundanceL1[3] = tempSumGrasses; + RelAbundanceL1[4] = estimCover[bareGround]; +} + +/** + @brief Helper function to `esimatePotNatVegComposition()` that doesn't allow a value to go below zero + + @return A value that is either above or equal to zero + */ + +double cutZeroInf(double value) { + if(value < 0.) { + return 0.; + } else { + return value; + } +} + +/** + @brief Helper function to `esimatePotNatVegComposition()` that gets unique indices from two input arrays + + @param[in] inputValues Array of size eight that holds the input to `esimatePotNatVegComposition()` + @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(double inputValues[], int arrayOne[], int arrayTwo[], int arrayOneSize, + int arrayTwoSize, int *finalIndexArray, int *finalIndexArraySize) { + + int indexOne, indexTwo, finalArrayIndex = *finalIndexArraySize; + + Bool indexFound = swFALSE; + + // Loop through first array and check all of second array to make sure it's not a repeat + for(indexOne = 0; indexOne < arrayOneSize; indexOne++) { + for(indexTwo = 0; indexTwo < arrayTwoSize; indexTwo++) { + if(arrayOne[indexOne] == arrayTwo[indexTwo]) indexFound = swTRUE; + } + if(!indexFound && inputValues[arrayOne[indexOne]] != SW_MISSING) { + finalIndexArray[finalArrayIndex] = arrayOne[indexOne]; + finalArrayIndex++; + indexFound = swFALSE; + } + } + + // Loop through the second array to make sure to add things that were missed + for(indexTwo = 0; indexTwo < arrayTwoSize; indexTwo++) { + for(indexOne = 0; indexOne < arrayOneSize; indexOne++) { + if(arrayTwo[indexTwo] == arrayOne[indexOne]) indexFound = swTRUE; + } + if(!indexFound && inputValues[arrayTwo[indexTwo]] != SW_MISSING) { + finalIndexArray[finalArrayIndex] = arrayOne[indexOne]; + finalArrayIndex++; + indexFound = swFALSE; + } + } + + *finalIndexArraySize = finalArrayIndex; + +} diff --git a/SW_VegProd.h b/SW_VegProd.h index 1641530ca..7ccb41259 100644 --- a/SW_VegProd.h +++ b/SW_VegProd.h @@ -263,6 +263,14 @@ 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 *veg, int startYear, int endYear); +void esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTempMon_C[], + double PPTMon_cm[], double inputValues[], double SumGrassesFraction, double C4Variables[], + Bool fillEmptyWithBareGround, Bool inNorth, Bool warnExtrapolation, double *grassOutput, + double *RelAbundanceL0, double *RelAbundanceL1); +double cutZeroInf(double value); +void uniqueIndices(double inputValues[], 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); From bc2e7fe6b998e7b79e7c1b9cc4784650e9555d57 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 28 Jul 2022 14:07:26 -0400 Subject: [PATCH 124/326] Added call to `estimateVegetationFromClimate()` - `estimateVegetationFromClimate()` is now called from `SW_VPD_init_run()` - Added globals in `SW_VPD_init_run()` to use in `estimateVegetationFromClimate()` --- SW_VegProd.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/SW_VegProd.c b/SW_VegProd.c index ede8471fe..44e29bad3 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -576,6 +576,9 @@ void SW_VPD_init_run(void) { TimeInt year; int k; + SW_VEGPROD *veg = &SW_VegProd; + SW_MODEL *model = &SW_Model; + /* Set co2-multipliers to default */ for (year = 0; year < MAX_NYEAR; year++) { @@ -585,6 +588,8 @@ void SW_VPD_init_run(void) { SW_VegProd.veg[k].co2_multipliers[WUE_INDEX][year] = 1.; } } + + estimateVegetationFromClimate(veg, model->startyr, model->endyr); } From 86e6e5e66113ab859f74e2c251f3a661b5614a6c Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Mon, 1 Aug 2022 22:20:03 -0400 Subject: [PATCH 125/326] Added to/patched a lot of `estimatePotNatVegComposition()` - Added "shrubLimit" as a parameter - Deleted grass indices from "overallEstim" - Added prevention of calculations if "initialVegSum" is greater than or equal to one - Replaced "!= SW_MISSING" with the macro `missing()` --- SW_VegProd.c | 182 +++++++++++++++++++++++++++++---------------------- SW_VegProd.h | 6 +- 2 files changed, 107 insertions(+), 81 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index 44e29bad3..282eedb74 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -867,9 +867,9 @@ void estimateVegetationFromClimate(SW_VEGPROD *veg, int startYear, int endYear) // NOTE: 8 = number of types, 5 = (number of types) - grasses double coverValues[8] = {SW_MISSING, SW_MISSING, SW_MISSING, SW_MISSING, - SW_MISSING, SW_MISSING, SW_MISSING, SW_MISSING}; + SW_MISSING, SW_MISSING, SW_MISSING, SW_MISSING}, shrubLimit = .2; - double SumGrassesFraction = SW_MISSING, C4Variables[3], grassOutput[3], + double SumGrassesFraction = -SW_MISSING, C4Variables[3], grassOutput[3], RelAbundanceL0[8], RelAbundanceL1[5]; Bool fillEmptyWithBareGround = swTRUE, inNorth = swTRUE, warnExtrapolation = swTRUE; @@ -886,7 +886,7 @@ void estimateVegetationFromClimate(SW_VEGPROD *veg, int startYear, int endYear) C4Variables[2] = climateAverages.frostFree_days; esimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, - climateAverages.PPTMon_cm, coverValues, SumGrassesFraction, C4Variables, + climateAverages.PPTMon_cm, coverValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorth, warnExtrapolation, grassOutput, RelAbundanceL0, RelAbundanceL1); @@ -903,6 +903,7 @@ void estimateVegetationFromClimate(SW_VEGPROD *veg, int startYear, int endYear) @param[in] meanTempMon_C Array of size MAX_MONTHS containing sum of 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 starting values for the function to start with + @param[in] shrubLimit Value containing max allowed amount of shrubs @param[in] SumGrassesFraction Value holding sum of grass if user would like it to be fixed @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 @@ -919,49 +920,67 @@ void estimateVegetationFromClimate(SW_VEGPROD *veg, int startYear, int endYear) */ void esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTempMon_C[], - double PPTMon_cm[], double inputValues[], double SumGrassesFraction, double C4Variables[], - Bool fillEmptyWithBareGround, Bool inNorth, Bool warnExtrapolation, double *grassOutput, - double *RelAbundanceL0, double *RelAbundanceL1) { + double PPTMon_cm[], double inputValues[], double shrubLimit, double SumGrassesFraction, + double C4Variables[], Bool fillEmptyWithBareGround, Bool inNorth, Bool warnExtrapolation, + double *grassOutput, double *RelAbundanceL0, double *RelAbundanceL1) { - int nTypes = 8, idsToEstimateGrass[3], winterMonths[3], summerMonths[3]; + int nTypes = 8, 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, frostFreeDays = 1, degreeAbove65 = 2, - estimIndices[5] = {succIndex, forbIndex, C3Index, C4Index, shrubIndex}, + int index, succIndex = 0, forbIndex = 1, C3Index = 2, C4Index = 3, grassAnn = 4, + shrubIndex = 5, treeIndex = 6, bareGround = 7, grassEstimSize = 0, overallEstimSize = 0, + julyMin = 0, frostFreeDays = 1, degreeAbove65 = 2, estimIndicesSize = 0, estimIndicesNotNA = 0, grassesEstim[3], overallEstim[nTypes], iFixed[nTypes], - iFixedSize = 0, isetIndices[3] = {grassAnn, treeIndex, bareGround}, estimIndicesSize = 0; + iFixedSize = 0, isetIndices[3] = {grassAnn, treeIndex, bareGround}; - double inputSum = 0, totalSumGrasses = 0., inputSumGrasses = 0., tempDiffJanJul, + // Totals of different areas of variables + double totalSumGrasses = 0., inputSumGrasses = 0., tempDiffJanJul, summerMAP = 0., winterMAP = 0., C4Species = 0., C3Grassland, C3Shrubland, estimGrassSum = 0, - finalVegSum = 0., estimCoverSum = 0., tempSumGrasses = 0., estimCover[nTypes], iFixSum = 0.; + finalVegSum = 0., estimCoverSum = 0., tempSumGrasses = 0., estimCover[nTypes], + initialVegSum = 0.; - Bool fixGrasses, fixBareGround = (Bool) (inputValues[bareGround] != SW_MISSING); + Bool fixSumGrasses, fixBareGround = (Bool) (missing(inputValues[bareGround])), + fullVeg = swFALSE, isGrassIndex = swFALSE; + + for(index = 0; index < nTypes; index++) { + if(!missing(inputValues[index])) { + initialVegSum += inputValues[index]; + } else { + initialVegSum += 0.; + } + } + + if(initialVegSum >= 1.) fullVeg = swTRUE; // Initialize estimCover and overallEstim for(index = 0; index < nTypes; index++) { - if(inputValues[index] != SW_MISSING) { + if(index == bareGround) { + estimCover[bareGround] = + (!missing(inputValues[bareGround])) ? inputValues[bareGround] : 0.; + iFixed[iFixedSize] = bareGround; + iFixedSize++; + } else if(!missing(inputValues[index])) { iFixed[iFixedSize] = index; iFixedSize++; + estimCover[index] = inputValues[index]; } else { - estimIndices[estimIndicesSize] = index; - estimIndicesSize++; + overallEstim[overallEstimSize] = index; + overallEstimSize++; + estimCover[index] = 0.; } - estimCover[index] = 0.; } - fixGrasses = (Bool) (inputValues[grassAnn] != SW_MISSING && inputValues[C3Index] != SW_MISSING - && inputValues[C3Index] != SW_MISSING); + fixSumGrasses = (Bool) (!missing(inputValues[grassAnn]) && !missing(inputValues[C3Index]) + && !missing(inputValues[C3Index]) && SumGrassesFraction >= 0); // Check if grasses are fixed - if(fixGrasses) { + if(fixSumGrasses) { // Set SumGrassesFraction // If SumGrassesFraction < 0, set to zero, otherwise keep at value SumGrassesFraction = (SumGrassesFraction < 0) ? 0 : SumGrassesFraction; // Get sum of input grass values and set to inputSumGrasses for(index = C3Index; index < grassAnn; index++) { - if(inputValues[index] != SW_MISSING) inputSumGrasses += inputValues[index]; + if(!missing(inputValues[index])) inputSumGrasses += inputValues[index]; } // Get totalSumGrasses @@ -1000,19 +1019,6 @@ void esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTe uniqueIndices(inputValues, isetIndices, estimIndices, 3, estimIndicesSize, iFixed, &iFixedSize); - // Loop through inputValues - for(index = 0; index < nTypes; index++) { - // Check if the element isn't missing - if(inputValues[index] != SW_MISSING) { - // Add value to inputSum - inputSum += inputValues[index]; - } else { - // Put index in overallEstim - overallEstim[overallEstimSize] = index; - // Incrememnt overallEstim index - overallEstimSize++; - } - } // Check if number of elements to estimate is less than or equal to 1 if(overallEstimSize <= 1) { @@ -1021,14 +1027,14 @@ void esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTe if(fillEmptyWithBareGround) { // Set estimCover at index `bareGround` to 1 - (all values execpt // at index `bareGround`) - estimCover[bareGround] = 1 - (inputSum - estimCover[bareGround]); - } else if(inputSum < 1) { + estimCover[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 - inputSum; + estimCover[overallEstim[0]] = 1 - initialVegSum; } } else { @@ -1081,27 +1087,28 @@ void esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTe "(117 - 1011 mm): MAP = %3f mm", PPT_cm * 10); } } - // Paruelo & Lauenroth (1996): shrub climate-relationship: - if(PPT_cm * 10 < 1) { + if(PPT_cm * 10 < 1 && !fullVeg) { estimCover[shrubIndex] = 0.; } else { - estimCover[shrubIndex] = cutZeroInf(1.7105 - (.2918 * log(PPT_cm * 10)) + if(missing(inputValues[shrubIndex]) && !fullVeg) { + estimCover[shrubIndex] = cutZeroInf(1.7105 - (.2918 * log(PPT_cm * 10)) + (1.5451 * winterMAP)); + } } // Paruelo & Lauenroth (1996): C4-grass climate-relationship: - if(meanTemp_C <= 0) { + if(meanTemp_C <= 0 && !fullVeg) { estimCover[C4Index] = 0; } else { - estimCover[C4Index] = cutZeroInf(-0.9837 + (.000594 * (PPT_cm * 10)) + if(missing(inputValues[C4Index]) && !fullVeg) { + 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 - // TODO: Check to see if we can add a check to see if C4Variables is sufficient - if(C4Variables != NULL) { + if(C4Variables != NULL && !fullVeg) { if(C4Variables[frostFreeDays] <= 0) { C4Species = 0; } else { @@ -1124,31 +1131,37 @@ void esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTe if(C3Shrubland < 0.) C3Shrubland = 0.; } - if(estimCover[shrubIndex] != SW_MISSING) { - estimCover[C3Index] = C3Shrubland; + if(!missing(estimCover[shrubIndex]) && estimCover[shrubIndex] >= shrubLimit) { + if(missing(inputValues[C3Index]) && !fullVeg) { + estimCover[C3Index] = C3Shrubland; + } } else { - estimCover[C3Index] = C3Grassland; + if(missing(inputValues[C3Index]) && !fullVeg) { + estimCover[C3Index] = C3Grassland; + } } - // Paruelo & Lauenroth (1996): forb climate-relationship: - if(PPT_cm * 10 < 1 || meanTemp_C <= 0) { + if((PPT_cm * 10 < 1 || meanTemp_C <= 0) && !fullVeg) { estimCover[forbIndex] = SW_MISSING; } else { - estimCover[forbIndex] = cutZeroInf(-.2035 + (.07975 * log(PPT_cm * 10)) + if(missing(inputValues[forbIndex]) && !fullVeg) { + estimCover[forbIndex] = cutZeroInf(-.2035 + (.07975 * log(PPT_cm * 10)) - (.0623 * log(meanTemp_C))); + } } - // Paruelo & Lauenroth (1996): succulent climate-relationship: - if(tempDiffJanJul <= 0 || winterMAP <= 0) { + if((tempDiffJanJul <= 0 || winterMAP <= 0)) { estimCover[succIndex] = SW_MISSING; } else { - estimCover[succIndex] = cutZeroInf(-1 + ((1.20246 * pow(tempDiffJanJul, -.0689)) * (pow(winterMAP, -.0322)))); + if(missing(inputValues[succIndex])) { + estimCover[succIndex] = + cutZeroInf(-1 + ((1.20246 * pow(tempDiffJanJul, -.0689)) * (pow(winterMAP, -.0322)))); + } } } - - for(index = 0; index < 5; index++) { - if(estimCover[estimIndices[index]] == SW_MISSING) { - estimCover[estimIndices[index]] = 0.; + for(index = 0; index < overallEstimSize; index++) { + if(missing(estimCover[overallEstim[index]])) { + estimCover[overallEstim[index]] = 0.; } else { estimIndicesNotNA++; } @@ -1166,9 +1179,9 @@ void esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTe } } - if(fixGrasses && totalSumGrasses > 0) { + if(fixSumGrasses && totalSumGrasses > 0) { for(index = 0; index < grassEstimSize; index++) { - estimGrassSum += estimCover[idsToEstimateGrass[index]]; + estimGrassSum += estimCover[grassesEstim[index]]; } for(index = 0; index < grassEstimSize; index++) { estimCover[grassesEstim[index]] *= (totalSumGrasses / estimGrassSum); @@ -1182,32 +1195,45 @@ void esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTe "requested cover evenly divided among grass types."); } - // TODO: FIX - if(fixGrasses) { - //uniqueIndices(iFixed, &iFixedSize, iFixed, grassIndices, iFixedSize, 3); - //uniqueIndices(estimIndices, &idsToEstimateGrass) - } + if(fixSumGrasses) { + // Add grasses to `iFixed` array + uniqueIndices(iFixed, grassesEstim, iFixedSize, grassEstimSize, iFixed, &iFixedSize); - for(index = 0; index < nTypes; index++) { - finalVegSum += estimCover[index]; + // Remove them from the `estimIndices` array + for(index = 0; index < estimIndicesSize; index++) { + do { + isGrassIndex = (overallEstim[index] == grassAnn + || overallEstim[index] == treeIndex + || overallEstim[index] == bareGround); + + if(isGrassIndex) { + overallEstim[estimIndicesSize - 1] = overallEstim[index]; + estimIndicesSize--; + } + } while(index != estimIndicesSize - 1 && isGrassIndex); + } } + for(index = 0; index < iFixedSize; index++) { + if(missing(estimCover[index])) { + finalVegSum += 0.; + } else { + finalVegSum += estimCover[iFixed[index]]; + } + } if(!EQ(finalVegSum, 1.)) { - estimCoverSum = mean(estimCover, nTypes) * nTypes; - - for(index = 0; index < iFixedSize; index++) { - iFixSum += estimCover[iFixed[index]]; + for(index = 0; index < overallEstimSize; index++) { + estimCoverSum += estimCover[overallEstim[index]]; } - if(estimCoverSum > 0) { - for(index = 0; index < estimIndicesSize; index++) { - estimCover[estimIndices[index]] *= (1 - iFixSum) / estimCoverSum; + for(index = 0; index < overallEstimSize; index++) { + estimCover[overallEstim[index]] *= (1 - finalVegSum) / estimCoverSum; } } else { if(fillEmptyWithBareGround && !fixBareGround) { - inputValues[index] = 1.; + estimCover[index] = 1.; for(index = 0; index < nTypes - 1; index++) { - inputValues[index] -= inputValues[index]; + estimCover[index] -= estimCover[index]; } } else { LogError(logfp, LOGFATAL, "'estimate_PotNatVeg_composition': " diff --git a/SW_VegProd.h b/SW_VegProd.h index 7ccb41259..9f20e01f5 100644 --- a/SW_VegProd.h +++ b/SW_VegProd.h @@ -265,9 +265,9 @@ void SW_VPD_fix_cover(void); void SW_VPD_construct(void); void estimateVegetationFromClimate(SW_VEGPROD *veg, int startYear, int endYear); void esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTempMon_C[], - double PPTMon_cm[], double inputValues[], double SumGrassesFraction, double C4Variables[], - Bool fillEmptyWithBareGround, Bool inNorth, Bool warnExtrapolation, double *grassOutput, - double *RelAbundanceL0, double *RelAbundanceL1); + double PPTMon_cm[], double inputValues[], double shrubLimit, double SumGrassesFraction, + double C4Variables[], Bool fillEmptyWithBareGround, Bool inNorth, Bool warnExtrapolation, + double *grassOutput, double *RelAbundanceL0, double *RelAbundanceL1); double cutZeroInf(double value); void uniqueIndices(double inputValues[], int arrayOne[], int arrayTwo[], int arrayOneSize, int arrayTwoSize, int *finalIndexArray, int *finalIndexArraySize); From 9a4eae7bd156940ebcfd9ac91ea2b6e472f66ca7 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Mon, 1 Aug 2022 22:24:11 -0400 Subject: [PATCH 126/326] Rewrote `uniqueIndices()` - Previous version would not properly find unique indices - Made more efficient - Removed the need for "inputValues" --- SW_VegProd.c | 47 ++++++++++++++++++++++++----------------------- SW_VegProd.h | 4 ++-- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index 282eedb74..2acbac6bd 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -1017,8 +1017,7 @@ void esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTe } } - uniqueIndices(inputValues, isetIndices, estimIndices, 3, estimIndicesSize, iFixed, &iFixedSize); - + uniqueIndices(isetIndices, iFixed, 3, iFixedSize, iFixed, &iFixedSize); // Check if number of elements to estimate is less than or equal to 1 if(overallEstimSize <= 1) { @@ -1285,7 +1284,6 @@ double cutZeroInf(double value) { /** @brief Helper function to `esimatePotNatVegComposition()` that gets unique indices from two input arrays - @param[in] inputValues Array of size eight that holds the input to `esimatePotNatVegComposition()` @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 @@ -1294,34 +1292,37 @@ double cutZeroInf(double value) { @param[in,out] finalIndexArraySize Value holding the size of finalIndexArray both before and after the function is run */ -void uniqueIndices(double inputValues[], int arrayOne[], int arrayTwo[], int arrayOneSize, - int arrayTwoSize, int *finalIndexArray, int *finalIndexArraySize) { +void uniqueIndices(int arrayOne[], int arrayTwo[], int arrayOneSize, int arrayTwoSize, + int *finalIndexArray, int *finalIndexArraySize) { - int indexOne, indexTwo, finalArrayIndex = *finalIndexArraySize; + int index, finalArrayIndex = 0, + tempSize = arrayOneSize + arrayTwoSize + finalArrayIndex, tempIndex = 0; + int tempArray[tempSize], tempArraySeen[tempSize]; - Bool indexFound = swFALSE; + for(index = 0; index < tempSize; index++) { + // Initalize the `seen` version of tempArray + tempArraySeen[index] = 0; - // Loop through first array and check all of second array to make sure it's not a repeat - for(indexOne = 0; indexOne < arrayOneSize; indexOne++) { - for(indexTwo = 0; indexTwo < arrayTwoSize; indexTwo++) { - if(arrayOne[indexOne] == arrayTwo[indexTwo]) indexFound = swTRUE; + if(index < finalArrayIndex) { + tempArray[tempIndex] = finalIndexArray[index]; + tempIndex++; } - if(!indexFound && inputValues[arrayOne[indexOne]] != SW_MISSING) { - finalIndexArray[finalArrayIndex] = arrayOne[indexOne]; - finalArrayIndex++; - indexFound = swFALSE; + if(index < arrayOneSize) { + tempArray[tempIndex] = arrayOne[index]; + tempIndex++; + } + if(index < arrayTwoSize) { + tempArray[tempIndex] = arrayTwo[index]; + tempIndex++; } } - // Loop through the second array to make sure to add things that were missed - for(indexTwo = 0; indexTwo < arrayTwoSize; indexTwo++) { - for(indexOne = 0; indexOne < arrayOneSize; indexOne++) { - if(arrayTwo[indexTwo] == arrayOne[indexOne]) indexFound = swTRUE; - } - if(!indexFound && inputValues[arrayTwo[indexTwo]] != SW_MISSING) { - finalIndexArray[finalArrayIndex] = arrayOne[indexOne]; + 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++; - indexFound = swFALSE; + tempArraySeen[tempArray[index]] = tempArray[index]; } } diff --git a/SW_VegProd.h b/SW_VegProd.h index 9f20e01f5..e26be9466 100644 --- a/SW_VegProd.h +++ b/SW_VegProd.h @@ -269,8 +269,8 @@ void esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTe double C4Variables[], Bool fillEmptyWithBareGround, Bool inNorth, Bool warnExtrapolation, double *grassOutput, double *RelAbundanceL0, double *RelAbundanceL1); double cutZeroInf(double value); -void uniqueIndices(double inputValues[], int arrayOne[], int arrayTwo[], int arrayOneSize, - int arrayTwoSize, int *finalIndexArray, int *finalIndexArraySize); +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); From 41e1f7e080aa920799078e3440c06af8b5426b2e Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 2 Aug 2022 15:59:22 -0400 Subject: [PATCH 127/326] Added unit tests for `estimatePotNatVegComposition()` - Unit tests cover the main scenarios of: * Input vegetation is greater than or equal to one (EstimateVegetationTest.FullVegetation) * Input vegetation is not greater than or equal to one (EstimateVegetationTest.NotFullVegetation) - Input vegetation is not greater than or equal to one covers: * All input is "SW_MISSING" * Half input is "SW_MISSING" * All input is filled with values - Input vegetation is greater than or equal to one covers: * All input is filled and equals one * A couple inputs are not "SW_MISSING" and add to one --- test/test_SW_VegProd.cc | 280 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 280 insertions(+) diff --git a/test/test_SW_VegProd.cc b/test/test_SW_VegProd.cc index 1c04cd203..3a47c4dab 100644 --- a/test/test_SW_VegProd.cc +++ b/test/test_SW_VegProd.cc @@ -159,4 +159,284 @@ namespace { Reset_SOILWAT2_after_UnitTest(); } + TEST(EstimateVegetationTest, NotFullVegetation) { + + SW_CLIMATE_YEARLY climateOutput; + SW_CLIMATE_CLIM climateAverages; + + double inputValues[8] = {SW_MISSING, SW_MISSING, SW_MISSING, SW_MISSING, + SW_MISSING, SW_MISSING, SW_MISSING, SW_MISSING}; + 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] = {0.0, 0.2608391, 0.4307062, + 0.0, 0.0, 0.3084547, 0.0, 0.0}; + double RelAbundanceL1Expected[5] = {0.0, 0.3084547, 0.2608391, 0.4307062, 0.0}; + + Bool fillEmptyWithBareGround = swTRUE; + Bool inNorth = swTRUE; + Bool warnExtrapolation = swTRUE; + + int deallocate = 0; + int allocate = 1; + int index; + + // Reset "SW_Weather.allHist" + SW_WTH_read(); + + // Allocate arrays needed for `calcSiteClimate()` and `averageClimateAcrossYears()` + allocDeallocClimateStructs(allocate, 31, &climateOutput, &climateAverages); + + // Calculate climate of the site and add results to "climateOutput" + calcSiteClimate(SW_Weather.allHist, 31, 1980, &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.minTempJuly_C; + C4Variables[1] = climateAverages.ddAbove65F_degday; + C4Variables[2] = climateAverages.frostFree_days; + + // Estimate vegetation based off calculated variables and "inputValues" + esimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorth, warnExtrapolation, + grassOutput, RelAbundanceL0, RelAbundanceL1); + + // Loop through RelAbundanceL0 and test results + for(index = 0; index < 8; 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); + } + + EXPECT_DOUBLE_EQ(grassOutput[0], 1.); + EXPECT_DOUBLE_EQ(grassOutput[1], 0.); + EXPECT_DOUBLE_EQ(grassOutput[2], 0.); + + // Test with half of input values not "SW_MISSING" + inputValues[0] = .376; + inputValues[1] = SW_MISSING; + inputValues[2] = .096; + inputValues[3] = SW_MISSING; + inputValues[4] = SW_MISSING; + inputValues[5] = .1098; + inputValues[6] = .0372; + inputValues[7] = SW_MISSING; + + RelAbundanceL0Expected[0] = 0.3760; + RelAbundanceL0Expected[1] = 0.3810; + RelAbundanceL0Expected[2] = 0.0960; + RelAbundanceL0Expected[3] = 0.0000; + RelAbundanceL0Expected[4] = 0.0000; + RelAbundanceL0Expected[5] = 0.1098; + RelAbundanceL0Expected[6] = 0.0372; + RelAbundanceL0Expected[7] = 0.0000; + + RelAbundanceL1Expected[0] = 0.0372; + RelAbundanceL1Expected[1] = 0.1098; + RelAbundanceL1Expected[2] = 0.7570; + RelAbundanceL1Expected[3] = 0.0960; + RelAbundanceL1Expected[4] = 0.0000; + + esimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorth, warnExtrapolation, + grassOutput, RelAbundanceL0, RelAbundanceL1); + + // Loop through RelAbundanceL0 and test results + for(index = 0; index < 8; 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); + } + + EXPECT_DOUBLE_EQ(grassOutput[0], 1.); + EXPECT_DOUBLE_EQ(grassOutput[1], 0.); + EXPECT_DOUBLE_EQ(grassOutput[2], 0.); + + // Test with all input values not "SW_MISSING" + inputValues[0] = .1098; + inputValues[1] = .1098; + inputValues[2] = .1098; + inputValues[3] = .1098; + inputValues[4] = .1098; + inputValues[5] = .1098; + inputValues[6] = .1098; + inputValues[7] = .1098; + + RelAbundanceL0Expected[0] = 0.1098; + RelAbundanceL0Expected[1] = 0.1098; + RelAbundanceL0Expected[2] = 0.1098; + RelAbundanceL0Expected[3] = 0.1098; + RelAbundanceL0Expected[4] = 0.1098; + RelAbundanceL0Expected[5] = 0.1098; + RelAbundanceL0Expected[6] = 0.1098; + RelAbundanceL0Expected[7] = 0.2314; + + RelAbundanceL1Expected[0] = 0.1098; + RelAbundanceL1Expected[1] = 0.1098; + RelAbundanceL1Expected[2] = 0.2196; + RelAbundanceL1Expected[3] = 0.3294; + RelAbundanceL1Expected[4] = 0.2314; + + esimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorth, warnExtrapolation, + grassOutput, RelAbundanceL0, RelAbundanceL1); + + // Loop through RelAbundanceL0 and test results. Since initial values + // do not add to one and we fill empty with bare ground, bare ground should be higher + // than the other values (in this case, .2314) + for(index = 0; index < 8; 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); + } + + EXPECT_NEAR(grassOutput[0], .333333, tol6); + EXPECT_NEAR(grassOutput[1], .333333, tol6); + EXPECT_NEAR(grassOutput[2], .333333, tol6); + + // Deallocate structs + allocDeallocClimateStructs(deallocate, 31, &climateOutput, &climateAverages); + } + + TEST(EstimateVegetationTest, FullVegetation) { + + SW_CLIMATE_YEARLY climateOutput; + SW_CLIMATE_CLIM climateAverages; + + double inputValues[8] = {.0567, .2317, .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 + + double SumGrassesFraction = -SW_MISSING; + double C4Variables[3]; + double RelAbundanceL0Expected[8] = {0.0567, 0.2317, .0392, + .0981, .3218, .0827, .1293, .0405}; + double RelAbundanceL1Expected[5] = {.1293, .0827, .2884, .4591, .0405}; + + Bool fillEmptyWithBareGround = swTRUE; + Bool inNorth = swTRUE; + Bool warnExtrapolation = swTRUE; + + int deallocate = 0; + int allocate = 1; + int index; + + // Reset "SW_Weather.allHist" + SW_WTH_read(); + + // Allocate arrays needed for `calcSiteClimate()` and `averageClimateAcrossYears()` + allocDeallocClimateStructs(allocate, 31, &climateOutput, &climateAverages); + + // Calculate climate of the site and add results to "climateOutput" + calcSiteClimate(SW_Weather.allHist, 31, 1980, &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.minTempJuly_C; + C4Variables[1] = climateAverages.ddAbove65F_degday; + C4Variables[2] = climateAverages.frostFree_days; + + // Estimate vegetation based off calculated variables and "inputValues" + esimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorth, warnExtrapolation, + grassOutput, RelAbundanceL0, RelAbundanceL1); + + // All values in "RelAbundanceL0" should be exactly the same as "inputValues" + for(index = 0; index < 8; index++) { + EXPECT_NEAR(RelAbundanceL0[index], RelAbundanceL0Expected[index], tol6); + } + + // All values in "RelAbundanceL1" should be exactly the same as "inputValues" + for(index = 0; index < 5; index++) { + EXPECT_NEAR(RelAbundanceL1[index], RelAbundanceL1Expected[index], tol6); + } + + EXPECT_NEAR(grassOutput[0], 0.08538445, tol6); + EXPECT_NEAR(grassOutput[1], 0.21367894, tol6); + EXPECT_NEAR(grassOutput[2], 0.70093662, tol6); + + // Test when a couple input values are not "SW_MISSING" + inputValues[0] = .5; + inputValues[1] = SW_MISSING; + inputValues[2] = .5; + inputValues[3] = SW_MISSING; + inputValues[4] = SW_MISSING; + inputValues[5] = SW_MISSING; + inputValues[6] = SW_MISSING; + inputValues[7] = SW_MISSING; + + RelAbundanceL0Expected[0] = .5; + RelAbundanceL0Expected[1] = 0.; + RelAbundanceL0Expected[2] = .5; + RelAbundanceL0Expected[3] = 0.; + RelAbundanceL0Expected[4] = 0.; + RelAbundanceL0Expected[5] = 0.; + RelAbundanceL0Expected[6] = 0.; + RelAbundanceL0Expected[7] = 0.; + + RelAbundanceL1Expected[0] = 0.; + RelAbundanceL1Expected[1] = 0.; + RelAbundanceL1Expected[2] = .5; + RelAbundanceL1Expected[3] = .5; + RelAbundanceL1Expected[4] = 0.; + + esimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorth, warnExtrapolation, + 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]); + } + + EXPECT_DOUBLE_EQ(grassOutput[0], 1.); + EXPECT_DOUBLE_EQ(grassOutput[1], 0.); + EXPECT_DOUBLE_EQ(grassOutput[2], 0.); + + // Deallocate structs + allocDeallocClimateStructs(deallocate, 31, &climateOutput, &climateAverages); + } + } // namespace From 15960c5f957413d7cc14003c61852a536e62684e Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 2 Aug 2022 16:55:26 -0400 Subject: [PATCH 128/326] Added new user input in file `veg.in` - Added new section in `SW_VPD_read()` to read new input - Added new variable "veg_method" in type SW_VEGPROD struct - `SW_VPD_init_run()` now decides whether or not to call `estimateVegetationFromClimate()` --- SW_VegProd.c | 73 +++++++++++++++++++++++++------------------- SW_VegProd.h | 5 +-- testing/Input/veg.in | 6 +++- 3 files changed, 50 insertions(+), 34 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index 2acbac6bd..abf99a3b7 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -85,8 +85,8 @@ 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 + 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); @@ -95,8 +95,17 @@ void SW_VPD_read(void) { while (GetALine(f, inbuf)) { if (lineno++ < line_help) { switch (lineno) { + case 1: + x = sscanf(inbuf, "%d", &veg_method); + if(x != 1) { + sprintf(errstr, "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 1: + 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) { @@ -111,7 +120,7 @@ void SW_VPD_read(void) { break; /* albedo */ - case 2: + 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) { @@ -126,7 +135,7 @@ void SW_VPD_read(void) { break; /* canopy height */ - case 3: + 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) { @@ -138,7 +147,7 @@ void SW_VPD_read(void) { v->veg[k].cnpy.xinflec = help_veg[k]; } break; - case 4: + 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) { @@ -150,7 +159,7 @@ void SW_VPD_read(void) { v->veg[k].cnpy.yinflec = help_veg[k]; } break; - case 5: + 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) { @@ -162,7 +171,7 @@ void SW_VPD_read(void) { v->veg[k].cnpy.range = help_veg[k]; } break; - case 6: + 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) { @@ -174,7 +183,7 @@ void SW_VPD_read(void) { v->veg[k].cnpy.slope = help_veg[k]; } break; - case 7: + 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) { @@ -188,7 +197,7 @@ void SW_VPD_read(void) { break; /* vegetation interception parameters */ - case 8: + 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) { @@ -200,7 +209,7 @@ void SW_VPD_read(void) { v->veg[k].veg_kSmax = help_veg[k]; } break; - case 9: + 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) { @@ -214,7 +223,7 @@ void SW_VPD_read(void) { break; /* litter interception parameters */ - case 10: + 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) { @@ -228,7 +237,7 @@ void SW_VPD_read(void) { break; /* parameter for partitioning of bare-soil evaporation and transpiration */ - case 11: + 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) { @@ -242,7 +251,7 @@ void SW_VPD_read(void) { break; /* Parameter for scaling and limiting bare soil evaporation rate */ - case 12: + 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) { @@ -256,7 +265,7 @@ void SW_VPD_read(void) { break; /* shade effects */ - case 13: + 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) { @@ -268,7 +277,7 @@ void SW_VPD_read(void) { v->veg[k].shade_scale = help_veg[k]; } break; - case 14: + 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) { @@ -280,7 +289,7 @@ void SW_VPD_read(void) { v->veg[k].shade_deadmax = help_veg[k]; } break; - case 15: + 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) { @@ -292,7 +301,7 @@ void SW_VPD_read(void) { v->veg[k].tr_shade_effects.xinflec = help_veg[k]; } break; - case 16: + 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) { @@ -304,7 +313,7 @@ void SW_VPD_read(void) { v->veg[k].tr_shade_effects.yinflec = help_veg[k]; } break; - case 17: + 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) { @@ -316,7 +325,7 @@ void SW_VPD_read(void) { v->veg[k].tr_shade_effects.range = help_veg[k]; } break; - case 18: + 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) { @@ -330,7 +339,7 @@ void SW_VPD_read(void) { break; /* Hydraulic redistribution */ - case 19: + 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) { @@ -342,7 +351,7 @@ void SW_VPD_read(void) { v->veg[k].flagHydraulicRedistribution = (Bool) EQ(help_veg[k], 1.); } break; - case 20: + 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) { @@ -354,7 +363,7 @@ void SW_VPD_read(void) { v->veg[k].maxCondroot = help_veg[k]; } break; - case 21: + 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) { @@ -366,7 +375,7 @@ void SW_VPD_read(void) { v->veg[k].swpMatric50 = help_veg[k]; } break; - case 22: + 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) { @@ -380,7 +389,7 @@ void SW_VPD_read(void) { break; /* Critical soil water potential */ - case 23: + 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) { @@ -397,7 +406,7 @@ void SW_VPD_read(void) { /* CO2 Biomass Power Equation */ // Coefficient 1 - case 24: + 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) { @@ -410,7 +419,7 @@ void SW_VPD_read(void) { } break; // Coefficient 2 - case 25: + 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) { @@ -425,7 +434,7 @@ void SW_VPD_read(void) { /* CO2 WUE Power Equation */ // Coefficient 1 - case 26: + 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) { @@ -438,7 +447,7 @@ void SW_VPD_read(void) { } break; // Coefficient 2 - case 27: + 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) { @@ -589,7 +598,9 @@ void SW_VPD_init_run(void) { } } - estimateVegetationFromClimate(veg, model->startyr, model->endyr); + if(veg->veg_method) { + estimateVegetationFromClimate(veg, model->startyr, model->endyr); + } } @@ -857,7 +868,7 @@ void get_critical_rank(void){ @param[in] endYear Ending year of the simulation */ -void estimateVegetationFromClimate(SW_VEGPROD *veg, int startYear, int endYear) { +void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYear) { int numYears = endYear - startYear + 1, deallocate = 0, allocate = 1; diff --git a/SW_VegProd.h b/SW_VegProd.h index e26be9466..5edd39689 100644 --- a/SW_VegProd.h +++ b/SW_VegProd.h @@ -238,7 +238,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,7 +264,7 @@ 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 *veg, int startYear, int endYear); +void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYear); void esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTempMon_C[], double PPTMon_cm[], double inputValues[], double shrubLimit, double SumGrassesFraction, double C4Variables[], Bool fillEmptyWithBareGround, Bool inNorth, Bool warnExtrapolation, diff --git a/testing/Input/veg.in b/testing/Input/veg.in index fd5af9a61..7233ccce9 100755 --- a/testing/Input/veg.in +++ b/testing/Input/veg.in @@ -6,7 +6,11 @@ # USER: Most of the other values in this file are parameters that # describe the four available vegetation types and should not be -# modified unless a vegetation type itself is altered. +# modified unless a vegetation type itself is altered. + +#---- Flag to activate/deactivate estimating vegetation based off weather +1 # 0 - Read in the values from file + # 1 - Estimate vegetation from weather #---- Composition of vegetation type components (0-1; must add up to 1) # Grasses Shrubs Trees Forbs BareGround From f3ff65cc9bcabd698b2b82affad5b8b9a8b37f6a Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 2 Aug 2022 16:57:08 -0400 Subject: [PATCH 129/326] Accommodated to test_severe and set veg values - Modified code to silence "test_severe" - `estimateVegetationFromClimate()` now sets vegetation values in "vegProd" --- SW_VegProd.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index abf99a3b7..9c11ca3ab 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -901,6 +901,11 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe fillEmptyWithBareGround, inNorth, warnExtrapolation, grassOutput, RelAbundanceL0, RelAbundanceL1); + vegProd->veg[SW_TREES].cov.fCover = RelAbundanceL1[0]; + vegProd->veg[SW_SHRUB].cov.fCover = RelAbundanceL1[1]; + vegProd->veg[SW_FORBS].cov.fCover = RelAbundanceL1[2]; + vegProd->veg[SW_GRASS].cov.fCover = RelAbundanceL1[3]; + // Deallocate climate structs' memory allocDeallocClimateStructs(deallocate, numYears, &climateOutput, &climateAverages); @@ -935,7 +940,8 @@ void esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTe double C4Variables[], Bool fillEmptyWithBareGround, Bool inNorth, Bool warnExtrapolation, double *grassOutput, double *RelAbundanceL0, double *RelAbundanceL1) { - int nTypes = 8, winterMonths[3], summerMonths[3]; + 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, @@ -1212,7 +1218,7 @@ void esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTe // Remove them from the `estimIndices` array for(index = 0; index < estimIndicesSize; index++) { do { - isGrassIndex = (overallEstim[index] == grassAnn + isGrassIndex = (Bool) (overallEstim[index] == grassAnn || overallEstim[index] == treeIndex || overallEstim[index] == bareGround); @@ -1306,13 +1312,16 @@ double cutZeroInf(double value) { void uniqueIndices(int arrayOne[], int arrayTwo[], int arrayOneSize, int arrayTwoSize, int *finalIndexArray, int *finalIndexArraySize) { - int index, finalArrayIndex = 0, + int index, finalArrayIndex = 0, nTypes = 8, tempSize = arrayOneSize + arrayTwoSize + finalArrayIndex, tempIndex = 0; - int tempArray[tempSize], tempArraySeen[tempSize]; + int *tempArray, *tempArraySeen; + + tempArray = (int *)malloc(sizeof(int) * tempSize); + tempArraySeen = (int *)malloc(sizeof(int) * nTypes); for(index = 0; index < tempSize; index++) { // Initalize the `seen` version of tempArray - tempArraySeen[index] = 0; + if(index < nTypes) tempArraySeen[index] = 0; if(index < finalArrayIndex) { tempArray[tempIndex] = finalIndexArray[index]; @@ -1339,4 +1348,7 @@ void uniqueIndices(int arrayOne[], int arrayTwo[], int arrayOneSize, int arrayTw *finalIndexArraySize = finalArrayIndex; + free(tempArray); + free(tempArraySeen); + } From ed6d0aaf2786f5ab15d9741f711acb47a209197d Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Tue, 2 Aug 2022 17:15:59 -0400 Subject: [PATCH 130/326] Specify type of soil density inputs - close #280 (Soil density: input either for < 2 mm fraction or for whole soil) - `SW_SITE` gains `type_soilDensityInput` to encode whether soil density inputs represent matric soil density or bulk soil density -> user inputs change `siteparam.in`: new input for `type_soilDensityInput` - `SW_LAYER_INFO` gains new `soilDensityInput` to store soil density inputs (inputs were previously stored as `soilMatric_density`) - new `calculate_soilMatricDensity()` to calculate matric density from bulk density - `SW_SIT_init_run()` now determines both matric soil density and bulk soil density from `soilDensityInput` depending on `type_soilDensityInput` - `set_soillayers()` now expects bulk density inputs as `bd` instead of matric density `matricd` -> previous behavior: input soil density was treated as matric density and converted to bulk density (however, most of the time real word inputs represent bulk density) --- SW_Site.c | 108 +++++++++++++++++++++++++++++-------- SW_Site.h | 10 +++- doc/Doxyfile | 4 +- test/sw_testhelpers.cc | 4 +- test/test_SW_Site.cc | 96 +++++++++++++++++++++++++++++++++ testing/Input/siteparam.in | 4 ++ 6 files changed, 199 insertions(+), 27 deletions(-) diff --git a/SW_Site.c b/SW_Site.c index 5e99509c8..088beeaa3 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -110,7 +110,7 @@ static void _read_layers(void) { FILE *f; LyrIndex lyrno; int x, k; - RealF dmin = 0.0, dmax, evco, trco_veg[NVEGTYPES], psand, pclay, matricd, imperm, + 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. */ @@ -125,7 +125,7 @@ static void _read_layers(void) { inbuf, "%f %f %f %f %f %f %f %f %f %f %f %f", &dmax, - &matricd, + &soildensity, &f_gravel, &evco, &trco_veg[SW_GRASS], &trco_veg[SW_SHRUB], &trco_veg[SW_TREES], &trco_veg[SW_FORBS], @@ -153,7 +153,7 @@ static void _read_layers(void) { dmin = dmax; v->lyr[lyrno]->fractionVolBulk_gravel = f_gravel; - v->lyr[lyrno]->soilMatric_density = matricd; + v->lyr[lyrno]->soilDensityInput = soildensity; v->lyr[lyrno]->evap_coeff = evco; ForEachVegType(k) @@ -370,18 +370,49 @@ void water_eqn(RealD fractionGravel, RealD sand, RealD clay, LyrIndex n) { /** @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 @@ -675,8 +706,12 @@ 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; + default: - if (lineno > 40 + MAX_TRANSP_REGIONS) + if (lineno > 41 + MAX_TRANSP_REGIONS) break; /* skip extra lines */ if (MAX_TRANSP_REGIONS < v->n_transp_rgn) { @@ -732,8 +767,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 +800,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 +824,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]; @@ -955,9 +990,12 @@ void SW_SIT_init_run(void) { fval = lyr->width; errtype = Str_Dup("layer width"); - } else if (LT(lyr->soilMatric_density, 0.)) { + } else if ( + LT(lyr->soilDensityInput, 0.) || + GT(lyr->soilDensityInput, 2.65) + ) { fail = swTRUE; - fval = lyr->soilMatric_density; + fval = lyr->soilDensityInput; errtype = Str_Dup("soil density"); } else if ( @@ -1028,11 +1066,39 @@ void SW_SIT_init_run(void) { } - /* Update soil density for gravel */ - lyr->soilBulk_density = calculate_soilBulkDensity( - lyr->soilMatric_density, - lyr->fractionVolBulk_gravel - ); + /* Update soil density depending on inputs */ + switch (SW_Site.type_soilDensityInput) { + + 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; + + default: + LogError( + logfp, + LOGFATAL, + "Soil density type not recognized", + SW_Site.type_soilDensityInput + ); + } + + /* Calculate pedotransfer function parameters */ water_eqn( diff --git a/SW_Site.h b/SW_Site.h index 9b7ad141d..6008c0f04 100644 --- a/SW_Site.h +++ b/SW_Site.h @@ -53,6 +53,8 @@ extern "C" { #endif +#define SW_MATRIC 0 +#define SW_BULK 1 typedef unsigned int LyrIndex; @@ -63,7 +65,7 @@ typedef struct { 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 +75,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] */ @@ -105,6 +108,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 */ @@ -166,6 +171,7 @@ extern RealD _SWCInitVal, _SWCWetVal, _SWCMinVal; /* --------------------------------------------------- */ void water_eqn(RealD fractionGravel, RealD sand, RealD clay, LyrIndex n); 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[]); @@ -181,7 +187,7 @@ 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/doc/Doxyfile b/doc/Doxyfile index 42e2fba40..36aeabcff 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -1666,8 +1666,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/test/sw_testhelpers.cc b/test/sw_testhelpers.cc index c34054d0a..8999d7c55 100644 --- a/test/sw_testhelpers.cc +++ b/test/sw_testhelpers.cc @@ -68,7 +68,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 +111,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/test_SW_Site.cc b/test/test_SW_Site.cc index 7b8f97fd1..a4d34bc62 100644 --- a/test/test_SW_Site.cc +++ b/test/test_SW_Site.cc @@ -200,4 +200,100 @@ namespace { 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), + "@ generic.c LogError" + ); + + + // Check error if type_soilDensityInput not implemented + SW_Site.type_soilDensityInput = SW_MISSING; + + EXPECT_DEATH_IF_SUPPORTED( + SW_SIT_init_run(), + "@ generic.c LogError" + ); + + + // Reset to previous global states + Reset_SOILWAT2_after_UnitTest(); + } } // namespace diff --git a/testing/Input/siteparam.in b/testing/Input/siteparam.in index 28b936e99..e019ee2bf 100644 --- a/testing/Input/siteparam.in +++ b/testing/Input/siteparam.in @@ -83,6 +83,10 @@ 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 + #---- Transpiration regions # ndx : 1=shallow, 2=medium, 3=deep, 4=very deep # layer: deepest soil layer number of the region. From cb5377c20ae283ebadf6ccfe782d1d785c6d1ada Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Thu, 4 Aug 2022 10:16:38 -0400 Subject: [PATCH 131/326] Add citations and references to `estimatePotNatVegComposition()` --- SW_VegProd.c | 6 ++++-- doc/Doxyfile | 4 ++-- doc/SOILWAT2.bib | 22 ++++++++++++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index 9c11ca3ab..3339f4bb1 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -601,7 +601,7 @@ void SW_VPD_init_run(void) { if(veg->veg_method) { estimateVegetationFromClimate(veg, model->startyr, model->endyr); } - + } @@ -932,7 +932,9 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe @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 a process that is specified in Teeri JA, Stowe LG (1976) and equations from Paruelo & Lauenroth (1996) + @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 esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTempMon_C[], diff --git a/doc/Doxyfile b/doc/Doxyfile index 42e2fba40..36aeabcff 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -1666,8 +1666,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 0d2709150..7e7e6240b 100644 --- a/doc/SOILWAT2.bib +++ b/doc/SOILWAT2.bib @@ -332,3 +332,25 @@ @article{huang2018JAMC publisher = {American Meteorological Society}, doi = {10.1175/JAMC-D-17-0334.1} } + +@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} +} From 5ad9684818ac95c8f08e8acd1bd768ff142b8228 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Sun, 7 Aug 2022 16:09:13 -0400 Subject: [PATCH 132/326] New `has_swrcp` and eliminate "NoPDF" - concern: the same variable `pdf_name` attempted to encode both (i) the name of the pedotransfer function and (ii) the information whether `SWRCp` values should be estimated during run-time or not * previously, the value "NoPDF" informed SOILWAT2 that `SWRCp` was not to be estimated during run-time * issue: once `pdf_name` was set to "NoPDF", SOILWAT2 and downstream applications lost all information about the utilized pedotransfer function, e.g., ** SOILWAT2 couldn't properly determine saturated water content for Campbell1974/Cosby1984AndOthers ** downstream applications couldn't use the selected pedotransfer function for additional estimation of `SWRCp` -> separate the two types of information: keep `pdf_name` (eliminate "NoPDF") and add new `has_swrcp` - `site_has_swrcp` is a new user input from "siteparam.in" * 0: Estimate with specified pedotransfer function * 1: Use values from "swrc_params.in" -> this approach allows to pass values for `pdf_name` that are not implemented as long as `SWRCp` are provided (and `site_has_swrcp` set to TRUE) -> this approach eliminate the need that SOILWAT2 includes the pedotransfer functions implemented by rSOILWAT2; this simplifies several elements and functions including: * `pdf2str` * `SWRC_PDF_estimate_parameters()` * `check_SWRC_vs_PDF()` -> increase readability of switch-statements that select SWRC or PDF: new macros that encode indices for each implemented SWRC and PDF -> update unit tests * simplified `pdf_name` start now at index 0 * only check those PDFs implemented in SOILWAT2 * utilize new `site_has_swrcp` --- SW_Site.c | 136 ++++++++++++------------------- SW_Site.h | 35 +++++--- SW_SoilWater.c | 12 +-- test/test_SW_Site.cc | 43 +++------- test/test_SW_SoilWater.cc | 6 +- test/test_WaterBalance.cc | 6 +- testing/Input/siteparam.in | 37 +++++---- testing/Input/swrc_params.in | 2 +- testing/Input/swrc_params_FXW.in | 2 +- 9 files changed, 122 insertions(+), 157 deletions(-) diff --git a/SW_Site.c b/SW_Site.c index 3fcbbd09f..f8e8c1adb 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -95,6 +95,7 @@ RealD @note Code maintenance: - Values must exactly match those provided in `siteparam.in`. + - Order must exactly match "indices of `swrc2str`" - See details in section #swrc_pdf */ char const *swrc2str[N_SWRCs] = { @@ -107,15 +108,13 @@ char const *swrc2str[N_SWRCs] = { @note Code maintenance: - Values must exactly match those provided in `siteparam.in`. - - The first value must be "NoPDF". + - Order must exactly match "indices of `pdf2str`" - See details in section #swrc_pdf + - `rSOILWAT2` may implemented additional PDFs */ char const *pdf2str[N_PDFs] = { - "NoPDF", "Cosby1984AndOthers", - "Cosby1984", - "Rosetta3", - "neuroFX2021" + "Cosby1984" }; @@ -336,6 +335,7 @@ static double ui_theta_min( @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 @@ -369,7 +369,8 @@ unsigned int encode_str2swrc(char *swrc_name) { @param[in] *pdf_name Name of a PDF - @return Internal identification number of selected PDF + @return Internal identification number of selected PDF; + #SW_MISSING if not implemented. */ unsigned int encode_str2pdf(char *pdf_name) { unsigned int k; @@ -381,12 +382,7 @@ unsigned int encode_str2pdf(char *pdf_name) { ); if (k == N_PDFs) { - LogError( - logfp, - LOGFATAL, - "PDF '%s' is not implemented.", - pdf_name - ); + k = (unsigned int) SW_MISSING; } return k; @@ -420,35 +416,22 @@ void SWRC_PDF_estimate_parameters( double bdensity ) { - if (pdf_type == 0) { + /* Initialize swrcp[] to 0 */ + memset(swrcp, 0., SWRC_PARAM_NMAX * sizeof(swrcp[0])); + + if (pdf_type == sw_Cosby1984AndOthers) { + SWRC_PDF_Cosby1984_for_Campbell1974(swrcp, sand, clay); + + } else if (pdf_type == sw_Cosby1984) { + SWRC_PDF_Cosby1984_for_Campbell1974(swrcp, sand, clay); + + } else { LogError( logfp, - LOGNOTE, - "`SWRC_PDF_estimate_parameters()` was called even though " - "`pdf_type` %d was requested " - "(which uses values from 'swrc_params.in' instead of estimation).", + LOGFATAL, + "PDF is not implemented in SOILWAT2.", pdf_type ); - - } else { - - /* Initialize swrcp[] to 0 */ - memset(swrcp, 0., SWRC_PARAM_NMAX * sizeof(swrcp[0])); - - if (pdf_type == 1) { - SWRC_PDF_Cosby1984_for_Campbell1974(swrcp, sand, clay); - - } else if (pdf_type == 2) { - SWRC_PDF_Cosby1984_for_Campbell1974(swrcp, sand, clay); - - } else { - LogError( - logfp, - LOGFATAL, - "PDF (type %d) is not implemented in SOILWAT2.", - pdf_type - ); - } } /**********************************/ @@ -547,8 +530,8 @@ double SW_swcBulk_saturated( double theta_sat = SW_MISSING; switch (swrc_type) { - case 0: // Campbell1974 - if (pdf_type == 1) { + case sw_Campbell1974: + if (pdf_type == sw_Cosby1984AndOthers) { // Cosby1984AndOthers (backwards compatible) PDF_Saxton2006(&theta_sat, sand, clay); } else { @@ -556,11 +539,11 @@ double SW_swcBulk_saturated( } break; - case 1: // vanGenuchten1980 + case sw_vanGenuchten1980: theta_sat = swrcp[1]; break; - case 2: // FXW + case sw_FXW: theta_sat = swrcp[0]; break; @@ -624,15 +607,15 @@ double SW_swcBulk_minimum( /* `theta_min` based on theoretical SWRC */ switch (swrc_type) { - case 0: // Campbell1974: phi = infinity at theta_min + case sw_Campbell1974: // phi = infinity at theta_min theta_min_theoretical = 0.; break; - case 1: // vanGenuchten1980: phi = infinity at theta_min + case sw_vanGenuchten1980: // phi = infinity at theta_min theta_min_theoretical = swrcp[0]; break; - case 2: // FXW: phi = 6.3 x 10^6 cm at theta_min + case sw_FXW: // phi = 6.3 x 10^6 cm at theta_min theta_min_theoretical = 0.; break; @@ -656,9 +639,9 @@ double SW_swcBulk_minimum( swcBulk_sat, swrc_type, swrcp, - // `pdf_type == 1` (Cosby1984AndOthers) doesn't work for unit test: + // `(Bool) pdf_type == sw_Cosby1984AndOthers` doesn't work for unit test: // error: "no known conversion from 'bool' to 'Bool'" - pdf_type == 1 ? swTRUE : swFALSE + pdf_type == sw_Cosby1984AndOthers ? swTRUE : swFALSE ); /* `theta_min_sim` must be strictly larger than `theta_min_theoretical` */ @@ -678,17 +661,13 @@ double SW_swcBulk_minimum( @param[in] *swrc_name Name of selected SWRC @param[in] *pdf_name Name of selected PDF - @param[in] isSW2 Logical; TRUE if scope of PDF implementation is "SOILWAT2". @return A logical value indicating if SWRC and PDF are compatible. */ -Bool check_SWRC_vs_PDF(char *swrc_name, char *pdf_name, Bool isSW2) { +Bool check_SWRC_vs_PDF(char *swrc_name, char *pdf_name) { Bool res = swFALSE; - if (Str_CompareI(pdf_name, (char *) "NoPDF") == 0) { - res = swTRUE; - } else { - + if (!missing((double) encode_str2pdf(pdf_name))) { if ( Str_CompareI(swrc_name, (char *) "Campbell1974") == 0 && ( @@ -698,22 +677,6 @@ Bool check_SWRC_vs_PDF(char *swrc_name, char *pdf_name, Bool isSW2) { ) { res = swTRUE; } - else if ( - !isSW2 && - Str_CompareI(swrc_name, (char *) "vanGenuchten1980") == 0 && - // "Rosetta3" PDF is not implemented in SOILWAT2 (but in rSOILWAT2) - Str_CompareI(pdf_name, (char *) "Rosetta3") == 0 - ) { - res = swTRUE; - } - else if ( - !isSW2 && - Str_CompareI(swrc_name, (char *) "FXW") == 0 && - // "neuroFX2021" PDF is not implemented in SOILWAT2 (but in rSOILWAT2) - Str_CompareI(pdf_name, (char *) "neuroFX2021") == 0 - ) { - res = swTRUE; - } } return res; @@ -734,15 +697,15 @@ Bool SWRC_check_parameters(unsigned int swrc_type, double *swrcp) { Bool res = swFALSE; switch (swrc_type) { - case 0: + case sw_Campbell1974: res = SWRC_check_parameters_for_Campbell1974(swrcp); break; - case 1: + case sw_vanGenuchten1980: res = SWRC_check_parameters_for_vanGenuchten1980(swrcp); break; - case 2: + case sw_FXW: res = SWRC_check_parameters_for_FXW(swrcp); break; @@ -1520,9 +1483,12 @@ void SW_SIT_read(void) { strcpy(v->site_pdf_name, inbuf); v->site_pdf_type = encode_str2pdf(v->site_pdf_name); break; + case 43: + v->site_has_swrcp = itob(atoi(inbuf)); + break; default: - if (lineno > 42 + MAX_TRANSP_REGIONS) + if (lineno > 43 + MAX_TRANSP_REGIONS) break; /* skip extra lines */ if (MAX_TRANSP_REGIONS < v->n_transp_rgn) { @@ -1863,7 +1829,7 @@ void derive_soilRegions(int nRegions, RealD *regionLowerBounds){ void SW_SWRC_read(void) { /* Don't read values from disk if they will be estimated via a PDF */ - if (SW_Site.site_pdf_type != 0) return; + if (!SW_Site.site_has_swrcp) return; FILE *f; LyrIndex lyrno = 0, k; @@ -1972,14 +1938,16 @@ void SW_SIT_init_run(void) { /* Check compatibility between selected SWRC and PDF */ - if (!check_SWRC_vs_PDF(sp->site_swrc_name, sp->site_pdf_name, swTRUE)) { - LogError( - logfp, - LOGFATAL, - "Selected PDF '%s' is incompatible with selected SWRC '%s'\n", - sp->site_pdf_name, - sp->site_swrc_name - ); + if (!sp->site_has_swrcp) { + if (!check_SWRC_vs_PDF(sp->site_swrc_name, sp->site_pdf_name)) { + LogError( + logfp, + LOGFATAL, + "Selected PDF '%s' is incompatible with selected SWRC '%s'\n", + sp->site_pdf_name, + sp->site_swrc_name + ); + } } @@ -2014,10 +1982,10 @@ void SW_SIT_init_run(void) { ); - if (lyr->pdf_type > 0) { + if (!sp->site_has_swrcp) { /* Use pedotransfer function PDF - estimate parameters of soil water retention curve (SWRC) for layer. - If pdf_type == 0, then the parameters were already obtained from disk + 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_PDF_estimate_parameters( diff --git a/SW_Site.h b/SW_Site.h index 20eddca0b..e2cb71981 100644 --- a/SW_Site.h +++ b/SW_Site.h @@ -72,7 +72,7 @@ extern "C" { (see input file `siteparam.in`); SWRC parameters can be estimated at run-time from soil properties by selecting a matching PDF (see input file `siteparam.in`) or, - alternatively ("NoPDF"), provide adequate SWRC parameter values + alternatively (`has_swrcp`), provide adequate SWRC parameter values (see input file `swrc_params.in`). Please note that `rSOILWAT2` may provide additional PDF functionality. @@ -80,16 +80,17 @@ extern "C" { __Approach__ -# User selections of SWRC and PDF are read in from input file `siteparam.in` - by `SW_SIT_read()` and, if "NoPDF", SWRC parameters are read from + 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()` - - calls `check_SWRC_vs_PDF()` to check that selected SWRC and PDF are - compatible - - calls `SWRC_PDF_estimate_parameters()` to estimate SWRC parameter values - from soil properties based on selected PDF (unless "NoPDF") + - if not `has_swrcp` + - calls `check_SWRC_vs_PDF()` to check that selected SWRC and PDF are + compatible + - calls `SWRC_PDF_estimate_parameters()` to estimate + SWRC parameter values from soil properties based on selected PDF - calls `SWRC_check_parameters()` to check that SWRC parameter values - are resonable for the selected SWRC + 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. @@ -106,7 +107,8 @@ extern "C" { -# Update #N_SWRCs and #N_PDFs - -# Add new names to #swrc2str and #pdf2str + -# Add new names to #swrc2str and #pdf2str and + add corresponding macros of indices -# Update input files `siteparam.in` and `swrc_params.in` @@ -135,8 +137,17 @@ extern "C" { */ #define SWRC_PARAM_NMAX 6 /**< Maximal number of SWRC parameters implemented */ -#define N_SWRCs 3 /**< Number of implemented SWRCs */ -#define N_PDFs 5 /**< Number of implemented PDFs */ +#define N_SWRCs 3 /**< Number of SWRCs implemented by SOILWAT2 */ +#define N_PDFs 2 /**< Number of PDFs 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 */ @@ -254,6 +265,8 @@ typedef struct { site_swrc_name[64], site_pdf_name[64]; + Bool site_has_swrcp; /**< Are `swrcp` already (TRUE) or not yet estimated (FALSE)? */ + } SW_SITE; @@ -291,7 +304,7 @@ void SWRC_PDF_Cosby1984_for_Campbell1974( ); -Bool check_SWRC_vs_PDF(char *swrc_name, char *pdf_name, Bool isSW2); +Bool check_SWRC_vs_PDF(char *swrc_name, char *pdf_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); diff --git a/SW_SoilWater.c b/SW_SoilWater.c index 13961653f..7cb1bd3af 100644 --- a/SW_SoilWater.c +++ b/SW_SoilWater.c @@ -1315,21 +1315,21 @@ double SWRC_SWCtoSWP( } switch (swrc_type) { - case 0: + case sw_Campbell1974: res = SWRC_SWCtoSWP_Campbell1974( swcBulk, swrcp, gravel, width, errmode ); break; - case 1: + case sw_vanGenuchten1980: res = SWRC_SWCtoSWP_vanGenuchten1980( swcBulk, swrcp, gravel, width, errmode ); break; - case 2: + case sw_FXW: res = SWRC_SWCtoSWP_FXW( swcBulk, swrcp, gravel, width, errmode @@ -1657,15 +1657,15 @@ double SWRC_SWPtoSWC( } switch (swrc_type) { - case 0: + case sw_Campbell1974: res = SWRC_SWPtoSWC_Campbell1974(swpMatric, swrcp, gravel, width); break; - case 1: + case sw_vanGenuchten1980: res = SWRC_SWPtoSWC_vanGenuchten1980(swpMatric, swrcp, gravel, width); break; - case 2: + case sw_FXW: res = SWRC_SWPtoSWC_FXW(swpMatric, swrcp, gravel, width); break; diff --git a/test/test_SW_Site.cc b/test/test_SW_Site.cc index 91683094d..c9615480e 100644 --- a/test/test_SW_Site.cc +++ b/test/test_SW_Site.cc @@ -52,7 +52,7 @@ namespace { }; const char *ns_pdfc2vG1980[] = { "vanGenuchten1980" - // PDFs implemented in C + // PDFs implemented in SOILWAT2 }; const char *ns_pdfa2FXW[] = { "FXW" @@ -61,7 +61,7 @@ namespace { }; const char *ns_pdfc2FXW[] = { "FXW" - // PDFs implemented in C + // PDFs implemented in SOILWAT2 }; // Test pedotransfer functions @@ -153,89 +153,72 @@ namespace { TEST(SiteTest, PDF2SWRC) { unsigned int k; // `length()` returns "unsigned long" - // Matching/incorrect PDF-SWRC pairs - // (k starts at 1 because 0 holds the SWRC) - for (k = 0; k < N_SWRCs; k++) { - EXPECT_TRUE( - (bool) check_SWRC_vs_PDF((char *) swrc2str[k], (char *) "NoPDF", swTRUE) - ); - } - for (k = 1; k < length(ns_pdfca2C1974); k++) { EXPECT_TRUE( (bool) check_SWRC_vs_PDF( (char *) ns_pdfca2C1974[0], - (char *) ns_pdfca2C1974[k], - swTRUE + (char *) ns_pdfca2C1974[k] ) ); EXPECT_FALSE( (bool) check_SWRC_vs_PDF( (char *) ns_pdfa2vG1980[0], - (char *) ns_pdfca2C1974[k], - swTRUE + (char *) ns_pdfca2C1974[k] ) ); EXPECT_FALSE( (bool) check_SWRC_vs_PDF( (char *) ns_pdfa2FXW[0], - (char *) ns_pdfca2C1974[k], - swTRUE + (char *) ns_pdfca2C1974[k] ) ); } for (k = 1; k < length(ns_pdfa2vG1980); k++) { - EXPECT_TRUE( + EXPECT_FALSE( (bool) check_SWRC_vs_PDF( (char *) ns_pdfa2vG1980[0], - (char *) ns_pdfa2vG1980[k], - swFALSE + (char *) ns_pdfa2vG1980[k] ) ); EXPECT_FALSE( (bool) check_SWRC_vs_PDF( (char *) ns_pdfca2C1974[0], - (char *) ns_pdfa2vG1980[k], - swFALSE + (char *) ns_pdfa2vG1980[k] ) ); EXPECT_FALSE( (bool) check_SWRC_vs_PDF( (char *) ns_pdfa2FXW[0], - (char *) ns_pdfa2vG1980[k], - swFALSE + (char *) ns_pdfa2vG1980[k] ) ); } for (k = 1; k < length(ns_pdfa2FXW); k++) { - EXPECT_TRUE( + EXPECT_FALSE( (bool) check_SWRC_vs_PDF( (char *) ns_pdfa2FXW[0], - (char *) ns_pdfa2FXW[k], - swFALSE + (char *) ns_pdfa2FXW[k] ) ); EXPECT_FALSE( (bool) check_SWRC_vs_PDF( (char *) ns_pdfca2C1974[0], - (char *) ns_pdfa2FXW[k], - swFALSE + (char *) ns_pdfa2FXW[k] ) ); EXPECT_FALSE( (bool) check_SWRC_vs_PDF( (char *) ns_pdfa2vG1980[0], - (char *) ns_pdfa2FXW[k], - swFALSE + (char *) ns_pdfa2FXW[k] ) ); } diff --git a/test/test_SW_SoilWater.cc b/test/test_SW_SoilWater.cc index 2429bd253..3a6df814d 100644 --- a/test/test_SW_SoilWater.cc +++ b/test/test_SW_SoilWater.cc @@ -101,13 +101,11 @@ namespace{ memset(swrcp, 0., SWRC_PARAM_NMAX * sizeof(swrcp[0])); // Find a suitable PDF to generate `SWRCp` - // (start `pdf_type` at 1 because 0 codes to "NoPDF") for ( - pdf_type = 1; + pdf_type = 0; pdf_type < N_PDFs && !check_SWRC_vs_PDF( (char *) swrc2str[swrc_type], - (char *) pdf2str[pdf_type], - swTRUE + (char *) pdf2str[pdf_type] ); pdf_type++ ) {} diff --git a/test/test_WaterBalance.cc b/test/test_WaterBalance.cc index ca11123da..828a32612 100644 --- a/test/test_WaterBalance.cc +++ b/test/test_WaterBalance.cc @@ -195,8 +195,9 @@ namespace { // Set SWRC and PDF (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_pdf_name, (char *) "NoPDF"); + strcpy(SW_Site.site_pdf_name, (char *) "Rosetta3"); SW_Site.site_pdf_type = encode_str2pdf(SW_Site.site_pdf_name); + SW_Site.site_has_swrcp = swTRUE; Mem_Free(InFiles[eSWRCp]); InFiles[eSWRCp] = Str_Dup("Input/swrc_params_vanGenuchten1980.in"); @@ -229,8 +230,9 @@ namespace { // Set SWRC and PDF (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_pdf_name, (char *) "NoPDF"); + strcpy(SW_Site.site_pdf_name, (char *) "neuroFX2021"); SW_Site.site_pdf_type = encode_str2pdf(SW_Site.site_pdf_name); + SW_Site.site_has_swrcp = swTRUE; Mem_Free(InFiles[eSWRCp]); InFiles[eSWRCp] = Str_Dup("Input/swrc_params_FXW.in"); diff --git a/testing/Input/siteparam.in b/testing/Input/siteparam.in index 2ac096f7a..fb715928f 100644 --- a/testing/Input/siteparam.in +++ b/testing/Input/siteparam.in @@ -9,7 +9,10 @@ #---- 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 +-1.0 # swc_min : cm/cm if 0 - <1.0, + # -bars if >= 1.0.; + # if < 0. then estimate residual water content for each layer + # (from realistic SWP limit, or Rawls et al. 1985) 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. @@ -87,25 +90,23 @@ RCP85 #--- Soil water retention curve (SWRC) ------ # # Implemented options (`swrc_name`/`pdf_name`, see `swrc2str[]`/`pdf2str[]`): -# - pdf_name = "NoPDF": SWRC parameters from input file "swrc_params.in" -# - swrc_name = "Campbell1974": (Campbell 1974) -# * pdf_name = "Cosby1984AndOthers": PDFs by Cosby et al. 1984 -# (but `swc_sat` by Saxton et al. 2006, -# `swc_min` by user input, realistic SWP limit, or Rawls et al. 1985) -# * pdf_name = "Cosby1984": PDFs by Cosby et al. 1984 -# - swrc_name = "vanGenuchten1980": van Genuchten-Mualem (van Genuchten 1980) -# * pdf_name = "Rosetta3": not implemented in C (but see rSOILWAT2) -## - swrc_name = "BrooksCorey1964": (Brooks and Corey 1964) -## * pdf_name: currently, no implemented PDF -## - swrc_name = "Brunswick": (Weber et al. 2019) -## * pdf_name: currently, no implemented PDF -# - swrc_name = "FXW": (Fredlund and Xing 1994, Wang et al. 2018) -# * pdf_name: "neuroFX2021": not implemented PDF in C (but see rSOILWAT2) +# - pdf_name = : SWRC parameters must be provided via "swrc_params.in" +# - swrc_name = "Campbell1974" (Campbell 1974) +# * pdf_name = "Cosby1984AndOthers" (Cosby et al. 1984 but `swc_sat` by Saxton et al. 2006) +# * pdf_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 -# -Campbell1974 # Select one of the implemented SWRCs -Cosby1984AndOthers # Select one of the implemented PDFs (or "NoPDF") +# Note: `rSOILWAT2` may implement additional PDFs + +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 diff --git a/testing/Input/swrc_params.in b/testing/Input/swrc_params.in index d9003860b..f05fb9e02 100644 --- a/testing/Input/swrc_params.in +++ b/testing/Input/swrc_params.in @@ -6,7 +6,7 @@ # selected SWRC (see `siteparam.in`) # - unused columns are ignored (if selected SWRC uses fewer than 6 parameters) -# swrc = "Campbell1974" (default values below) +# 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 [-] diff --git a/testing/Input/swrc_params_FXW.in b/testing/Input/swrc_params_FXW.in index 8d916e148..7f1bc8df3 100644 --- a/testing/Input/swrc_params_FXW.in +++ b/testing/Input/swrc_params_FXW.in @@ -6,7 +6,7 @@ # selected SWRC (see `siteparam.in`) # - unused columns are ignored (if selected SWRC uses fewer than 6 parameters) -# swrc = "FXW" +# 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 [-] From 4ef8100168967e38f820c5edd4ea2308eee1fe94 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 7 Aug 2022 18:02:11 -0400 Subject: [PATCH 133/326] Cleaned up new vegetation in `SW_VegProd.c` - `estimateVegetationFromClimate()` is called when "veg_method" is greater than zero - `estimateVegetationFromClimate()` related data is set when "veg_method" is one - Fixed `estimateVegetationFromClimate()` name - Removed unnecessary code (mainly with "estimIndicesNotNA" and "finalVegSum") - Added calls to `cutZeroInf()` when needed - Added missing error message - Updated documentation of `estimateVegetationFromClimate()`, `cutZeroInf()` and `uniqueIndices()` --- SW_VegProd.c | 126 +++++++++++++++++++++++++++++---------------------- SW_VegProd.h | 4 +- 2 files changed, 73 insertions(+), 57 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index 3339f4bb1..2605ae392 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -583,11 +583,13 @@ void SW_VPD_construct(void) { void SW_VPD_init_run(void) { TimeInt year; - int k; + int k, veg_method; SW_VEGPROD *veg = &SW_VegProd; SW_MODEL *model = &SW_Model; + veg_method = veg->veg_method; + /* Set co2-multipliers to default */ for (year = 0; year < MAX_NYEAR; year++) { @@ -598,8 +600,8 @@ void SW_VPD_init_run(void) { } } - if(veg->veg_method) { - estimateVegetationFromClimate(veg, model->startyr, model->endyr); + if(veg_method > 0) { + estimateVegetationFromClimate(veg, model->startyr, model->endyr, veg_method); } } @@ -863,14 +865,16 @@ void get_critical_rank(void){ /** @brief Wrapper function for estimating natural vegetation. First, climate is calculated and averaged, then values are estimated - @param[in] veg Structure holding all values for vegetation cover of simulation + @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 */ -void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYear) { +void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYear, int veg_method) { - int numYears = endYear - startYear + 1, deallocate = 0, allocate = 1; + int numYears = endYear - startYear + 1, deallocate = 0, allocate = 1, k; SW_CLIMATE_YEARLY climateOutput; SW_CLIMATE_CLIM climateAverages; @@ -880,7 +884,7 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe double coverValues[8] = {SW_MISSING, SW_MISSING, SW_MISSING, SW_MISSING, SW_MISSING, SW_MISSING, SW_MISSING, SW_MISSING}, shrubLimit = .2; - double SumGrassesFraction = -SW_MISSING, C4Variables[3], grassOutput[3], + double SumGrassesFraction = SW_MISSING, C4Variables[3], grassOutput[3], RelAbundanceL0[8], RelAbundanceL1[5]; Bool fillEmptyWithBareGround = swTRUE, inNorth = swTRUE, warnExtrapolation = swTRUE; @@ -892,19 +896,21 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe averageClimateAcrossYears(&climateOutput, numYears, &climateAverages); - C4Variables[0] = climateAverages.minTempJuly_C; - C4Variables[1] = climateAverages.ddAbove65F_degday; - C4Variables[2] = climateAverages.frostFree_days; + if(veg_method == 1) { - esimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, - climateAverages.PPTMon_cm, coverValues, shrubLimit, SumGrassesFraction, C4Variables, - fillEmptyWithBareGround, inNorth, warnExtrapolation, - grassOutput, RelAbundanceL0, RelAbundanceL1); + C4Variables[0] = climateAverages.minTempJuly_C; + C4Variables[1] = climateAverages.ddAbove65F_degday; + C4Variables[2] = climateAverages.frostFree_days; - vegProd->veg[SW_TREES].cov.fCover = RelAbundanceL1[0]; - vegProd->veg[SW_SHRUB].cov.fCover = RelAbundanceL1[1]; - vegProd->veg[SW_FORBS].cov.fCover = RelAbundanceL1[2]; - vegProd->veg[SW_GRASS].cov.fCover = RelAbundanceL1[3]; + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, coverValues, + shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, + inNorth, warnExtrapolation, grassOutput, RelAbundanceL0, RelAbundanceL1); + + ForEachVegType(k) { + vegProd->veg[k].cov.fCover = RelAbundanceL1[k]; + } + } // Deallocate climate structs' memory allocDeallocClimateStructs(deallocate, numYears, &climateOutput, &climateAverages); @@ -912,14 +918,35 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe } /** - @brief Using the data calculated in `calcSiteCliamte()`, estimates the natural vegetation composition of a site + @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. - @param[in] meanTemp_C Value containing the average of yearly temperatures [C] - @param[in] PPT_cm Value containing the average of yearly precipitation [cm] - @param[in] meanTempMon_C Array of size MAX_MONTHS containing sum of monthly mean temperatures [C] + `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 starting values for the function to start with - @param[in] shrubLimit Value containing max allowed amount of shrubs + @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 grass if user would like it to be fixed @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 @@ -937,7 +964,7 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe for C4 grasses, an equation by Teeri & Stowe (1976) @cite teeri1976O. */ -void esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTempMon_C[], +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 inNorth, Bool warnExtrapolation, double *grassOutput, double *RelAbundanceL0, double *RelAbundanceL1) { @@ -982,6 +1009,7 @@ void esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTe iFixed[iFixedSize] = index; iFixedSize++; estimCover[index] = inputValues[index]; + estimIndicesNotNA++; } else { overallEstim[overallEstimSize] = index; overallEstimSize++; @@ -990,13 +1018,13 @@ void esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTe } fixSumGrasses = (Bool) (!missing(inputValues[grassAnn]) && !missing(inputValues[C3Index]) - && !missing(inputValues[C3Index]) && SumGrassesFraction >= 0); + && !missing(inputValues[C3Index]) && !missing(SumGrassesFraction)); // Check if grasses are fixed if(fixSumGrasses) { // Set SumGrassesFraction // If SumGrassesFraction < 0, set to zero, otherwise keep at value - SumGrassesFraction = (SumGrassesFraction < 0) ? 0 : SumGrassesFraction; + SumGrassesFraction = (missing(SumGrassesFraction)) ? 0 : SumGrassesFraction; // Get sum of input grass values and set to inputSumGrasses for(index = C3Index; index < grassAnn; index++) { if(!missing(inputValues[index])) inputSumGrasses += inputValues[index]; @@ -1007,7 +1035,9 @@ void esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTe // Check if totalSumGrasses is less than zero if(totalSumGrasses < 0){ - // Throw error and stop + 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++) { @@ -1067,21 +1097,21 @@ void esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTe for(index = 0; index < 3; index++) { winterMonths[index] = (index + 11) % 12; summerMonths[index] = (index + 5); - summerMAP += PPTMon_cm[summerMonths[index]] * 10; - winterMAP += PPTMon_cm[winterMonths[index]] * 10; + summerMAP += PPTMon_cm[summerMonths[index]]; + winterMAP += PPTMon_cm[winterMonths[index]]; } } else { for(index = 0; index < 3; index++) { summerMonths[index] = (index + 11) % 12; winterMonths[index] = (index + 5); - summerMAP += PPTMon_cm[summerMonths[index]] * 10; - winterMAP += PPTMon_cm[winterMonths[index]] * 10; + summerMAP += PPTMon_cm[summerMonths[index]]; + winterMAP += PPTMon_cm[winterMonths[index]]; } } } // Set summer and winter precipitations in mm - summerMAP /= (PPT_cm * 10); - winterMAP /= (PPT_cm * 10); + summerMAP /= PPT_cm; + winterMAP /= PPT_cm; // Get the difference between July and Janurary tempDiffJanJul = meanTempMon_C[summerMonths[1]] - @@ -1126,13 +1156,13 @@ void esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTe } // This equations give percent species/vegetation -> use to limit // Paruelo's C4 equation, i.e., where no C4 species => C4 abundance == 0 - if(C4Variables != NULL && !fullVeg) { + if(!missing(C4Variables[0]) && !fullVeg) { if(C4Variables[frostFreeDays] <= 0) { C4Species = 0; } else { - C4Species = ((1.6 * (C4Variables[julyMin] * 9 / 5 + 32)) + + C4Species = cutZeroInf(((1.6 * (C4Variables[julyMin] * 9 / 5 + 32)) + (.0086 * (C4Variables[degreeAbove65] * 9 / 5)) - - (8.98 * log(C4Variables[frostFreeDays])) - 22.44) / 100; + - (8.98 * log(C4Variables[frostFreeDays])) - 22.44) / 100); } } @@ -1142,11 +1172,8 @@ void esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTe if(winterMAP <= 0) { C3Grassland = C3Shrubland = 0; } else { - C3Grassland = 1.1905 - .02909 * meanTemp_C + .1781 * log(winterMAP) - .2383; - C3Shrubland = 1.1905 - .02909 * meanTemp_C + .1781 * log(winterMAP) - .2383 * 2; - - if(C3Grassland < 0.) C3Grassland = 0.; - if(C3Shrubland < 0.) C3Shrubland = 0.; + C3Grassland = cutZeroInf(1.1905 - .02909 * meanTemp_C + .1781 * log(winterMAP) - .2383); + C3Shrubland = cutZeroInf(1.1905 - .02909 * meanTemp_C + .1781 * log(winterMAP) - .2383 * 2); } if(!missing(estimCover[shrubIndex]) && estimCover[shrubIndex] >= shrubLimit) { @@ -1177,13 +1204,6 @@ void esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTe } } } - for(index = 0; index < overallEstimSize; index++) { - if(missing(estimCover[overallEstim[index]])) { - estimCover[overallEstim[index]] = 0.; - } else { - estimIndicesNotNA++; - } - } if(!fillEmptyWithBareGround && estimIndicesNotNA <= 1) { if(PPT_cm * 10 < 600) { @@ -1233,11 +1253,7 @@ void esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTe } for(index = 0; index < iFixedSize; index++) { - if(missing(estimCover[index])) { - finalVegSum += 0.; - } else { - finalVegSum += estimCover[iFixed[index]]; - } + finalVegSum += estimCover[iFixed[index]]; } if(!EQ(finalVegSum, 1.)) { for(index = 0; index < overallEstimSize; index++) { @@ -1287,7 +1303,7 @@ void esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTe } /** - @brief Helper function to `esimatePotNatVegComposition()` that doesn't allow a value to go below zero + @brief Helper function to `estimatePotNatVegComposition()` that doesn't allow a value to go below zero @return A value that is either above or equal to zero */ @@ -1301,7 +1317,7 @@ double cutZeroInf(double value) { } /** - @brief Helper function to `esimatePotNatVegComposition()` that gets unique indices from two input arrays + @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 diff --git a/SW_VegProd.h b/SW_VegProd.h index 5edd39689..43a43a97a 100644 --- a/SW_VegProd.h +++ b/SW_VegProd.h @@ -264,8 +264,8 @@ 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); -void esimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanTempMon_C[], +void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYear, int veg_method); +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 inNorth, Bool warnExtrapolation, double *grassOutput, double *RelAbundanceL0, double *RelAbundanceL1); From 0da5b4d4eb7340a7a4910cb7d9e78e15a77be898 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 7 Aug 2022 18:04:14 -0400 Subject: [PATCH 134/326] Made default of `veg_method` zero/updated description of "1" --- testing/Input/veg.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testing/Input/veg.in b/testing/Input/veg.in index 7233ccce9..b08f58af7 100755 --- a/testing/Input/veg.in +++ b/testing/Input/veg.in @@ -8,9 +8,9 @@ # describe the four available vegetation types and should not be # modified unless a vegetation type itself is altered. -#---- Flag to activate/deactivate estimating vegetation based off weather -1 # 0 - Read in the values from file - # 1 - Estimate vegetation from weather +#---- Select method for vegetation parameters +0 # 0 - Read in the values from file + # 1 - Estimate fixed vegetation composition (fractional cover) from long-term climate conditions #---- Composition of vegetation type components (0-1; must add up to 1) # Grasses Shrubs Trees Forbs BareGround From eb03f98a5a01c6738a006d77436431f836d79083 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 7 Aug 2022 19:32:39 -0400 Subject: [PATCH 135/326] Updated unit tests for `estimatePotNatVegComposition()` - Made indices more self-documenting - Added tests that cover: * "fillEmptyWithBareGround" is false * "inNorth" is false --- test/test_SW_VegProd.cc | 277 ++++++++++++++++++++++++++++------------ 1 file changed, 197 insertions(+), 80 deletions(-) diff --git a/test/test_SW_VegProd.cc b/test/test_SW_VegProd.cc index 3a47c4dab..b1cb15d22 100644 --- a/test/test_SW_VegProd.cc +++ b/test/test_SW_VegProd.cc @@ -191,6 +191,23 @@ namespace { int allocate = 1; int index; + // 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 forbIndexL1 = 1; + int shrubIndexL1 = 2; + int grassesIndexL1 = 3; + int bareGroundL1 = 4; + // Reset "SW_Weather.allHist" SW_WTH_read(); @@ -209,11 +226,15 @@ namespace { C4Variables[2] = climateAverages.frostFree_days; // Estimate vegetation based off calculated variables and "inputValues" - esimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorth, warnExtrapolation, grassOutput, RelAbundanceL0, RelAbundanceL1); + /* ================================== + Test when all input values are "SW_MISSING" + ================================== */ + // Loop through RelAbundanceL0 and test results for(index = 0; index < 8; index++) { EXPECT_NEAR(RelAbundanceL0[index], RelAbundanceL0Expected[index], tol6); @@ -228,32 +249,34 @@ namespace { EXPECT_DOUBLE_EQ(grassOutput[1], 0.); EXPECT_DOUBLE_EQ(grassOutput[2], 0.); - // Test with half of input values not "SW_MISSING" - inputValues[0] = .376; - inputValues[1] = SW_MISSING; - inputValues[2] = .096; - inputValues[3] = SW_MISSING; - inputValues[4] = SW_MISSING; - inputValues[5] = .1098; - inputValues[6] = .0372; - inputValues[7] = SW_MISSING; - - RelAbundanceL0Expected[0] = 0.3760; - RelAbundanceL0Expected[1] = 0.3810; - RelAbundanceL0Expected[2] = 0.0960; - RelAbundanceL0Expected[3] = 0.0000; - RelAbundanceL0Expected[4] = 0.0000; - RelAbundanceL0Expected[5] = 0.1098; - RelAbundanceL0Expected[6] = 0.0372; - RelAbundanceL0Expected[7] = 0.0000; - - RelAbundanceL1Expected[0] = 0.0372; - RelAbundanceL1Expected[1] = 0.1098; - RelAbundanceL1Expected[2] = 0.7570; - RelAbundanceL1Expected[3] = 0.0960; - RelAbundanceL1Expected[4] = 0.0000; - - esimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + /* ================================== + Test with half of input values not "SW_MISSING" + ================================== */ + inputValues[succIndex] = .376; + inputValues[forbIndex] = SW_MISSING; + inputValues[C3Index] = .096; + inputValues[C4Index] = SW_MISSING; + inputValues[grassAnn] = SW_MISSING; + inputValues[shrubIndex] = .1098; + inputValues[treeIndex] = .0372; + inputValues[bareGround] = SW_MISSING; + + RelAbundanceL0Expected[succIndex] = 0.3760; + RelAbundanceL0Expected[forbIndex] = 0.3810; + RelAbundanceL0Expected[C3Index] = 0.0960; + RelAbundanceL0Expected[C4Index] = 0.0000; + RelAbundanceL0Expected[grassAnn] = 0.0000; + RelAbundanceL0Expected[shrubIndex] = 0.1098; + RelAbundanceL0Expected[treeIndex] = 0.0372; + RelAbundanceL0Expected[bareGround] = 0.0000; + + RelAbundanceL1Expected[treeIndexL1] = 0.0372; + RelAbundanceL1Expected[forbIndexL1] = 0.1098; + RelAbundanceL1Expected[shrubIndexL1] = 0.7570; + RelAbundanceL1Expected[grassesIndexL1] = 0.0960; + RelAbundanceL1Expected[bareGroundL1] = 0.0000; + + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorth, warnExtrapolation, grassOutput, RelAbundanceL0, RelAbundanceL1); @@ -272,32 +295,34 @@ namespace { EXPECT_DOUBLE_EQ(grassOutput[1], 0.); EXPECT_DOUBLE_EQ(grassOutput[2], 0.); - // Test with all input values not "SW_MISSING" - inputValues[0] = .1098; - inputValues[1] = .1098; - inputValues[2] = .1098; - inputValues[3] = .1098; - inputValues[4] = .1098; - inputValues[5] = .1098; - inputValues[6] = .1098; - inputValues[7] = .1098; - - RelAbundanceL0Expected[0] = 0.1098; - RelAbundanceL0Expected[1] = 0.1098; - RelAbundanceL0Expected[2] = 0.1098; - RelAbundanceL0Expected[3] = 0.1098; - RelAbundanceL0Expected[4] = 0.1098; - RelAbundanceL0Expected[5] = 0.1098; - RelAbundanceL0Expected[6] = 0.1098; - RelAbundanceL0Expected[7] = 0.2314; - - RelAbundanceL1Expected[0] = 0.1098; - RelAbundanceL1Expected[1] = 0.1098; - RelAbundanceL1Expected[2] = 0.2196; - RelAbundanceL1Expected[3] = 0.3294; - RelAbundanceL1Expected[4] = 0.2314; - - esimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + /* ================================== + 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; + + RelAbundanceL0Expected[succIndex] = 0.1098; + RelAbundanceL0Expected[forbIndex] = 0.1098; + RelAbundanceL0Expected[C3Index] = 0.1098; + RelAbundanceL0Expected[C4Index] = 0.1098; + RelAbundanceL0Expected[grassAnn] = 0.1098; + RelAbundanceL0Expected[shrubIndex] = 0.1098; + RelAbundanceL0Expected[treeIndex] = 0.1098; + RelAbundanceL0Expected[bareGround] = 0.2314; + + RelAbundanceL1Expected[treeIndexL1] = 0.1098; + RelAbundanceL1Expected[forbIndexL1] = 0.1098; + RelAbundanceL1Expected[shrubIndexL1] = 0.2196; + RelAbundanceL1Expected[grassesIndexL1] = 0.3294; + RelAbundanceL1Expected[bareGroundL1] = 0.2314; + + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorth, warnExtrapolation, grassOutput, RelAbundanceL0, RelAbundanceL1); @@ -318,6 +343,79 @@ namespace { EXPECT_NEAR(grassOutput[1], .333333, tol6); EXPECT_NEAR(grassOutput[2], .333333, tol6); + /* ================================== + Test with `fillEmptyWithBareGround` set to false, same input values as previous test + except for bare ground, which is now .2314 + ================================== */ + fillEmptyWithBareGround = swFALSE; + + // Bare ground value is set to have the whole sum of inputValues to equal one + // Otherwise, it would cause a program-stopping error, which is not what is wanted + inputValues[bareGround] = 0.2314; + + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorth, warnExtrapolation, + grassOutput, RelAbundanceL0, RelAbundanceL1); + + // Loop through RelAbundanceL0 and test results. + for(index = 0; index < 8; 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); + } + + EXPECT_NEAR(grassOutput[0], .333333, tol6); + EXPECT_NEAR(grassOutput[1], .333333, tol6); + EXPECT_NEAR(grassOutput[2], .333333, tol6); + + /* ================================== + Test with `inNorth` to be false, same input values as previous test + except for trees and bare ground which are both .0549 + ================================== */ + RelAbundanceL0Expected[succIndex] = 0.1098; + RelAbundanceL0Expected[forbIndex] = 0.1098; + RelAbundanceL0Expected[C3Index] = 0.1098; + RelAbundanceL0Expected[C4Index] = 0.1098; + RelAbundanceL0Expected[grassAnn] = 0.1098; + RelAbundanceL0Expected[shrubIndex] = 0.1098; + RelAbundanceL0Expected[treeIndex] = 0.0549; + RelAbundanceL0Expected[bareGround] = 0.2863; + + RelAbundanceL1Expected[treeIndexL1] = 0.0549; + RelAbundanceL1Expected[forbIndexL1] = 0.1098; + RelAbundanceL1Expected[shrubIndexL1] = 0.2196; + RelAbundanceL1Expected[grassesIndexL1] = 0.3294; + RelAbundanceL1Expected[bareGroundL1] = 0.2863; + + inNorth = swFALSE; + fillEmptyWithBareGround = swTRUE; + + inputValues[treeIndex] = .0549; + inputValues[bareGround] = .0549; + + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorth, warnExtrapolation, + grassOutput, RelAbundanceL0, RelAbundanceL1); + + // Loop through RelAbundanceL0 and test results. + for(index = 0; index < 8; 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); + } + + EXPECT_NEAR(grassOutput[0], .333333, tol6); + EXPECT_NEAR(grassOutput[1], .333333, tol6); + EXPECT_NEAR(grassOutput[2], .333333, tol6); + // Deallocate structs allocDeallocClimateStructs(deallocate, 31, &climateOutput, &climateAverages); } @@ -354,6 +452,23 @@ namespace { int allocate = 1; int index; + // 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 succIndexL1 = 0; + int forbIndexL1 = 1; + int shrubIndexL1 = 2; + int treeIndexL1 = 3; + int bareGroundL1 = 4; + // Reset "SW_Weather.allHist" SW_WTH_read(); @@ -372,7 +487,7 @@ namespace { C4Variables[2] = climateAverages.frostFree_days; // Estimate vegetation based off calculated variables and "inputValues" - esimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorth, warnExtrapolation, grassOutput, RelAbundanceL0, RelAbundanceL1); @@ -391,32 +506,34 @@ namespace { EXPECT_NEAR(grassOutput[1], 0.21367894, tol6); EXPECT_NEAR(grassOutput[2], 0.70093662, tol6); - // Test when a couple input values are not "SW_MISSING" - inputValues[0] = .5; - inputValues[1] = SW_MISSING; - inputValues[2] = .5; - inputValues[3] = SW_MISSING; - inputValues[4] = SW_MISSING; - inputValues[5] = SW_MISSING; - inputValues[6] = SW_MISSING; - inputValues[7] = SW_MISSING; - - RelAbundanceL0Expected[0] = .5; - RelAbundanceL0Expected[1] = 0.; - RelAbundanceL0Expected[2] = .5; - RelAbundanceL0Expected[3] = 0.; - RelAbundanceL0Expected[4] = 0.; - RelAbundanceL0Expected[5] = 0.; - RelAbundanceL0Expected[6] = 0.; - RelAbundanceL0Expected[7] = 0.; - - RelAbundanceL1Expected[0] = 0.; - RelAbundanceL1Expected[1] = 0.; - RelAbundanceL1Expected[2] = .5; - RelAbundanceL1Expected[3] = .5; - RelAbundanceL1Expected[4] = 0.; - - esimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + /* ================================== + Test when a couple input values are not "SW_MISSING" + ================================== */ + inputValues[succIndex] = .5; + inputValues[forbIndex] = SW_MISSING; + inputValues[C3Index] = .5; + inputValues[C4Index] = SW_MISSING; + inputValues[grassAnn] = SW_MISSING; + inputValues[shrubIndex] = SW_MISSING; + inputValues[treeIndex] = SW_MISSING; + inputValues[bareGround] = SW_MISSING; + + RelAbundanceL0Expected[succIndex] = .5; + RelAbundanceL0Expected[forbIndex] = 0.; + RelAbundanceL0Expected[C3Index] = .5; + RelAbundanceL0Expected[C4Index] = 0.; + RelAbundanceL0Expected[grassAnn] = 0.; + RelAbundanceL0Expected[shrubIndex] = 0.; + RelAbundanceL0Expected[treeIndex] = 0.; + RelAbundanceL0Expected[bareGround] = 0.; + + RelAbundanceL1Expected[succIndexL1] = 0.; + RelAbundanceL1Expected[forbIndexL1] = 0.; + RelAbundanceL1Expected[shrubIndexL1] = .5; + RelAbundanceL1Expected[treeIndexL1] = .5; + RelAbundanceL1Expected[bareGroundL1] = 0.; + + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorth, warnExtrapolation, grassOutput, RelAbundanceL0, RelAbundanceL1); From 823d7afd15b53bbf6203787a21bda9e595d713f5 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 12 Aug 2022 17:09:20 -0400 Subject: [PATCH 136/326] Fix seeding of markov_rng by `SW_MKV_construct()` - `SW_MKV_construct()` now only seeds `markov_rng` if run as "SOILWAT2" - previously, `SW_MKV_construct()` used to seed `markov_rng` with a non-reproducible value: * this was a no-operation for rSOILWAT2 * it did not affect how STEPWAT2 worked because it seeded `markov_rng` after the call to `SW_MKV_construct()` with its own seed * however, the "feature_read_weather" branch caused that `SW_MKV_construct()` was called after STEPWAT2 seeding and before using `markov_rng`; thus, erroneously, the STEPWAT2 seed had no effect --- SW_Markov.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/SW_Markov.c b/SW_Markov.c index 2a0425195..9be58a898 100644 --- a/SW_Markov.c +++ b/SW_Markov.c @@ -206,9 +206,14 @@ void SW_MKV_construct(void) { SW_MARKOV *m = &SW_Markov; size_t s = sizeof(RealD); - /* STEPWAT2: The markov_rng seed will be reset with `Globals.randseed` by - its `main` at the beginning of each iteration */ + /* Set seed of `markov_rng` + - SOILWAT2: set seed here + - STEPWAT2: `main()` uses `Globals.randseed` to (re-)set for each iteration + - rSOILWAT2: R API handles RNGs + */ + #if defined(SOILWAT) RandSeed(0, &markov_rng); + #endif m->ppt_events = 0; From 3b0d8a3d62a9920370e7958b9d2fe2a322728b9f Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 16 Aug 2022 20:23:36 -0400 Subject: [PATCH 137/326] Modified `calcSiteClimate()` to adjusted to N/S * - Created new helper function to take in all constant data (doesn't matter on northern or southern hemisphere) - Created new unit test to go along with this * This is not currently working fully and will be changed --- SW_Weather.c | 124 ++++++++++++++++++++++++++++++---------- SW_Weather.h | 4 +- test/test_SW_Weather.cc | 115 ++++++++++++++++++++++++++++++++++++- 3 files changed, 209 insertions(+), 34 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 0347da338..ca4d89c49 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -131,10 +131,13 @@ void averageClimateAcrossYears(SW_CLIMATE_YEARLY *climateOutput, int numYears, */ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, - SW_CLIMATE_YEARLY *climateOutput) { - - int month, yearIndex, year, day, numDaysYear, numDaysMonth = Time_days_in_month(0), - currMonDay; + SW_CLIMATE_YEARLY *climateOutput, double latitude) { + + Bool isNorth = (latitude >= 0.0 && latitude < 90.0) ? swTRUE : swFALSE; + + int month, yearIndex, year, day, numDaysYear, currMonDay; + int numDaysMonth = (isNorth) ? Time_days_in_month(Jan) : Time_days_in_month(Jul); + int adjustedDoy, adjustedYear = 0, secondMonth, seventhMonth, adjustedFullYear; double currentTempMin, currentTempMean, totalAbove65, currentJulyMin, JulyPPT, consecNonFrost, currentNonFrost; @@ -149,35 +152,52 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, memset(climateOutput->meanTemp_C, 0., sizeof(double) * numYears); memset(climateOutput->minTempFeb_C, 0., sizeof(double) * numYears); memset(climateOutput->minTempJuly_C, 0., sizeof(double) * numYears); + + fillNorthSouthConstants(allHist, numYears, startYear, climateOutput); + for(yearIndex = 0; yearIndex < numYears; yearIndex++) { year = yearIndex + startYear; Time_new_year(year); numDaysYear = Time_get_lastdoy_y(year); - month = 0; + month = (isNorth) ? Jan : Jul; currMonDay = 0; currentJulyMin = SW_MISSING; totalAbove65 = 0; currentNonFrost = 0; consecNonFrost = 0; JulyPPT = 0; - + if(isNorth) { + secondMonth = Feb; + seventhMonth = Jul; + } else { + secondMonth = Aug; + seventhMonth = Jan; + } + 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]; + if(isNorth) { + adjustedDoy = day; + adjustedYear = yearIndex; + } else { + // Check if current year is leap year + adjustedDoy = (numDaysYear == 366) ? day + 182 : day + 181; + adjustedDoy = adjustedDoy % numDaysYear; + adjustedYear = (adjustedDoy == 0) ? yearIndex + 1 : adjustedYear; + adjustedFullYear = adjustedYear + startYear; + if(adjustedDoy == 0) Time_new_year(adjustedFullYear); + } - climateOutput->PPT_cm[yearIndex] += allHist[yearIndex]->ppt[day]; - climateOutput->meanTemp_C[yearIndex] += allHist[yearIndex]->temp_avg[day]; + if(month == Jul && yearIndex == numYears - 1 && !isNorth) break; + + currMonDay++; - currentTempMin = allHist[yearIndex]->temp_min[day]; - currentTempMean = allHist[yearIndex]->temp_avg[day]; + currentTempMin = allHist[adjustedYear]->temp_min[adjustedDoy]; + currentTempMean = allHist[adjustedYear]->temp_avg[adjustedDoy]; - if(month == Jul){ + if(month == seventhMonth){ currentJulyMin = (currentTempMin < currentJulyMin) ? currentTempMin : currentJulyMin; - JulyPPT += allHist[yearIndex]->ppt[day] * 10; + JulyPPT += allHist[adjustedYear]->ppt[adjustedDoy] * 10; } if(currentTempMin > 0.0) { @@ -189,40 +209,84 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, currentNonFrost = 0.; } - if(month == Feb) { - climateOutput->minTempFeb_C[yearIndex] += allHist[yearIndex]->temp_min[day]; + if(month == secondMonth) { + climateOutput->minTempFeb_C[yearIndex] += allHist[adjustedYear]->temp_min[adjustedDoy]; } if(currMonDay == numDaysMonth) { - // Take the average of the current months values for current year - climateOutput->meanTempMon_C[month][yearIndex] /= numDaysMonth; - climateOutput->maxTempMon_C[month][yearIndex] /= numDaysMonth; - climateOutput->minTempMon_C[month][yearIndex] /= numDaysMonth; - - if(month == Feb) climateOutput->minTempFeb_C[yearIndex] /= numDaysMonth; + if(month == secondMonth) climateOutput->minTempFeb_C[yearIndex] /= numDaysMonth; - month++; - numDaysMonth = Time_days_in_month(month % 12); + month = (month + 1) % 12; + numDaysMonth = Time_days_in_month(month); currMonDay = 0; } - + currentTempMean -= 18.333; totalAbove65 += (currentTempMean > 0.0) ? currentTempMean : 0.; } - climateOutput->minTempJuly_C[yearIndex] = currentJulyMin; + climateOutput->minTempJuly_C[yearIndex] = (currentJulyMin == SW_MISSING) ? 0 : currentJulyMin; climateOutput->PPTJuly_mm[yearIndex] = JulyPPT; 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; - climateOutput->meanTemp_C[yearIndex] /= numDaysYear; } + + if(!isNorth) { + currentJulyMin = SW_MISSING; + for(day = 0; day < 31; day++) { + if(allHist[0]->temp_min[day] < currentJulyMin) { + currentJulyMin = allHist[0]->temp_min[day]; + } + } + climateOutput->minTempJuly_C[numYears - 1] = currentJulyMin; + + for(day = 31; day < 59; day++) { + climateOutput->PPTJuly_mm[numYears - 1] += allHist[0]->temp_min[day]; + } + } + findDriestQtr(climateOutput->meanTempDriestQtr_C, numYears, climateOutput->meanTempMon_C, climateOutput->PPTMon_cm); } +void fillNorthSouthConstants(SW_WEATHER_HIST **allHist, int numYears, int startYear, + SW_CLIMATE_YEARLY *climateOutput) { + + int month = Jan, monDay, 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) % 12; + numDaysMonth = Time_days_in_month(month); + currMonDay = 0; + } + } + climateOutput->meanTemp_C[yearIndex] /= numDaysYear; + } + +} + /** @brief Helper function to calcsiteClimate to find the driest quarter of the year's average temperature diff --git a/SW_Weather.h b/SW_Weather.h index 00384eb58..f76b66452 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -163,7 +163,9 @@ Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather); void averageClimateAcrossYears(SW_CLIMATE_YEARLY *climateOutput, int numYears, SW_CLIMATE_CLIM *climateAverages); void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, - SW_CLIMATE_YEARLY *climateOutput); + SW_CLIMATE_YEARLY *climateOutput, double latitude); +void fillNorthSouthConstants(SW_WEATHER_HIST **allHist, int numYears, int startYear, + SW_CLIMATE_YEARLY *climateOutput); void findDriestQtr(double *meanTempDriestQtr_C, int numYears, double **meanTempMon_C, double **meanPPTMon_cm); void allocDeallocClimateStructs(int action, int numYears, SW_CLIMATE_YEARLY *climateOutput, diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index c249e2734..4d5a74879 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -159,6 +159,8 @@ namespace { int deallocate = 0; int allocate = 1; + double latitude = 0.0; + // Allocate memory // 31 = number of years used in test allocDeallocClimateStructs(allocate, 31, &climateOutput, &climateAverages); @@ -181,7 +183,7 @@ namespace { // ) // ``` - calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput); + calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput, latitude); EXPECT_NEAR(climateOutput.meanTempMon_C[Jan][0], -8.432581, tol6); EXPECT_NEAR(climateOutput.maxTempMon_C[Jan][0], -2.562581, tol6); @@ -262,6 +264,8 @@ namespace { int deallocate = 0; int allocate = 1; + double latitude = 0.0; + // Allocate memory // 1 = number of years used in test allocDeallocClimateStructs(allocate, 1, &climateOutput, &climateAverages); @@ -279,7 +283,7 @@ namespace { // ) // ``` - calcSiteClimate(SW_Weather.allHist, 1, 1980, &climateOutput); + calcSiteClimate(SW_Weather.allHist, 1, 1980, &climateOutput, latitude); averageClimateAcrossYears(&climateOutput, 1, &climateAverages); // Expect that aggregated values across one year are identical @@ -369,6 +373,109 @@ namespace { } + TEST(ClimateVariableTest, ClimateFromDefaultWeatherSouth) { + + // This test relies on allHist from `SW_WEATHER` being already filled + SW_CLIMATE_YEARLY climateOutput; + SW_CLIMATE_CLIM climateAverages; + + int deallocate = 0; + int allocate = 1; + + double latitude = -10.0; + + // Allocate memory + // 31 = number of years used in test + allocDeallocClimateStructs(allocate, 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) + // ```{r} + // rSOILWAT2::calc_SiteClimate( + // weatherList = rSOILWAT2::get_WeatherHistory( + // rSOILWAT2::sw_exampleData + // )[1], + // do_C4vars = TRUE, + // do_Cheatgrass_ClimVars = TRUE + // ) + // ``` + + calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput, latitude); + + 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.minTempJuly_C[0], -16.98, tol6); + EXPECT_NEAR(climateOutput.frostFree_days[0], 78, tol6); // TODO: Look into (78) + EXPECT_NEAR(climateOutput.ddAbove65F_degday[0], 16.990001, tol6); + + + // Climate variables used for cheatgrass cover + // (stdev of one value is undefined) + EXPECT_NEAR(climateOutput.PPTJuly_mm[0], 22.199999, tol6); + EXPECT_NEAR(climateOutput.meanTempDriestQtr_C[0], 0.936387, tol6); + EXPECT_NEAR(climateOutput.minTempFeb_C[0], 5.1445161, tol6); + + + // --- Long-term variables (aggregated across years) ------ + // Expect identical output to rSOILWAT2 (e.g., v5.3.1) + // ```{r} + // rSOILWAT2::calc_SiteClimate( + // 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.minTempJuly_C, -27.243870, tol6); + EXPECT_NEAR(climateAverages.frostFree_days, 68.290323, tol6); + EXPECT_NEAR(climateAverages.ddAbove65F_degday, 20.684935, tol6); + + EXPECT_NEAR(climateAverages.sdC4[0], 5.241726, tol6); + EXPECT_NEAR(climateAverages.sdC4[1], 13.446669, tol6); + EXPECT_NEAR(climateAverages.sdC4[2], 19.755513, tol6); + + // Climate variables used for cheatgrass cover + EXPECT_NEAR(climateAverages.PPTJuly_mm, 22.199999, tol6); + EXPECT_NEAR(climateAverages.meanTempDriestQtr_C, 11.524859, tol6); + EXPECT_NEAR(climateAverages.minTempFeb_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 + allocDeallocClimateStructs(deallocate, 31, &climateOutput, &climateAverages); + } + TEST(ClimateVariableTest, ClimateFromConstantWeather) { @@ -378,6 +485,8 @@ namespace { int allocate = 1; int deallocate = 0; + + double latitude = 0.0; // Allocate memory allocDeallocClimateStructs(allocate, 2, &climateOutput, &climateAverages); @@ -408,7 +517,7 @@ namespace { } // --- Annual time-series of climate variables ------ - calcSiteClimate(allHist, 2, 1980, &climateOutput); + calcSiteClimate(allHist, 2, 1980, &climateOutput, latitude); EXPECT_DOUBLE_EQ(climateOutput.meanTempMon_C[Jan][0], 1.); EXPECT_DOUBLE_EQ(climateOutput.maxTempMon_C[Jan][0], 1.); From 72be4ba34f4ed4433342993eff6b213c81f5dbc5 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 16 Aug 2022 20:49:33 -0400 Subject: [PATCH 138/326] Accommodated vegetation for new "latitude" input - If program is being run normally, the default value is 45.0 (northern hemisphere) --- SW_VegProd.c | 18 ++++++++++++++---- SW_VegProd.h | 3 ++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index 2605ae392..61fec4db0 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -590,6 +590,8 @@ void SW_VPD_init_run(void) { veg_method = veg->veg_method; + double latitude = 45.0; + /* Set co2-multipliers to default */ for (year = 0; year < MAX_NYEAR; year++) { @@ -601,7 +603,7 @@ void SW_VPD_init_run(void) { } if(veg_method > 0) { - estimateVegetationFromClimate(veg, model->startyr, model->endyr, veg_method); + estimateVegetationFromClimate(veg, model->startyr, model->endyr, veg_method, latitude); } } @@ -872,7 +874,8 @@ void get_critical_rank(void){ 1 - Estimate fixed vegetation composition (fractional cover) from long-term climate conditions */ -void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYear, int veg_method) { +void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYear, + int veg_method, double latitude) { int numYears = endYear - startYear + 1, deallocate = 0, allocate = 1, k; @@ -887,12 +890,19 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe double SumGrassesFraction = SW_MISSING, C4Variables[3], grassOutput[3], RelAbundanceL0[8], RelAbundanceL1[5]; - Bool fillEmptyWithBareGround = swTRUE, inNorth = swTRUE, warnExtrapolation = swTRUE; + Bool fillEmptyWithBareGround = swTRUE, warnExtrapolation = swTRUE; + Bool inNorth; + + if(latitude > 0.0 && latitude < 90.0) { + inNorth = swTRUE; + } else { + inNorth = swFALSE; + } // Allocate climate structs' memory allocDeallocClimateStructs(allocate, numYears, &climateOutput, &climateAverages); - calcSiteClimate(SW_Weather.allHist, numYears, startYear, &climateOutput); + calcSiteClimate(SW_Weather.allHist, numYears, startYear, &climateOutput, latitude); averageClimateAcrossYears(&climateOutput, numYears, &climateAverages); diff --git a/SW_VegProd.h b/SW_VegProd.h index 43a43a97a..4941cda43 100644 --- a/SW_VegProd.h +++ b/SW_VegProd.h @@ -264,7 +264,8 @@ 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); +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 inNorth, Bool warnExtrapolation, From 324ddcf10467e2173b110141c9cc885390efd5b1 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 18 Aug 2022 14:38:15 -0400 Subject: [PATCH 139/326] Modified `calcSiteClimate()` and unit tests - Replaced "latitude" with "isNorth" input for `calcSiteClimate()` - Used `missing()` macro and replaced "numDaysYear" with 365 to correctly wrap to next year - Replaced two tests with expected value to test for --- SW_Weather.c | 8 +++----- SW_Weather.h | 2 +- test/test_SW_Weather.cc | 20 ++++++++++---------- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index ca4d89c49..13824df67 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -131,9 +131,7 @@ void averageClimateAcrossYears(SW_CLIMATE_YEARLY *climateOutput, int numYears, */ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, - SW_CLIMATE_YEARLY *climateOutput, double latitude) { - - Bool isNorth = (latitude >= 0.0 && latitude < 90.0) ? swTRUE : swFALSE; + SW_CLIMATE_YEARLY *climateOutput, Bool isNorth) { int month, yearIndex, year, day, numDaysYear, currMonDay; int numDaysMonth = (isNorth) ? Time_days_in_month(Jan) : Time_days_in_month(Jul); @@ -181,7 +179,7 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, } else { // Check if current year is leap year adjustedDoy = (numDaysYear == 366) ? day + 182 : day + 181; - adjustedDoy = adjustedDoy % numDaysYear; + adjustedDoy = adjustedDoy % 365; adjustedYear = (adjustedDoy == 0) ? yearIndex + 1 : adjustedYear; adjustedFullYear = adjustedYear + startYear; if(adjustedDoy == 0) Time_new_year(adjustedFullYear); @@ -225,7 +223,7 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, totalAbove65 += (currentTempMean > 0.0) ? currentTempMean : 0.; } - climateOutput->minTempJuly_C[yearIndex] = (currentJulyMin == SW_MISSING) ? 0 : currentJulyMin; + climateOutput->minTempJuly_C[yearIndex] = (missing(currentJulyMin)) ? 0 : currentJulyMin; climateOutput->PPTJuly_mm[yearIndex] = JulyPPT; climateOutput->ddAbove65F_degday[yearIndex] = totalAbove65; diff --git a/SW_Weather.h b/SW_Weather.h index f76b66452..b210a03c0 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -163,8 +163,8 @@ Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather); void averageClimateAcrossYears(SW_CLIMATE_YEARLY *climateOutput, int numYears, SW_CLIMATE_CLIM *climateAverages); void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, - SW_CLIMATE_YEARLY *climateOutput, double latitude); void fillNorthSouthConstants(SW_WEATHER_HIST **allHist, int numYears, int startYear, + SW_CLIMATE_YEARLY *climateOutput, Bool isNorth); SW_CLIMATE_YEARLY *climateOutput); void findDriestQtr(double *meanTempDriestQtr_C, int numYears, double **meanTempMon_C, double **meanPPTMon_cm); diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 4d5a74879..19cfcf755 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -159,7 +159,7 @@ namespace { int deallocate = 0; int allocate = 1; - double latitude = 0.0; + Bool isNorth = swTRUE; // Allocate memory // 31 = number of years used in test @@ -183,7 +183,7 @@ namespace { // ) // ``` - calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput, latitude); + calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput, isNorth); EXPECT_NEAR(climateOutput.meanTempMon_C[Jan][0], -8.432581, tol6); EXPECT_NEAR(climateOutput.maxTempMon_C[Jan][0], -2.562581, tol6); @@ -264,7 +264,7 @@ namespace { int deallocate = 0; int allocate = 1; - double latitude = 0.0; + Bool isNorth = swTRUE; // Allocate memory // 1 = number of years used in test @@ -283,7 +283,7 @@ namespace { // ) // ``` - calcSiteClimate(SW_Weather.allHist, 1, 1980, &climateOutput, latitude); + calcSiteClimate(SW_Weather.allHist, 1, 1980, &climateOutput, isNorth); averageClimateAcrossYears(&climateOutput, 1, &climateAverages); // Expect that aggregated values across one year are identical @@ -382,7 +382,7 @@ namespace { int deallocate = 0; int allocate = 1; - double latitude = -10.0; + Bool isNorth = swFALSE; // Allocate memory // 31 = number of years used in test @@ -406,7 +406,7 @@ namespace { // ) // ``` - calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput, latitude); + calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput, isNorth); EXPECT_NEAR(climateOutput.meanTempMon_C[Jan][0], -8.432581, tol6); EXPECT_NEAR(climateOutput.maxTempMon_C[Jan][0], -2.562581, tol6); @@ -418,13 +418,13 @@ namespace { // Climate variables used for C4 grass cover // (stdev of one value is undefined) EXPECT_NEAR(climateOutput.minTempJuly_C[0], -16.98, tol6); - EXPECT_NEAR(climateOutput.frostFree_days[0], 78, tol6); // TODO: Look into (78) + EXPECT_NEAR(climateOutput.frostFree_days[0], 76, tol6); EXPECT_NEAR(climateOutput.ddAbove65F_degday[0], 16.990001, tol6); // Climate variables used for cheatgrass cover // (stdev of one value is undefined) - EXPECT_NEAR(climateOutput.PPTJuly_mm[0], 22.199999, tol6); + EXPECT_NEAR(climateOutput.PPTJuly_mm[0], 24.699999, tol6); EXPECT_NEAR(climateOutput.meanTempDriestQtr_C[0], 0.936387, tol6); EXPECT_NEAR(climateOutput.minTempFeb_C[0], 5.1445161, tol6); @@ -486,7 +486,7 @@ namespace { int allocate = 1; int deallocate = 0; - double latitude = 0.0; + Bool isNorth = swTRUE; // Allocate memory allocDeallocClimateStructs(allocate, 2, &climateOutput, &climateAverages); @@ -517,7 +517,7 @@ namespace { } // --- Annual time-series of climate variables ------ - calcSiteClimate(allHist, 2, 1980, &climateOutput, latitude); + calcSiteClimate(allHist, 2, 1980, &climateOutput, isNorth); EXPECT_DOUBLE_EQ(climateOutput.meanTempMon_C[Jan][0], 1.); EXPECT_DOUBLE_EQ(climateOutput.maxTempMon_C[Jan][0], 1.); From 87e94cb18e7e1e5e6b845ccbdb64c6baba28d90f Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 18 Aug 2022 14:38:46 -0400 Subject: [PATCH 140/326] Renamed and added brief for `fillNorthSouthConstants()` --- SW_Weather.c | 9 +++++++-- SW_Weather.h | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 13824df67..b2d426e6b 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -151,7 +151,7 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, memset(climateOutput->minTempFeb_C, 0., sizeof(double) * numYears); memset(climateOutput->minTempJuly_C, 0., sizeof(double) * numYears); - fillNorthSouthConstants(allHist, numYears, startYear, climateOutput); + calcSiteClimateLatInvariants(allHist, numYears, startYear, climateOutput); for(yearIndex = 0; yearIndex < numYears; yearIndex++) { year = yearIndex + startYear; @@ -250,7 +250,12 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, climateOutput->meanTempMon_C, climateOutput->PPTMon_cm); } -void fillNorthSouthConstants(SW_WEATHER_HIST **allHist, int numYears, int startYear, +/** + @brief Helper function to `calcSiteClimate()`. Manages all information that is independant of the site + being in the northern/southern hemisphere. + */ + +void calcSiteClimateLatInvariants(SW_WEATHER_HIST **allHist, int numYears, int startYear, SW_CLIMATE_YEARLY *climateOutput) { int month = Jan, monDay, numDaysMonth = Time_days_in_month(month), yearIndex, diff --git a/SW_Weather.h b/SW_Weather.h index b210a03c0..c18e60be6 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -163,8 +163,8 @@ Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather); void averageClimateAcrossYears(SW_CLIMATE_YEARLY *climateOutput, int numYears, SW_CLIMATE_CLIM *climateAverages); void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, -void fillNorthSouthConstants(SW_WEATHER_HIST **allHist, int numYears, int startYear, SW_CLIMATE_YEARLY *climateOutput, Bool isNorth); +void calcSiteClimateLatInvariants(SW_WEATHER_HIST **allHist, int numYears, int startYear, SW_CLIMATE_YEARLY *climateOutput); void findDriestQtr(double *meanTempDriestQtr_C, int numYears, double **meanTempMon_C, double **meanPPTMon_cm); From 600c181bee6d05fdb6a729e5f5742350724bb9ce Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 19 Aug 2022 12:12:11 -0400 Subject: [PATCH 141/326] Made `findDriestQuarter()` N/S hemisphere compatible - Added "adjustedMonth" variable to conform to northern/southern hemisphere - Added "isNorth" input to the function --- SW_Weather.c | 18 ++++++++++++------ SW_Weather.h | 2 +- test/test_SW_Weather.cc | 8 +++++--- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index b2d426e6b..3ec4bebc3 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -302,9 +302,9 @@ void calcSiteClimateLatInvariants(SW_WEATHER_HIST **allHist, int numYears, int s @param[out] meanTempDriestQtr_C Array of size numYears holding the average temperature of the driest quarter of the year for every year */ void findDriestQtr(double *meanTempDriestQtr_C, int numYears, double **meanTempMon_C, - double **PPTMon_cm) { + double **PPTMon_cm, Bool isNorth) { - int yearIndex, month, prevMonth, nextMonth; + int yearIndex, month, prevMonth, nextMonth, adjustedMonth; double defaultVal = 999., driestThreeMonPPT, driestMeanTemp, currentQtrPPT, currentQtrTemp; @@ -314,16 +314,22 @@ void findDriestQtr(double *meanTempDriestQtr_C, int numYears, double **meanTempM driestMeanTemp = defaultVal; for(month = 0; month < MAX_MONTHS; month++) { + if(isNorth) { + adjustedMonth = month; + } else { + adjustedMonth = month + Jul; + adjustedMonth %= MAX_MONTHS; + } - prevMonth = (month == 0) ? 11 : month - 1; - nextMonth = (month == 11) ? 0 : month + 1; + prevMonth = (adjustedMonth == 0) ? 11 : adjustedMonth - 1; + nextMonth = (adjustedMonth == 11) ? 0 : adjustedMonth + 1; currentQtrPPT = (PPTMon_cm[prevMonth][yearIndex]) + - (PPTMon_cm[month][yearIndex]) + + (PPTMon_cm[adjustedMonth][yearIndex]) + (PPTMon_cm[nextMonth][yearIndex]); currentQtrTemp = (meanTempMon_C[prevMonth][yearIndex]) + - (meanTempMon_C[month][yearIndex]) + + (meanTempMon_C[adjustedMonth][yearIndex]) + (meanTempMon_C[nextMonth][yearIndex]); if(currentQtrPPT < driestThreeMonPPT) { diff --git a/SW_Weather.h b/SW_Weather.h index c18e60be6..9861d23d4 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -167,7 +167,7 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, void calcSiteClimateLatInvariants(SW_WEATHER_HIST **allHist, int numYears, int startYear, SW_CLIMATE_YEARLY *climateOutput); void findDriestQtr(double *meanTempDriestQtr_C, int numYears, double **meanTempMon_C, - double **meanPPTMon_cm); + double **meanPPTMon_cm, Bool isNorth); void allocDeallocClimateStructs(int action, int numYears, SW_CLIMATE_YEARLY *climateOutput, SW_CLIMATE_CLIM *climateAverages); void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, unsigned int n_years); diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 19cfcf755..f45166dab 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -597,6 +597,8 @@ namespace { double **meanTempMon_C = new double*[MAX_MONTHS]; + Bool isNorth = swTRUE; + for(month = 0; month < MAX_MONTHS; month++) { PPTMon_cm[month] = new double[2]; meanTempMon_C[month] = new double[2]; @@ -609,7 +611,7 @@ namespace { // ------ Test for one year ------ - findDriestQtr(result, 1, meanTempMon_C, PPTMon_cm); + findDriestQtr(result, 1, meanTempMon_C, PPTMon_cm, isNorth); // Value 1.433333... is the average temperature of the driest quarter of the year // In this case, the driest quarter is February-April @@ -617,7 +619,7 @@ namespace { // ------ Test for two years ------ - findDriestQtr(result, 2, meanTempMon_C, PPTMon_cm); + findDriestQtr(result, 2, meanTempMon_C, PPTMon_cm, isNorth); EXPECT_NEAR(result[0], 1.4333333333333333, tol9); EXPECT_NEAR(result[1], 1.4333333333333333, tol9); @@ -631,7 +633,7 @@ namespace { } } - findDriestQtr(result, 1, meanTempMon_C, PPTMon_cm); + findDriestQtr(result, 1, meanTempMon_C, PPTMon_cm, isNorth); // Expect that the driest quarter that occurs first // among all driest quarters is used From cf191722aafea1faf581adca183b60fbb340b129 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 19 Aug 2022 12:26:10 -0400 Subject: [PATCH 142/326] Modified description/north south handling of `calcSiteClimate()` - Added statement about N/S in `calcSiteClimate` documentation - Removed fetching of data in first January and incorrect seventh month precipitation * rSOILWAT2 uses data that shouldn't be used * SOILWAT2 will not be using data that shouldn't be used (i.e., never accessing/using first and last six months of data) so, there will be different values compared to rSOILWAT2 --- SW_Weather.c | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 3ec4bebc3..5fe73753d 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -122,6 +122,10 @@ void averageClimateAcrossYears(SW_CLIMATE_YEARLY *climateOutput, int numYears, /** @brief Calculate monthly and annual time series of climate variables from daily weather + + When site is in southern hemisphere, the first and last six months of data are ignored. The beginning of the year + starts on the first day of July and ends on June 30th of the next calendar year. Due to this, what is referred to as + the "adjusted" calendar year and is shifted six months in comparison to calendar years. @param[in] allHist Array containing all historical data of a site @param[in] numYears Number of years represented by `allHist` @@ -232,22 +236,8 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, climateOutput->frostFree_days[yearIndex] = (consecNonFrost > 0) ? consecNonFrost : currentNonFrost; } - if(!isNorth) { - currentJulyMin = SW_MISSING; - for(day = 0; day < 31; day++) { - if(allHist[0]->temp_min[day] < currentJulyMin) { - currentJulyMin = allHist[0]->temp_min[day]; - } - } - climateOutput->minTempJuly_C[numYears - 1] = currentJulyMin; - - for(day = 31; day < 59; day++) { - climateOutput->PPTJuly_mm[numYears - 1] += allHist[0]->temp_min[day]; - } - } - findDriestQtr(climateOutput->meanTempDriestQtr_C, numYears, - climateOutput->meanTempMon_C, climateOutput->PPTMon_cm); + climateOutput->meanTempMon_C, climateOutput->PPTMon_cm, isNorth); } /** @@ -342,6 +332,9 @@ void findDriestQtr(double *meanTempDriestQtr_C, int numYears, double **meanTempM meanTempDriestQtr_C[yearIndex] = driestMeanTemp / 3; } + + if(!isNorth) meanTempDriestQtr_C[numYears - 1] = SW_MISSING; + } From 9e73f2ff6c7497a85f31c272dcc34e34985a1751 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 25 Aug 2022 10:05:33 -0700 Subject: [PATCH 143/326] Adjusted how latitude is set in `SW_VegProd.c` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Instead of setting a default latitude of 45º, we now use latitude given in type SW_SITE --- SW_VegProd.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index 61fec4db0..5cf731e44 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -54,6 +54,7 @@ changed _echo_inits() to now display the bare ground components in logfile.log #include "SW_VegProd.h" #include "SW_Model.h" // externs SW_Model #include "SW_Weather.h" +#include "SW_Site.h" @@ -587,10 +588,11 @@ void SW_VPD_init_run(void) { SW_VEGPROD *veg = &SW_VegProd; SW_MODEL *model = &SW_Model; + SW_SITE *site = &SW_Site; veg_method = veg->veg_method; - double latitude = 45.0; + double latitude = site->latitude; /* Set co2-multipliers to default */ for (year = 0; year < MAX_NYEAR; year++) @@ -893,7 +895,7 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe Bool fillEmptyWithBareGround = swTRUE, warnExtrapolation = swTRUE; Bool inNorth; - if(latitude > 0.0 && latitude < 90.0) { + if(latitude > 0.0) { inNorth = swTRUE; } else { inNorth = swFALSE; From 0f8e9503cbd0e28f7fb4b4b31c47138c836f1cf3 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 25 Aug 2022 10:51:36 -0700 Subject: [PATCH 144/326] Fixed possible bugs in vegetation calculation - Added call to `memset()` to initialize "tempArray" and "tempArraySeen" * NOTE: This is only to fix bugs in rSOILWAT when the intermediate R to C function is called * This does fix anything on the SOILWAT side - Put C4 species calculations in correct place - Removed "bareGround" index part of loop that determines fixed values - Set default input values for trees, bare ground and annual grasses to zero --- SW_VegProd.c | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index 5cf731e44..fe3651380 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -887,7 +887,7 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe // NOTE: 8 = number of types, 5 = (number of types) - grasses double coverValues[8] = {SW_MISSING, SW_MISSING, SW_MISSING, SW_MISSING, - SW_MISSING, SW_MISSING, SW_MISSING, SW_MISSING}, shrubLimit = .2; + 0.0, SW_MISSING, 0.0, 0.0}, shrubLimit = .2; double SumGrassesFraction = SW_MISSING, C4Variables[3], grassOutput[3], RelAbundanceL0[8], RelAbundanceL1[5]; @@ -1003,8 +1003,6 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT for(index = 0; index < nTypes; index++) { if(!missing(inputValues[index])) { initialVegSum += inputValues[index]; - } else { - initialVegSum += 0.; } } @@ -1012,12 +1010,7 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT // Initialize estimCover and overallEstim for(index = 0; index < nTypes; index++) { - if(index == bareGround) { - estimCover[bareGround] = - (!missing(inputValues[bareGround])) ? inputValues[bareGround] : 0.; - iFixed[iFixedSize] = bareGround; - iFixedSize++; - } else if(!missing(inputValues[index])) { + if(!missing(inputValues[index])) { iFixed[iFixedSize] = index; iFixedSize++; estimCover[index] = inputValues[index]; @@ -1165,21 +1158,21 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT 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[0]) && !fullVeg) { - 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); + + // 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[0]) && !fullVeg) { + 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; } - if(EQ(C4Species, 0.)) estimCover[C4Index] = 0; - // Paruelo & Lauenroth (1996): C3-grass climate-relationship: if(winterMAP <= 0) { C3Grassland = C3Shrubland = 0; @@ -1349,6 +1342,9 @@ void uniqueIndices(int arrayOne[], int arrayTwo[], int arrayOneSize, int arrayTw 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; From 18b6449087453dda9dd78e56cdb714895284bf06 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sat, 27 Aug 2022 10:34:40 -0700 Subject: [PATCH 145/326] Fixed bugs in `estimatePotNatVegComposition()` - Added code to determine if sum of grasses is fixed - Prevented possibility of dividing by zero in grasses - Fixed code that removes grass indices from estimated array --- SW_VegProd.c | 50 +++++++++++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index fe3651380..61b463c2c 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -995,10 +995,11 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT double totalSumGrasses = 0., inputSumGrasses = 0., tempDiffJanJul, summerMAP = 0., winterMAP = 0., C4Species = 0., C3Grassland, C3Shrubland, estimGrassSum = 0, finalVegSum = 0., estimCoverSum = 0., tempSumGrasses = 0., estimCover[nTypes], - initialVegSum = 0.; + initialVegSum = 0., tempValue; - Bool fixSumGrasses, fixBareGround = (Bool) (missing(inputValues[bareGround])), - fullVeg = swFALSE, isGrassIndex = swFALSE; + Bool fixSumGrasses = (Bool) (!missing(SumGrassesFraction)), + fixBareGround = (Bool) (missing(inputValues[bareGround])), fullVeg = swFALSE, + isGrassIndex = swFALSE; for(index = 0; index < nTypes; index++) { if(!missing(inputValues[index])) { @@ -1006,7 +1007,13 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT } } - if(initialVegSum >= 1.) fullVeg = swTRUE; + if(initialVegSum == 1.) { + fullVeg = swTRUE; + } else if(initialVegSum > 1.) { + LogError(logfp, LOGFATAL, "'estimate_PotNatVeg_composition': ", + "User defined relative abundance values sum to more than ", + "1 = full land cover."); + } // Initialize estimCover and overallEstim for(index = 0; index < nTypes; index++) { @@ -1021,17 +1028,15 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT estimCover[index] = 0.; } } - - fixSumGrasses = (Bool) (!missing(inputValues[grassAnn]) && !missing(inputValues[C3Index]) - && !missing(inputValues[C3Index]) && !missing(SumGrassesFraction)); + fixSumGrasses = (Bool) (!missing(SumGrassesFraction)); // Check if grasses are fixed if(fixSumGrasses) { // Set SumGrassesFraction // If SumGrassesFraction < 0, set to zero, otherwise keep at value - SumGrassesFraction = (missing(SumGrassesFraction)) ? 0 : SumGrassesFraction; + cutZeroInf(SumGrassesFraction); // Get sum of input grass values and set to inputSumGrasses - for(index = C3Index; index < grassAnn; index++) { + for(index = C3Index; index <= grassAnn; index++) { if(!missing(inputValues[index])) inputSumGrasses += inputValues[index]; } @@ -1226,12 +1231,16 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT 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; + 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 / estimGrassSum); + estimCover[grassesEstim[index]] = (totalSumGrasses / grassEstimSize); } LogError(logfp, LOGWARN, "'estimate_PotNatVeg_composition': " "Total grass cover set, but no grass cover estimated; " @@ -1243,18 +1252,21 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT uniqueIndices(iFixed, grassesEstim, iFixedSize, grassEstimSize, iFixed, &iFixedSize); // Remove them from the `estimIndices` array - for(index = 0; index < estimIndicesSize; index++) { + for(index = 0; index < overallEstimSize; index++) { do { - isGrassIndex = (Bool) (overallEstim[index] == grassAnn - || overallEstim[index] == treeIndex - || overallEstim[index] == bareGround); - - if(isGrassIndex) { - overallEstim[estimIndicesSize - 1] = overallEstim[index]; - estimIndicesSize--; + isGrassIndex = (Bool) (overallEstim[index] == C3Index + || overallEstim[index] == C4Index + || overallEstim[index] == grassAnn); + + if(isGrassIndex && index + 1 != overallEstimSize) { + tempValue = overallEstim[overallEstimSize - 1]; + overallEstim[overallEstimSize - 1] = overallEstim[index]; + overallEstim[index] = tempValue; + overallEstimSize--; } - } while(index != estimIndicesSize - 1 && isGrassIndex); + } while(index != overallEstimSize - 1 && isGrassIndex); } + overallEstimSize--; } for(index = 0; index < iFixedSize; index++) { From c30831aa7381ad76f0a5d940b4a4d4f37ecb1894 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 28 Aug 2022 18:33:46 -0700 Subject: [PATCH 146/326] Adjusted `calcSiteClimate()` to handle both north/south - `calcSiteClimate()` now handles southern hemisphere calculations - Updated documentation of `calcSiteClimate()` - Unit test for southern hemisphere now contains comments next to tests that are the new outputs the program is outputting - Test values have yet to be finalized --- SW_Weather.c | 58 +++++++++++++++++++++++++++-------------- test/test_SW_Weather.cc | 34 ++++++++++++------------ 2 files changed, 56 insertions(+), 36 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 5fe73753d..29f575720 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -125,7 +125,8 @@ void averageClimateAcrossYears(SW_CLIMATE_YEARLY *climateOutput, int numYears, When site is in southern hemisphere, the first and last six months of data are ignored. The beginning of the year starts on the first day of July and ends on June 30th of the next calendar year. Due to this, what is referred to as - the "adjusted" calendar year and is shifted six months in comparison to calendar years. + the "adjusted" calendar year and is shifted six months in comparison to calendar years. The southern year + starts with 1981 instead of 1980 to handle leap years better. @param[in] allHist Array containing all historical data of a site @param[in] numYears Number of years represented by `allHist` @@ -138,8 +139,8 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, SW_CLIMATE_YEARLY *climateOutput, Bool isNorth) { int month, yearIndex, year, day, numDaysYear, currMonDay; - int numDaysMonth = (isNorth) ? Time_days_in_month(Jan) : Time_days_in_month(Jul); - int adjustedDoy, adjustedYear = 0, secondMonth, seventhMonth, adjustedFullYear; + int numDaysMonth, adjustedDoy, adjustedYear = 0, secondMonth, seventhMonth, + adjustedStartYear; double currentTempMin, currentTempMean, totalAbove65, currentJulyMin, JulyPPT, consecNonFrost, currentNonFrost; @@ -157,8 +158,25 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, calcSiteClimateLatInvariants(allHist, numYears, startYear, climateOutput); - for(yearIndex = 0; yearIndex < numYears; yearIndex++) { - year = yearIndex + startYear; + // Set everything that is dependent on north/south before main loop is entered + if(isNorth) { + 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->minTempJuly_C[0] = SW_MISSING; + climateOutput->PPTJuly_mm[0] = SW_MISSING; + climateOutput->ddAbove65F_degday[0] = SW_MISSING; + climateOutput->frostFree_days[0] = SW_MISSING; + } + + for(yearIndex = adjustedStartYear; yearIndex < numYears; yearIndex++) { + year = (isNorth) ? yearIndex + startYear : yearIndex + startYear + 1; Time_new_year(year); numDaysYear = Time_get_lastdoy_y(year); month = (isNorth) ? Jan : Jul; @@ -168,31 +186,33 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, currentNonFrost = 0; consecNonFrost = 0; JulyPPT = 0; - if(isNorth) { - secondMonth = Feb; - seventhMonth = Jul; - } else { - secondMonth = Aug; - seventhMonth = Jan; - } for(day = 0; day < numDaysYear; day++) { if(isNorth) { adjustedDoy = day; adjustedYear = yearIndex; } else { - // Check if current year is leap year + // Adjust year and day to meet southern hemisphere requirements adjustedDoy = (numDaysYear == 366) ? day + 182 : day + 181; adjustedDoy = adjustedDoy % 365; - adjustedYear = (adjustedDoy == 0) ? yearIndex + 1 : adjustedYear; - adjustedFullYear = adjustedYear + startYear; - if(adjustedDoy == 0) Time_new_year(adjustedFullYear); + + if(adjustedDoy == 0) { + adjustedYear++; + } } - if(month == Jul && yearIndex == numYears - 1 && !isNorth) break; + if(month == Jul && adjustedYear >= numYears - 1 && !isNorth) { + // Set all current accumulated values to SW_MISSING to prevent + // a zero going into the last year of the arrays + JulyPPT = 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]; @@ -227,7 +247,7 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, totalAbove65 += (currentTempMean > 0.0) ? currentTempMean : 0.; } - climateOutput->minTempJuly_C[yearIndex] = (missing(currentJulyMin)) ? 0 : currentJulyMin; + climateOutput->minTempJuly_C[yearIndex] = currentJulyMin; climateOutput->PPTJuly_mm[yearIndex] = JulyPPT; climateOutput->ddAbove65F_degday[yearIndex] = totalAbove65; diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index f45166dab..0ccc97700 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -417,16 +417,16 @@ namespace { // Climate variables used for C4 grass cover // (stdev of one value is undefined) - EXPECT_NEAR(climateOutput.minTempJuly_C[0], -16.98, tol6); - EXPECT_NEAR(climateOutput.frostFree_days[0], 76, tol6); - EXPECT_NEAR(climateOutput.ddAbove65F_degday[0], 16.990001, tol6); + EXPECT_NEAR(climateOutput.minTempJuly_C[1], -16.98, tol6); + EXPECT_NEAR(climateOutput.frostFree_days[1], 76, tol6); // 79 + EXPECT_NEAR(climateOutput.ddAbove65F_degday[1], 16.990001, tol6); // 16.965000 // Climate variables used for cheatgrass cover // (stdev of one value is undefined) - EXPECT_NEAR(climateOutput.PPTJuly_mm[0], 24.699999, tol6); - EXPECT_NEAR(climateOutput.meanTempDriestQtr_C[0], 0.936387, tol6); - EXPECT_NEAR(climateOutput.minTempFeb_C[0], 5.1445161, tol6); + EXPECT_NEAR(climateOutput.PPTJuly_mm[1], 24.699999, tol6); // 22.19999 + EXPECT_NEAR(climateOutput.meanTempDriestQtr_C[1], 0.936387, tol6); // 15.8733906 + EXPECT_NEAR(climateOutput.minTempFeb_C[1], 5.1445161, tol6); // 5.3467742 // --- Long-term variables (aggregated across years) ------ @@ -454,22 +454,22 @@ namespace { EXPECT_NEAR(climateAverages.meanTemp_C, 4.154009, tol6); // Climate variables used for C4 grass cover - EXPECT_NEAR(climateAverages.minTempJuly_C, -27.243870, tol6); - EXPECT_NEAR(climateAverages.frostFree_days, 68.290323, tol6); - EXPECT_NEAR(climateAverages.ddAbove65F_degday, 20.684935, tol6); + EXPECT_NEAR(climateAverages.minTempJuly_C, -27.243870, tol6); // -27.146999 + EXPECT_NEAR(climateAverages.frostFree_days, 68.290323, tol6); // 72.6333333 + EXPECT_NEAR(climateAverages.ddAbove65F_degday, 20.684935, tol6); // 21.2880665 EXPECT_NEAR(climateAverages.sdC4[0], 5.241726, tol6); - EXPECT_NEAR(climateAverages.sdC4[1], 13.446669, tol6); - EXPECT_NEAR(climateAverages.sdC4[2], 19.755513, tol6); + EXPECT_NEAR(climateAverages.sdC4[1], 13.446669, tol6); // 9.4229482 + EXPECT_NEAR(climateAverages.sdC4[2], 19.755513, tol6); // 19.589081 // Climate variables used for cheatgrass cover - EXPECT_NEAR(climateAverages.PPTJuly_mm, 22.199999, tol6); - EXPECT_NEAR(climateAverages.meanTempDriestQtr_C, 11.524859, tol6); - EXPECT_NEAR(climateAverages.minTempFeb_C, -13.904599, tol6); + EXPECT_NEAR(climateAverages.PPTJuly_mm, 22.199999, tol6); // 65.75333 + EXPECT_NEAR(climateAverages.meanTempDriestQtr_C, 11.524859, tol6); // 11.4012 + EXPECT_NEAR(climateAverages.minTempFeb_C, -13.904599, tol6); // 6.5445577 - EXPECT_NEAR(climateAverages.sdCheatgrass[0], 21.598367, tol6); - EXPECT_NEAR(climateAverages.sdCheatgrass[1], 7.171922, tol6); - EXPECT_NEAR(climateAverages.sdCheatgrass[2], 2.618434, tol6); + EXPECT_NEAR(climateAverages.sdCheatgrass[0], 21.598367, tol6); // 35.46237 + EXPECT_NEAR(climateAverages.sdCheatgrass[1], 7.171922, tol6); // 7.260851 + EXPECT_NEAR(climateAverages.sdCheatgrass[2], 2.618434, tol6); // 1.6247347 // ------ Reset and deallocate From 391959476a73727aef77ad65518df30a67669256 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 28 Aug 2022 18:36:10 -0700 Subject: [PATCH 147/326] Updated `mean()` and `standardDeviation()` to handle SW_MISSING - These two functions now ignore any value of SW_MISSING so they don't harm the mean/standard deviation of the input array --- generic.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/generic.c b/generic.c index 51e86df4d..e13d50969 100644 --- a/generic.c +++ b/generic.c @@ -428,12 +428,18 @@ double final_running_sd(unsigned int n, double ssqr) @param length Size of input array may be years, months, days, etc. */ double mean(double values[], int length) { - int index; - double total = 0.0; + + int index, finalLength = 0; + double total = 0.0, currentVal; for(index = 0; index < length; index++) { - total += values[index]; + currentVal = values[index]; + + if(!missing(currentVal)) { + total += currentVal; + finalLength++; + } } - return total / length; + return total / finalLength; } /** @@ -447,14 +453,18 @@ double mean(double values[], int length) { */ double standardDeviation(double inputArray[], int length) { - int index; - double arrayMean = mean(inputArray, length), total = 0.0; + int index, finalLength = 0; + double arrayMean = mean(inputArray, length), total = 0.0, currentVal; for(index = 0; index < length; index++) { + currentVal = inputArray[index]; - total += (inputArray[index] - arrayMean) * (inputArray[index] - arrayMean); + if(!missing(currentVal)) { + total += (currentVal - arrayMean) * (currentVal - arrayMean); + finalLength++; + } } - return sqrt(total / (length - 1)); + return sqrt(total / (finalLength - 1)); } From ca42c9372d7ad3b1892256c6467a3b20bdb7ccab Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 28 Aug 2022 21:07:27 -0700 Subject: [PATCH 148/326] Updated `estimatePotNatVegComposition()` "inputValues" description --- SW_VegProd.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index 61b463c2c..c18807d30 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -956,7 +956,8 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe @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 starting values for the function to start with + @param[in] inputValues Array of size eight that contains starting values for the function to start with. + The elements of compositions are: 0) Succulents 1) Forbs 2) C3 3) C4 4) Grass Annuals 5) Shrubs 6) Trees 7) Bare ground @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 grass if user would like it to be fixed From c45e14c4498f7fdfabb7c9b056e248162e21dda2 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 28 Aug 2022 23:12:03 -0700 Subject: [PATCH 149/326] Updated tests for vegetation - Added new tests when "SumGrassesFraction" is fixed - Added variable "isNorth" to send into `estimatePotNatVegComposition()` - "NotFullVegetation" and "FullVegetation" test blocks now have a description of what the block does in relation to input cover values - Tests when "isNorth" is false will be used once calcSiteClimate's southern hemisphere adjustment is confirmed to be working properly --- test/test_SW_VegProd.cc | 111 +++++++++++++++++++++++++++++++--------- 1 file changed, 87 insertions(+), 24 deletions(-) diff --git a/test/test_SW_VegProd.cc b/test/test_SW_VegProd.cc index b1cb15d22..d9da48296 100644 --- a/test/test_SW_VegProd.cc +++ b/test/test_SW_VegProd.cc @@ -161,6 +161,12 @@ namespace { TEST(EstimateVegetationTest, NotFullVegetation) { + /* ================================================================ + This block of tests deals with input values to + `estimatePotNatVegComposition()` that do not add up to 1 + ================================================================ */ + + SW_CLIMATE_YEARLY climateOutput; SW_CLIMATE_CLIM climateAverages; @@ -177,7 +183,7 @@ namespace { // Array holding all values from estimation minus grasses double RelAbundanceL1[5]; // 5 = Number of types minus grasses - double SumGrassesFraction = -SW_MISSING; + double SumGrassesFraction = SW_MISSING; double C4Variables[3]; double RelAbundanceL0Expected[8] = {0.0, 0.2608391, 0.4307062, 0.0, 0.0, 0.3084547, 0.0, 0.0}; @@ -186,6 +192,7 @@ namespace { Bool fillEmptyWithBareGround = swTRUE; Bool inNorth = swTRUE; Bool warnExtrapolation = swTRUE; + Bool isNorth = swTRUE; int deallocate = 0; int allocate = 1; @@ -215,7 +222,7 @@ namespace { allocDeallocClimateStructs(allocate, 31, &climateOutput, &climateAverages); // Calculate climate of the site and add results to "climateOutput" - calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput); + calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput, isNorth); // Average values from "climateOutput" and put them in "climateAverages" averageClimateAcrossYears(&climateOutput, 31, &climateAverages); @@ -376,27 +383,77 @@ namespace { Test with `inNorth` to be false, same input values as previous test except for trees and bare ground which are both .0549 ================================== */ - RelAbundanceL0Expected[succIndex] = 0.1098; - RelAbundanceL0Expected[forbIndex] = 0.1098; - RelAbundanceL0Expected[C3Index] = 0.1098; - RelAbundanceL0Expected[C4Index] = 0.1098; - RelAbundanceL0Expected[grassAnn] = 0.1098; - RelAbundanceL0Expected[shrubIndex] = 0.1098; - RelAbundanceL0Expected[treeIndex] = 0.0549; - RelAbundanceL0Expected[bareGround] = 0.2863; - - RelAbundanceL1Expected[treeIndexL1] = 0.0549; - RelAbundanceL1Expected[forbIndexL1] = 0.1098; - RelAbundanceL1Expected[shrubIndexL1] = 0.2196; - RelAbundanceL1Expected[grassesIndexL1] = 0.3294; - RelAbundanceL1Expected[bareGroundL1] = 0.2863; - - inNorth = swFALSE; - fillEmptyWithBareGround = swTRUE; +// RelAbundanceL0Expected[succIndex] = 0.1098; +// RelAbundanceL0Expected[forbIndex] = 0.1098; +// RelAbundanceL0Expected[C3Index] = 0.1098; +// RelAbundanceL0Expected[C4Index] = 0.1098; +// RelAbundanceL0Expected[grassAnn] = 0.1098; +// RelAbundanceL0Expected[shrubIndex] = 0.1098; +// RelAbundanceL0Expected[treeIndex] = 0.0549; +// RelAbundanceL0Expected[bareGround] = 0.2863; +// +// RelAbundanceL1Expected[treeIndexL1] = 0.0549; +// RelAbundanceL1Expected[forbIndexL1] = 0.1098; +// RelAbundanceL1Expected[shrubIndexL1] = 0.2196; +// RelAbundanceL1Expected[grassesIndexL1] = 0.3294; +// RelAbundanceL1Expected[bareGroundL1] = 0.2863; +// +// inNorth = swFALSE; +// fillEmptyWithBareGround = swTRUE; +// +// inputValues[treeIndex] = .0549; +// inputValues[bareGround] = .0549; +// +// estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, +// climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, +// SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorth, warnExtrapolation, +// grassOutput, RelAbundanceL0, RelAbundanceL1); +// +// // Loop through RelAbundanceL0 and test results. +// for(index = 0; index < 8; 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); +// } +// +// EXPECT_NEAR(grassOutput[0], .333333, tol6); +// EXPECT_NEAR(grassOutput[1], .333333, tol6); +// EXPECT_NEAR(grassOutput[2], .333333, tol6); + /* ================================== + Test with `SumGrassesFraction` being fixed, all input of previous tests + are halved to .0549 + ================================== */ + inputValues[succIndex] = .0549; + inputValues[forbIndex] = .0549; + inputValues[C3Index] = SW_MISSING; + inputValues[C4Index] = SW_MISSING; + inputValues[grassAnn] = SW_MISSING; + inputValues[shrubIndex] = .0549; inputValues[treeIndex] = .0549; inputValues[bareGround] = .0549; + RelAbundanceL0Expected[succIndex] = .0549; + RelAbundanceL0Expected[forbIndex] = .0549; + RelAbundanceL0Expected[C3Index] = 0.7255; + RelAbundanceL0Expected[C4Index] = 0.0; + RelAbundanceL0Expected[grassAnn] = 0.0; + RelAbundanceL0Expected[shrubIndex] = .0549; + RelAbundanceL0Expected[treeIndex] = .0549; + RelAbundanceL0Expected[bareGround] = 0.0549; + + RelAbundanceL1Expected[treeIndexL1] = .0549; + RelAbundanceL1Expected[forbIndexL1] = .0549; + RelAbundanceL1Expected[shrubIndexL1] = 0.1098; + RelAbundanceL1Expected[grassesIndexL1] = 0.7255; + RelAbundanceL1Expected[bareGroundL1] = 0.0549; + + fillEmptyWithBareGround = swTRUE; + SumGrassesFraction = .7255; + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorth, warnExtrapolation, @@ -412,9 +469,9 @@ namespace { EXPECT_NEAR(RelAbundanceL1[index], RelAbundanceL1Expected[index], tol6); } - EXPECT_NEAR(grassOutput[0], .333333, tol6); - EXPECT_NEAR(grassOutput[1], .333333, tol6); - EXPECT_NEAR(grassOutput[2], .333333, tol6); + EXPECT_NEAR(grassOutput[0], 1., tol6); + EXPECT_NEAR(grassOutput[1], 0.0, tol6); + EXPECT_NEAR(grassOutput[2], 0.0, tol6); // Deallocate structs allocDeallocClimateStructs(deallocate, 31, &climateOutput, &climateAverages); @@ -422,6 +479,11 @@ namespace { TEST(EstimateVegetationTest, FullVegetation) { + /* ================================================================ + This block of tests deals with input values to + `estimatePotNatVegComposition()` that add up to 1 + ================================================================ */ + SW_CLIMATE_YEARLY climateOutput; SW_CLIMATE_CLIM climateAverages; @@ -438,7 +500,7 @@ namespace { // Array holding all values from estimation minus grasses double RelAbundanceL1[5]; // 5 = Number of types minus grasses - double SumGrassesFraction = -SW_MISSING; + double SumGrassesFraction = SW_MISSING; double C4Variables[3]; double RelAbundanceL0Expected[8] = {0.0567, 0.2317, .0392, .0981, .3218, .0827, .1293, .0405}; @@ -447,6 +509,7 @@ namespace { Bool fillEmptyWithBareGround = swTRUE; Bool inNorth = swTRUE; Bool warnExtrapolation = swTRUE; + Bool isNorth = swTRUE; int deallocate = 0; int allocate = 1; @@ -476,7 +539,7 @@ namespace { allocDeallocClimateStructs(allocate, 31, &climateOutput, &climateAverages); // Calculate climate of the site and add results to "climateOutput" - calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput); + calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput, isNorth); // Average values from "climateOutput" and put them in "climateAverages" averageClimateAcrossYears(&climateOutput, 31, &climateAverages); From 677ee6cff4750e8114b6c45503c6e0a4b6322f92 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 1 Sep 2022 23:30:51 -0700 Subject: [PATCH 150/326] Fixed bug/commented `estimatePotNatVegComposition()` - Fixed bug where final total of a category is more than one i.e., set them to zero instead of SW_MISSING - Added more comments to explain more of what certain parts of the program are doing --- SW_VegProd.c | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index c18807d30..8a55118da 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -892,7 +892,7 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe double SumGrassesFraction = SW_MISSING, C4Variables[3], grassOutput[3], RelAbundanceL0[8], RelAbundanceL1[5]; - Bool fillEmptyWithBareGround = swTRUE, warnExtrapolation = swTRUE; + Bool fillEmptyWithBareGround = swFALSE, warnExtrapolation = swTRUE; Bool inNorth; if(latitude > 0.0) { @@ -988,13 +988,13 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT // 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, frostFreeDays = 1, degreeAbove65 = 2, estimIndicesSize = 0, - estimIndicesNotNA = 0, grassesEstim[3], overallEstim[nTypes], iFixed[nTypes], - iFixedSize = 0, isetIndices[3] = {grassAnn, treeIndex, bareGround}; + julyMin = 0, frostFreeDays = 1, degreeAbove65 = 2, estimIndicesNotNA = 0, grassesEstim[3], + overallEstim[nTypes], iFixed[nTypes], iFixedSize = 0, + isetIndices[3] = {grassAnn, treeIndex, bareGround}; // Totals of different areas of variables double totalSumGrasses = 0., inputSumGrasses = 0., tempDiffJanJul, - summerMAP = 0., winterMAP = 0., C4Species = 0., C3Grassland, C3Shrubland, estimGrassSum = 0, + summerMAP = 0., winterMAP = 0., C4Species = SW_MISSING, C3Grassland, C3Shrubland, estimGrassSum = 0, finalVegSum = 0., estimCoverSum = 0., tempSumGrasses = 0., estimCover[nTypes], initialVegSum = 0., tempValue; @@ -1002,6 +1002,7 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT fixBareGround = (Bool) (missing(inputValues[bareGround])), fullVeg = swFALSE, isGrassIndex = swFALSE; + // Loop through inputValues and get the total for(index = 0; index < nTypes; index++) { if(!missing(inputValues[index])) { initialVegSum += inputValues[index]; @@ -1016,7 +1017,7 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT "1 = full land cover."); } - // Initialize estimCover and overallEstim + // Initialize overallEstim and add fixed indices to `iFixed` for(index = 0; index < nTypes; index++) { if(!missing(inputValues[index])) { iFixed[iFixedSize] = index; @@ -1029,7 +1030,6 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT estimCover[index] = 0.; } } - fixSumGrasses = (Bool) (!missing(SumGrassesFraction)); // Check if grasses are fixed if(fixSumGrasses) { @@ -1104,16 +1104,17 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT estimCover[bareGround] = 1.; } else { // Set months of winter and summer (northern/southern hemisphere) + // and get their three month values in precipitation and temperature if(inNorth) { for(index = 0; index < 3; index++) { - winterMonths[index] = (index + 11) % 12; + 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) % 12; + summerMonths[index] = (index + 11) % MAX_MONTHS; winterMonths[index] = (index + 5); summerMAP += PPTMon_cm[summerMonths[index]]; winterMAP += PPTMon_cm[winterMonths[index]]; @@ -1198,7 +1199,7 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT } // Paruelo & Lauenroth (1996): forb climate-relationship: if((PPT_cm * 10 < 1 || meanTemp_C <= 0) && !fullVeg) { - estimCover[forbIndex] = SW_MISSING; + estimCover[forbIndex] = 0.; } else { if(missing(inputValues[forbIndex]) && !fullVeg) { estimCover[forbIndex] = cutZeroInf(-.2035 + (.07975 * log(PPT_cm * 10)) @@ -1206,8 +1207,8 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT } } // Paruelo & Lauenroth (1996): succulent climate-relationship: - if((tempDiffJanJul <= 0 || winterMAP <= 0)) { - estimCover[succIndex] = SW_MISSING; + if(tempDiffJanJul <= 0 || winterMAP <= 0) { + estimCover[succIndex] = 0.; } else { if(missing(inputValues[succIndex])) { estimCover[succIndex] = @@ -1216,6 +1217,8 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT } } + // 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.; @@ -1270,9 +1273,12 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT overallEstimSize--; } + // Get final estimated vegetation sum for(index = 0; index < iFixedSize; index++) { finalVegSum += estimCover[iFixed[index]]; } + + // 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]]; @@ -1298,6 +1304,9 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT } } + // Fill in all output arrays (grassOutput, RelAbundanceL0, RelAbundanceL1) + // with values from estimated array + grassOutput[0] = estimCover[C3Index]; grassOutput[1] = estimCover[C4Index]; grassOutput[2] = estimCover[grassAnn]; @@ -1359,9 +1368,11 @@ void uniqueIndices(int arrayOne[], int arrayTwo[], int arrayOneSize, int arrayTw memset(tempArraySeen, 0, sizeof(int) * nTypes); for(index = 0; index < tempSize; index++) { - // Initalize the `seen` version of tempArray + // 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++; @@ -1376,6 +1387,7 @@ void uniqueIndices(int arrayOne[], int arrayTwo[], int arrayOneSize, int arrayTw } } + // 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) { From 969d428443f5a42ede3c6f167247ef649703c703 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 1 Sep 2022 23:33:39 -0700 Subject: [PATCH 151/326] Modified `LogError()` to use `error()` and `warning()` - `LogError()` now uses `error()` and `warning()` instead of `REvprintf()` - Moved "if(LOGEXIT & mode) {...}" into part of ifdef that is when rSOILWAT is not defined --- filefuncs.c | 82 +++++++++++++++++++++++++++-------------------------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/filefuncs.c b/filefuncs.c index bfdd15e5d..d5a9f83e4 100644 --- a/filefuncs.c +++ b/filefuncs.c @@ -140,52 +140,54 @@ 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. - */ - - char outfmt[MAX_ERROR] = {0}; /* to prepend err type str */ - 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"); + /* 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 */ + 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"); - #ifdef RSOILWAT - if (fp != NULL) { - REvprintf(outfmt, args); - } + #ifdef RSOILWAT + if(LOGEXIT & mode) { + error(outfmt, args); + } else if(fp != NULL) { + warning(outfmt, args); + } - #else - int check_eof; - check_eof = (EOF == vfprintf(fp, outfmt, args)); + #else + 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"); - fflush(fp); - #endif + if (check_eof) + sw_error(0, "SYSTEM: Cannot write to FILE *fp in LogError()\n"); + fflush(fp); + if (LOGEXIT & mode) { + sw_error(-1, "@ generic.c LogError"); + } + #endif - logged = swTRUE; - va_end(args); - if (LOGEXIT & mode) { - sw_error(-1, "@ generic.c LogError"); - } + logged = swTRUE; + va_end(args); } From 87d8f2b4c8d74154e18d84d83ff3ef8feaf1aac6 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 1 Sep 2022 23:57:49 -0700 Subject: [PATCH 152/326] Added comments to climate functions and eliminated warning - Added comments to climate functions to increase readability of code - Replaced numbers 11 and 0 with Dec and Jan to be more descriptive and 12 with MAX_MONTHS - Deleted "monDay" in `calcSiteClimateLatInvariants()` to eliminate warning when compiled --- SW_Weather.c | 53 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 29f575720..b44be9ed4 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -92,14 +92,18 @@ 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->PPTJuly_mm = mean(climateOutput->PPTJuly_mm, numYears); @@ -144,7 +148,8 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, double currentTempMin, currentTempMean, totalAbove65, currentJulyMin, JulyPPT, 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); @@ -158,7 +163,7 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, calcSiteClimateLatInvariants(allHist, numYears, startYear, climateOutput); - // Set everything that is dependent on north/south before main loop is entered + // Set starting conditions that are dependent on north/south before main loop is entered if(isNorth) { secondMonth = Feb; seventhMonth = Jul; @@ -175,6 +180,7 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, climateOutput->frostFree_days[0] = SW_MISSING; } + // Loop through all years of data starting at "adjustedStartYear" for(yearIndex = adjustedStartYear; yearIndex < numYears; yearIndex++) { year = (isNorth) ? yearIndex + startYear : yearIndex + startYear + 1; Time_new_year(year); @@ -215,13 +221,15 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, 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){ currentJulyMin = (currentTempMin < currentJulyMin) ? currentTempMin : currentJulyMin; JulyPPT += 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){ @@ -231,10 +239,13 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, currentNonFrost = 0.; } + // Gather minimum temperature of second month of year if(month == secondMonth) { climateOutput->minTempFeb_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->minTempFeb_C[yearIndex] /= numDaysMonth; @@ -243,10 +254,12 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, currMonDay = 0; } + // Accumulate information of degrees above 65ºF currentTempMean -= 18.333; totalAbove65 += (currentTempMean > 0.0) ? currentTempMean : 0.; } + // Set all values climateOutput->minTempJuly_C[yearIndex] = currentJulyMin; climateOutput->PPTJuly_mm[yearIndex] = JulyPPT; climateOutput->ddAbove65F_degday[yearIndex] = totalAbove65; @@ -268,7 +281,7 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, void calcSiteClimateLatInvariants(SW_WEATHER_HIST **allHist, int numYears, int startYear, SW_CLIMATE_YEARLY *climateOutput) { - int month = Jan, monDay, numDaysMonth = Time_days_in_month(month), yearIndex, + int month = Jan, numDaysMonth = Time_days_in_month(month), yearIndex, day, numDaysYear, currMonDay, year; for(yearIndex = 0; yearIndex < numYears; yearIndex++) { @@ -290,7 +303,7 @@ void calcSiteClimateLatInvariants(SW_WEATHER_HIST **allHist, int numYears, int s climateOutput->maxTempMon_C[month][yearIndex] /= numDaysMonth; climateOutput->minTempMon_C[month][yearIndex] /= numDaysMonth; - month = (month + 1) % 12; + month = (month + 1) % MAX_MONTHS; numDaysMonth = Time_days_in_month(month); currMonDay = 0; } @@ -314,14 +327,13 @@ void calcSiteClimateLatInvariants(SW_WEATHER_HIST **allHist, int numYears, int s void findDriestQtr(double *meanTempDriestQtr_C, int numYears, double **meanTempMon_C, double **PPTMon_cm, Bool isNorth) { - int yearIndex, month, prevMonth, nextMonth, adjustedMonth; + int yearIndex, month, prevMonth, nextMonth, adjustedMonth, numQuarterMonths = 3; - double defaultVal = 999., driestThreeMonPPT, driestMeanTemp, - currentQtrPPT, currentQtrTemp; + double driestThreeMonPPT, driestMeanTemp, currentQtrPPT, currentQtrTemp; for(yearIndex = 0; yearIndex < numYears; yearIndex++) { - driestThreeMonPPT = defaultVal; - driestMeanTemp = defaultVal; + driestThreeMonPPT = SW_MISSING; + driestMeanTemp = SW_MISSING; for(month = 0; month < MAX_MONTHS; month++) { if(isNorth) { @@ -331,25 +343,30 @@ void findDriestQtr(double *meanTempDriestQtr_C, int numYears, double **meanTempM adjustedMonth %= MAX_MONTHS; } - prevMonth = (adjustedMonth == 0) ? 11 : adjustedMonth - 1; - nextMonth = (adjustedMonth == 11) ? 0 : adjustedMonth + 1; - + // Get next and previous months + prevMonth = (adjustedMonth == Jan) ? Dec : adjustedMonth - 1; + nextMonth = (adjustedMonth == Dec) ? Jan : adjustedMonth + 1; + + // Get precipitation of current quarter currentQtrPPT = (PPTMon_cm[prevMonth][yearIndex]) + (PPTMon_cm[adjustedMonth][yearIndex]) + (PPTMon_cm[nextMonth][yearIndex]); - + + // Get temperature of current quarter currentQtrTemp = (meanTempMon_C[prevMonth][yearIndex]) + (meanTempMon_C[adjustedMonth][yearIndex]) + (meanTempMon_C[nextMonth][yearIndex]); - + + // 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 / 3; + meanTempDriestQtr_C[yearIndex] = driestMeanTemp / numQuarterMonths; } From 5f14c4e792301778a74050d85703210cf40e862f Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Mon, 12 Sep 2022 00:48:18 -0700 Subject: [PATCH 153/326] Changed variable names containing months and updated test values - Updated variables containing "Feb" or "July" in their name with "2ndMon" and "7thMon" - Updated unit test values * Values are different from rSOILWAT because the process is different in C compared to R *. The C side starts at July 1st of the first year of data and skips 1980, starting at 1981 (going off of 365 days instead of 366) * Data starting from 1980 is considered 1981 to the C program, helping deal with leap years * R starts at 1980 and skips a few days starting a year at July 3rd/4th if it found it necessary, yielding different values than expected --- SW_Weather.c | 43 +++++++++++------------ SW_Weather.h | 12 +++---- test/test_SW_Weather.cc | 78 ++++++++++++++++++++--------------------- 3 files changed, 66 insertions(+), 67 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index b44be9ed4..72b6fa0fe 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -106,22 +106,22 @@ void averageClimateAcrossYears(SW_CLIMATE_YEARLY *climateOutput, int numYears, // C4 and Cheatgrass variables climateAverages->PPT_cm = mean(climateOutput->PPT_cm, numYears); climateAverages->meanTemp_C = mean(climateOutput->meanTemp_C, numYears); - climateAverages->PPTJuly_mm = mean(climateOutput->PPTJuly_mm, numYears); + climateAverages->PPT7thMon_mm = mean(climateOutput->PPT7thMon_mm, numYears); climateAverages->meanTempDriestQtr_C = mean(climateOutput->meanTempDriestQtr_C, numYears); - climateAverages->minTempFeb_C = mean(climateOutput->minTempFeb_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->minTempJuly_C = mean(climateOutput->minTempJuly_C, numYears); + climateAverages->minTemp7thMon_C = mean(climateOutput->minTemp7thMon_C, numYears); // Calculate and set standard deviation of C4 variables - climateAverages->sdC4[0] = standardDeviation(climateOutput->minTempJuly_C, numYears); + 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->PPTJuly_mm, numYears); + climateAverages->sdCheatgrass[0] = standardDeviation(climateOutput->PPT7thMon_mm, numYears); climateAverages->sdCheatgrass[1] = standardDeviation(climateOutput->meanTempDriestQtr_C, numYears); - climateAverages->sdCheatgrass[2] = standardDeviation(climateOutput->minTempFeb_C, numYears); + climateAverages->sdCheatgrass[2] = standardDeviation(climateOutput->minTemp2ndMon_C, numYears); } /** @@ -158,8 +158,8 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, } memset(climateOutput->PPT_cm, 0., sizeof(double) * numYears); memset(climateOutput->meanTemp_C, 0., sizeof(double) * numYears); - memset(climateOutput->minTempFeb_C, 0., sizeof(double) * numYears); - memset(climateOutput->minTempJuly_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); @@ -174,15 +174,15 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, seventhMonth = Jan; numDaysMonth = Time_days_in_month(Jul); adjustedStartYear = 1; - climateOutput->minTempJuly_C[0] = SW_MISSING; - climateOutput->PPTJuly_mm[0] = SW_MISSING; + 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 = (isNorth) ? yearIndex + startYear : yearIndex + startYear + 1; + year = yearIndex + startYear; Time_new_year(year); numDaysYear = Time_get_lastdoy_y(year); month = (isNorth) ? Jan : Jul; @@ -241,14 +241,13 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, // Gather minimum temperature of second month of year if(month == secondMonth) { - climateOutput->minTempFeb_C[yearIndex] += allHist[adjustedYear]->temp_min[adjustedDoy]; + 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->minTempFeb_C[yearIndex] /= numDaysMonth; - + if(month == secondMonth) climateOutput->minTemp2ndMon_C[yearIndex] /= numDaysMonth; month = (month + 1) % 12; numDaysMonth = Time_days_in_month(month); currMonDay = 0; @@ -260,8 +259,8 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, } // Set all values - climateOutput->minTempJuly_C[yearIndex] = currentJulyMin; - climateOutput->PPTJuly_mm[yearIndex] = JulyPPT; + climateOutput->minTemp7thMon_C[yearIndex] = currentJulyMin; + climateOutput->PPT7thMon_mm[yearIndex] = JulyPPT; climateOutput->ddAbove65F_degday[yearIndex] = totalAbove65; // The reason behind checking if consecNonFrost is greater than zero, @@ -1118,11 +1117,11 @@ void allocDeallocClimateStructs(int action, int numYears, SW_CLIMATE_YEARLY *cli if(action == deallocate) { free(climateOutput->PPT_cm); - free(climateOutput->PPTJuly_mm); + free(climateOutput->PPT7thMon_mm); free(climateOutput->meanTemp_C); free(climateOutput->meanTempDriestQtr_C); - free(climateOutput->minTempFeb_C); - free(climateOutput->minTempJuly_C); + free(climateOutput->minTemp2ndMon_C); + free(climateOutput->minTemp7thMon_C); free(climateOutput->frostFree_days); free(climateOutput->ddAbove65F_degday); free(climateAverages->meanTempMon_C); @@ -1157,11 +1156,11 @@ void allocDeallocClimateStructs(int action, int numYears, SW_CLIMATE_YEARLY *cli } climateOutput->PPT_cm = (double *)malloc(sizeof(double) * numYears); - climateOutput->PPTJuly_mm = (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->minTempFeb_C = (double *)malloc(sizeof(double) * numYears); - climateOutput->minTempJuly_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); diff --git a/SW_Weather.h b/SW_Weather.h index 9861d23d4..9cb19b15d 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -68,15 +68,15 @@ typedef struct { typedef struct { RealD **PPTMon_cm, /**< 2D array containing monthly amount precipitation (cm)*/ *PPT_cm, /**< Array containing annual precipitation amount [cm]*/ - *PPTJuly_mm, /**< Array containing July precipitation amount (mm) */ + *PPT7thMon_mm, /**< Array containing July precipitation amount (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 temperature [C] of the driest quarter of the year*/ - *minTempFeb_C, /**< Array containing the mean daily minimum temperature in February [C] */ - *minTempJuly_C, /**< Array containing minimum July temperatures [C] */ + *minTemp2ndMon_C, /**< Array containing the mean daily minimum temperature in February [C] */ + *minTemp7thMon_C, /**< Array containing minimum July temperatures [C] */ *frostFree_days, /**< Array containing the maximum consecutive days [-] without frost*/ *ddAbove65F_degday; /**< Array containing the amount of degree days [C x day] above 65 F */ @@ -100,12 +100,12 @@ typedef struct { temperature of dry quarter (1), mean minimum temperature of February (2)*/ meanTemp_C, /**< Value containing the average of yearly temperatures*/ PPT_cm, /**< Value containing the average of yearly precipitation*/ - PPTJuly_mm, /**< Value containing average of July precipitation (mm)*/ + PPT7thMon_mm, /**< Value containing average of July precipitation (mm)*/ meanTempDriestQtr_C, /**< Value containing average of mean temperatures in the driest quarters of years*/ - minTempFeb_C, /**< Value containing average of minimum temperatures in February*/ + minTemp2ndMon_C, /**< Value containing average of minimum temperatures in February*/ ddAbove65F_degday, /**< Value containing average of total degrees above 65F (18.33C) throughout the year*/ frostFree_days, /**< Value containing average of most consectutive days in a year without frost*/ - minTempJuly_C; /**< Value containing the average of lowest temperature in July*/ + minTemp7thMon_C; /**< Value containing the average of lowest temperature in July*/ } SW_CLIMATE_CLIM; typedef struct { diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 0ccc97700..09f6184c3 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -194,16 +194,16 @@ namespace { // Climate variables used for C4 grass cover // (stdev of one value is undefined) - EXPECT_NEAR(climateOutput.minTempJuly_C[0], 2.809999, tol6); + EXPECT_NEAR(climateOutput.minTemp7thMon_C[0], 2.809999, tol6); 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.PPTJuly_mm[0], 18.299999, tol6); + EXPECT_NEAR(climateOutput.PPT7thMon_mm[0], 18.299999, tol6); EXPECT_NEAR(climateOutput.meanTempDriestQtr_C[0], 0.936387, tol6); - EXPECT_NEAR(climateOutput.minTempFeb_C[0], -12.822068, tol6); + EXPECT_NEAR(climateOutput.minTemp2ndMon_C[0], -12.822068, tol6); // --- Long-term variables (aggregated across years) ------ @@ -231,7 +231,7 @@ namespace { EXPECT_NEAR(climateAverages.meanTemp_C, 4.154009, tol6); // Climate variables used for C4 grass cover - EXPECT_NEAR(climateAverages.minTempJuly_C, 3.078387, tol6); + EXPECT_NEAR(climateAverages.minTemp7thMon_C, 3.078387, tol6); EXPECT_NEAR(climateAverages.frostFree_days, 90.612903, tol6); EXPECT_NEAR(climateAverages.ddAbove65F_degday, 21.168032, tol6); @@ -240,9 +240,9 @@ namespace { EXPECT_NEAR(climateAverages.sdC4[2], 19.953560, tol6); // Climate variables used for cheatgrass cover - EXPECT_NEAR(climateAverages.PPTJuly_mm, 35.729032, tol6); + EXPECT_NEAR(climateAverages.PPT7thMon_mm, 35.729032, tol6); EXPECT_NEAR(climateAverages.meanTempDriestQtr_C, 11.524859, tol6); - EXPECT_NEAR(climateAverages.minTempFeb_C, -13.904599, 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); @@ -315,8 +315,8 @@ namespace { // Climate variables used for C4 grass cover EXPECT_DOUBLE_EQ( - climateAverages.minTempJuly_C, - climateOutput.minTempJuly_C[0] + climateAverages.minTemp7thMon_C, + climateOutput.minTemp7thMon_C[0] ); EXPECT_DOUBLE_EQ( climateAverages.frostFree_days, @@ -329,16 +329,16 @@ namespace { // Climate variables used for cheatgrass cover EXPECT_DOUBLE_EQ( - climateAverages.PPTJuly_mm, - climateOutput.PPTJuly_mm[0] + climateAverages.PPT7thMon_mm, + climateOutput.PPT7thMon_mm[0] ); EXPECT_DOUBLE_EQ( climateAverages.meanTempDriestQtr_C, climateOutput.meanTempDriestQtr_C[0] ); EXPECT_DOUBLE_EQ( - climateAverages.minTempFeb_C, - climateOutput.minTempFeb_C[0] + climateAverages.minTemp2ndMon_C, + climateOutput.minTemp2ndMon_C[0] ); @@ -351,7 +351,7 @@ namespace { // Climate variables used for C4 grass cover // (stdev of one value is undefined) - EXPECT_NEAR(climateAverages.minTempJuly_C, 2.809999, tol6); + EXPECT_NEAR(climateAverages.minTemp7thMon_C, 2.809999, tol6); EXPECT_NEAR(climateAverages.frostFree_days, 92, tol6); EXPECT_NEAR(climateAverages.ddAbove65F_degday, 13.546000, tol6); EXPECT_TRUE(isnan(climateAverages.sdC4[0])); @@ -360,9 +360,9 @@ namespace { // Climate variables used for cheatgrass cover // (stdev of one value is undefined) - EXPECT_NEAR(climateAverages.PPTJuly_mm, 18.299999, tol6); + EXPECT_NEAR(climateAverages.PPT7thMon_mm, 18.299999, tol6); EXPECT_NEAR(climateAverages.meanTempDriestQtr_C, 0.936387, tol6); - EXPECT_NEAR(climateAverages.minTempFeb_C, -12.822068, 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])); @@ -417,16 +417,16 @@ namespace { // Climate variables used for C4 grass cover // (stdev of one value is undefined) - EXPECT_NEAR(climateOutput.minTempJuly_C[1], -16.98, tol6); - EXPECT_NEAR(climateOutput.frostFree_days[1], 76, tol6); // 79 - EXPECT_NEAR(climateOutput.ddAbove65F_degday[1], 16.990001, tol6); // 16.965000 + EXPECT_NEAR(climateOutput.minTemp7thMon_C[1], -16.98, tol6); + EXPECT_NEAR(climateOutput.frostFree_days[1], 78, tol6); // 79 + EXPECT_NEAR(climateOutput.ddAbove65F_degday[1], 16.458001, tol6); // 16.965000 // Climate variables used for cheatgrass cover // (stdev of one value is undefined) - EXPECT_NEAR(climateOutput.PPTJuly_mm[1], 24.699999, tol6); // 22.19999 - EXPECT_NEAR(climateOutput.meanTempDriestQtr_C[1], 0.936387, tol6); // 15.8733906 - EXPECT_NEAR(climateOutput.minTempFeb_C[1], 5.1445161, tol6); // 5.3467742 + EXPECT_NEAR(climateOutput.PPT7thMon_mm[1], 22.199999, tol6); // 22.19999 + EXPECT_NEAR(climateOutput.meanTempDriestQtr_C[0], 0.936387, tol6); // 15.8733906 + EXPECT_NEAR(climateOutput.minTemp2ndMon_C[1], 5.1445161, tol6); // 5.3467742 // --- Long-term variables (aggregated across years) ------ @@ -454,22 +454,22 @@ namespace { EXPECT_NEAR(climateAverages.meanTemp_C, 4.154009, tol6); // Climate variables used for C4 grass cover - EXPECT_NEAR(climateAverages.minTempJuly_C, -27.243870, tol6); // -27.146999 - EXPECT_NEAR(climateAverages.frostFree_days, 68.290323, tol6); // 72.6333333 - EXPECT_NEAR(climateAverages.ddAbove65F_degday, 20.684935, tol6); // 21.2880665 + EXPECT_NEAR(climateAverages.minTemp7thMon_C, -27.199333, tol6); // -27.146999 + EXPECT_NEAR(climateAverages.frostFree_days, 72.599999, tol6); // 72.6333333 + EXPECT_NEAR(climateAverages.ddAbove65F_degday, 21.357533, tol6); // 21.2880665 - EXPECT_NEAR(climateAverages.sdC4[0], 5.241726, tol6); - EXPECT_NEAR(climateAverages.sdC4[1], 13.446669, tol6); // 9.4229482 - EXPECT_NEAR(climateAverages.sdC4[2], 19.755513, tol6); // 19.589081 + EXPECT_NEAR(climateAverages.sdC4[0], 5.325365, tol6); + EXPECT_NEAR(climateAverages.sdC4[1], 9.586628, tol6); // 9.4229482 + EXPECT_NEAR(climateAverages.sdC4[2], 19.550419, tol6); // 19.589081 // Climate variables used for cheatgrass cover - EXPECT_NEAR(climateAverages.PPTJuly_mm, 22.199999, tol6); // 65.75333 - EXPECT_NEAR(climateAverages.meanTempDriestQtr_C, 11.524859, tol6); // 11.4012 - EXPECT_NEAR(climateAverages.minTempFeb_C, -13.904599, tol6); // 6.5445577 + EXPECT_NEAR(climateAverages.PPT7thMon_mm, 65.916666, tol6); + EXPECT_NEAR(climateAverages.meanTempDriestQtr_C, 11.524859, tol6); + EXPECT_NEAR(climateAverages.minTemp2ndMon_C, 6.545577, tol6); - EXPECT_NEAR(climateAverages.sdCheatgrass[0], 21.598367, tol6); // 35.46237 - EXPECT_NEAR(climateAverages.sdCheatgrass[1], 7.171922, tol6); // 7.260851 - EXPECT_NEAR(climateAverages.sdCheatgrass[2], 2.618434, tol6); // 1.6247347 + EXPECT_NEAR(climateAverages.sdCheatgrass[0], 35.285408, tol6); + EXPECT_NEAR(climateAverages.sdCheatgrass[1], 7.171922, tol6); + EXPECT_NEAR(climateAverages.sdCheatgrass[2], 1.639639, tol6); // ------ Reset and deallocate @@ -531,7 +531,7 @@ namespace { // Climate variables used for C4 grass cover // (stdev of one value is undefined) - EXPECT_DOUBLE_EQ(climateOutput.minTempJuly_C[0], 1.); + 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.); @@ -539,9 +539,9 @@ namespace { // Climate variables used for cheatgrass cover // (stdev of one value is undefined) - EXPECT_DOUBLE_EQ(climateOutput.PPTJuly_mm[0], 310.); + EXPECT_DOUBLE_EQ(climateOutput.PPT7thMon_mm[0], 310.); EXPECT_DOUBLE_EQ(climateOutput.meanTempDriestQtr_C[0], 1.); - EXPECT_DOUBLE_EQ(climateOutput.minTempFeb_C[0], 1.); + EXPECT_DOUBLE_EQ(climateOutput.minTemp2ndMon_C[0], 1.); // --- Long-term variables (aggregated across years) ------ @@ -557,7 +557,7 @@ namespace { EXPECT_DOUBLE_EQ(climateAverages.meanTemp_C, 1.); // Climate variables used for C4 grass cover - EXPECT_DOUBLE_EQ(climateAverages.minTempJuly_C, 1.); + 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.); @@ -565,9 +565,9 @@ namespace { EXPECT_DOUBLE_EQ(climateAverages.sdC4[2], 0.); // Climate variables used for cheatgrass cover - EXPECT_DOUBLE_EQ(climateAverages.PPTJuly_mm, 310.); + EXPECT_DOUBLE_EQ(climateAverages.PPT7thMon_mm, 310.); EXPECT_DOUBLE_EQ(climateAverages.meanTempDriestQtr_C, 1.); - EXPECT_DOUBLE_EQ(climateAverages.minTempFeb_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.); From cf58f2335e2472cd773099217065f4a7316519f5 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Mon, 12 Sep 2022 00:54:23 -0700 Subject: [PATCH 154/326] Updated `calcSiteClimate()` and `findDriestQtr()` for southern adjustment - `calcSiteClimate()`: * Updated documentation giving examples and better explanation of "calendar" versus "adjusted" years * Corrected number of days in year (leap or non leap year) * If SW_MISSING is encountered when dealing with leap year number of days of a non leap year, we do not count it in temperature above 65 - `findDriestQtr()` * Started work on correctly adjusting for southern hemisphere with "adjustedYear"/"adjustedMonth" --- SW_Weather.c | 77 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 58 insertions(+), 19 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 72b6fa0fe..ff3f26340 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -127,10 +127,24 @@ void averageClimateAcrossYears(SW_CLIMATE_YEARLY *climateOutput, int numYears, /** @brief Calculate monthly and annual time series of climate variables from daily weather - When site is in southern hemisphere, the first and last six months of data are ignored. The beginning of the year - starts on the first day of July and ends on June 30th of the next calendar year. Due to this, what is referred to as - the "adjusted" calendar year and is shifted six months in comparison to calendar years. The southern year - starts with 1981 instead of 1980 to handle leap years better. + 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 by `allHist` @@ -144,7 +158,7 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, int month, yearIndex, year, day, numDaysYear, currMonDay; int numDaysMonth, adjustedDoy, adjustedYear = 0, secondMonth, seventhMonth, - adjustedStartYear; + adjustedStartYear, calendarYearDays; double currentTempMin, currentTempMean, totalAbove65, currentJulyMin, JulyPPT, consecNonFrost, currentNonFrost; @@ -193,23 +207,32 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, consecNonFrost = 0; JulyPPT = 0; + if(!isNorth) { + // 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(isNorth) { adjustedDoy = day; adjustedYear = yearIndex; } else { // Adjust year and day to meet southern hemisphere requirements - adjustedDoy = (numDaysYear == 366) ? day + 182 : day + 181; - adjustedDoy = adjustedDoy % 365; + adjustedDoy = (calendarYearDays == 366) ? day + 182 : day + 181; + adjustedDoy = adjustedDoy % calendarYearDays; if(adjustedDoy == 0) { adjustedYear++; + Time_new_year(year); } } if(month == Jul && adjustedYear >= numYears - 1 && !isNorth) { // Set all current accumulated values to SW_MISSING to prevent - // a zero going into the last year of the arrays + // 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" JulyPPT = SW_MISSING; totalAbove65 = SW_MISSING; currentNonFrost = SW_MISSING; @@ -248,14 +271,24 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, // 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 - currentTempMean -= 18.333; - totalAbove65 += (currentTempMean > 0.0) ? currentTempMean : 0.; + // 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 @@ -326,20 +359,26 @@ void calcSiteClimateLatInvariants(SW_WEATHER_HIST **allHist, int numYears, int s void findDriestQtr(double *meanTempDriestQtr_C, int numYears, double **meanTempMon_C, double **PPTMon_cm, Bool isNorth) { - int yearIndex, month, prevMonth, nextMonth, adjustedMonth, numQuarterMonths = 3; - + int yearIndex, month, prevMonth, nextMonth, adjustedMonth, numQuarterMonths = 3, + adjustedYear = 0, endNumYears = (isNorth) ? numYears : numYears - 1; + double driestThreeMonPPT, driestMeanTemp, currentQtrPPT, currentQtrTemp; - for(yearIndex = 0; yearIndex < numYears; yearIndex++) { + for(yearIndex = 0; yearIndex < endNumYears; yearIndex++) { driestThreeMonPPT = SW_MISSING; driestMeanTemp = SW_MISSING; for(month = 0; month < MAX_MONTHS; month++) { if(isNorth) { adjustedMonth = month; + adjustedYear = yearIndex; } else { adjustedMonth = month + Jul; adjustedMonth %= MAX_MONTHS; + + if(adjustedMonth == Jan) { + adjustedYear++; + } } // Get next and previous months @@ -347,14 +386,14 @@ void findDriestQtr(double *meanTempDriestQtr_C, int numYears, double **meanTempM nextMonth = (adjustedMonth == Dec) ? Jan : adjustedMonth + 1; // Get precipitation of current quarter - currentQtrPPT = (PPTMon_cm[prevMonth][yearIndex]) + - (PPTMon_cm[adjustedMonth][yearIndex]) + - (PPTMon_cm[nextMonth][yearIndex]); + currentQtrPPT = (PPTMon_cm[prevMonth][adjustedYear]) + + (PPTMon_cm[adjustedMonth][adjustedYear]) + + (PPTMon_cm[nextMonth][adjustedYear]); // Get temperature of current quarter - currentQtrTemp = (meanTempMon_C[prevMonth][yearIndex]) + - (meanTempMon_C[adjustedMonth][yearIndex]) + - (meanTempMon_C[nextMonth][yearIndex]); + currentQtrTemp = (meanTempMon_C[prevMonth][adjustedYear]) + + (meanTempMon_C[adjustedMonth][adjustedYear]) + + (meanTempMon_C[nextMonth][adjustedYear]); // Check if the current quarter precipitation is a new low if(currentQtrPPT < driestThreeMonPPT) { From 353a54dc26b50fbabd99632b592af6b264788753 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Mon, 12 Sep 2022 01:20:26 -0700 Subject: [PATCH 155/326] Fixed error caused by naming and incorrect function input - Updated necessary variable names that have a month in their name (7th month in this case) - Updated call to `calcSiteClimate()` in `estimateVegetationFromClimate()` to input a bool, not double --- SW_VegProd.c | 4 ++-- test/test_SW_VegProd.cc | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index 8a55118da..5c609dc0d 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -904,13 +904,13 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe // Allocate climate structs' memory allocDeallocClimateStructs(allocate, numYears, &climateOutput, &climateAverages); - calcSiteClimate(SW_Weather.allHist, numYears, startYear, &climateOutput, latitude); + calcSiteClimate(SW_Weather.allHist, numYears, startYear, &climateOutput, inNorth); averageClimateAcrossYears(&climateOutput, numYears, &climateAverages); if(veg_method == 1) { - C4Variables[0] = climateAverages.minTempJuly_C; + C4Variables[0] = climateAverages.minTemp7thMon_C; C4Variables[1] = climateAverages.ddAbove65F_degday; C4Variables[2] = climateAverages.frostFree_days; diff --git a/test/test_SW_VegProd.cc b/test/test_SW_VegProd.cc index d9da48296..104afcb5f 100644 --- a/test/test_SW_VegProd.cc +++ b/test/test_SW_VegProd.cc @@ -228,7 +228,7 @@ namespace { averageClimateAcrossYears(&climateOutput, 31, &climateAverages); // Set C4 results, standard deviations are not needed for estimating vegetation - C4Variables[0] = climateAverages.minTempJuly_C; + C4Variables[0] = climateAverages.minTemp7thMon_C; C4Variables[1] = climateAverages.ddAbove65F_degday; C4Variables[2] = climateAverages.frostFree_days; @@ -545,7 +545,7 @@ namespace { averageClimateAcrossYears(&climateOutput, 31, &climateAverages); // Set C4 results, standard deviations are not needed for estimating vegetation - C4Variables[0] = climateAverages.minTempJuly_C; + C4Variables[0] = climateAverages.minTemp7thMon_C; C4Variables[1] = climateAverages.ddAbove65F_degday; C4Variables[2] = climateAverages.frostFree_days; From 491ed9a41e59c3c55ce1f5f19b90d33c45196dcc Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 13 Sep 2022 00:50:45 -0700 Subject: [PATCH 156/326] Finished correctly calculating driest quarter temp - Created new function `driestQtrSouthAdjMonYears()` which handles: * Getting the necessary adjusted years and months to wrap around calendar years - Added three new variables in `findDriestQtr()` to handle individual needs for adjusted years - Updated unit tests to values now produced --- SW_Weather.c | 100 ++++++++++++++++++++++++++++++++-------- SW_Weather.h | 3 ++ test/test_SW_Weather.cc | 4 +- 3 files changed, 86 insertions(+), 21 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index ff3f26340..057adec8f 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -359,8 +359,14 @@ void calcSiteClimateLatInvariants(SW_WEATHER_HIST **allHist, int numYears, int s void findDriestQtr(double *meanTempDriestQtr_C, int numYears, double **meanTempMon_C, double **PPTMon_cm, Bool isNorth) { - int yearIndex, month, prevMonth, nextMonth, adjustedMonth, numQuarterMonths = 3, - adjustedYear = 0, endNumYears = (isNorth) ? numYears : numYears - 1; + int yearIndex, month, prevMonth, nextMonth, adjustedMonth = 0, + numQuarterMonths = 3, endNumYears = (isNorth) ? 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; @@ -368,32 +374,29 @@ void findDriestQtr(double *meanTempDriestQtr_C, int numYears, double **meanTempM driestThreeMonPPT = SW_MISSING; driestMeanTemp = SW_MISSING; + adjustedYearZero = adjustedYearOne = adjustedYearTwo = yearIndex; + for(month = 0; month < MAX_MONTHS; month++) { + if(isNorth) { adjustedMonth = month; - adjustedYear = yearIndex; - } else { - adjustedMonth = month + Jul; - adjustedMonth %= MAX_MONTHS; - if(adjustedMonth == Jan) { - adjustedYear++; - } + prevMonth = (adjustedMonth == Jan) ? Dec : adjustedMonth - 1; + nextMonth = (adjustedMonth == Dec) ? Jan : adjustedMonth + 1; + } else { + driestQtrSouthAdjMonYears(month, &adjustedYearZero, &adjustedYearOne, + &adjustedYearTwo, &adjustedMonth, &prevMonth, &nextMonth); } - // Get next and previous months - prevMonth = (adjustedMonth == Jan) ? Dec : adjustedMonth - 1; - nextMonth = (adjustedMonth == Dec) ? Jan : adjustedMonth + 1; - // Get precipitation of current quarter - currentQtrPPT = (PPTMon_cm[prevMonth][adjustedYear]) + - (PPTMon_cm[adjustedMonth][adjustedYear]) + - (PPTMon_cm[nextMonth][adjustedYear]); + currentQtrPPT = (PPTMon_cm[prevMonth][adjustedYearZero]) + + (PPTMon_cm[adjustedMonth][adjustedYearOne]) + + (PPTMon_cm[nextMonth][adjustedYearTwo]); // Get temperature of current quarter - currentQtrTemp = (meanTempMon_C[prevMonth][adjustedYear]) + - (meanTempMon_C[adjustedMonth][adjustedYear]) + - (meanTempMon_C[nextMonth][adjustedYear]); + 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) { @@ -413,6 +416,65 @@ void findDriestQtr(double *meanTempDriestQtr_C, int numYears, double **meanTempM } +/** + @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) with respect 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; + } +} + /* =================================================== */ /* Global Function Definitions */ diff --git a/SW_Weather.h b/SW_Weather.h index 9cb19b15d..9bedaccff 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -168,6 +168,9 @@ void calcSiteClimateLatInvariants(SW_WEATHER_HIST **allHist, int numYears, int s SW_CLIMATE_YEARLY *climateOutput); void findDriestQtr(double *meanTempDriestQtr_C, int numYears, double **meanTempMon_C, double **meanPPTMon_cm, Bool isNorth); +void driestQtrSouthAdjMonYears(int month, int *adjustedYearZero, int *adjustedYearOne, + int *adjustedYearTwo, int *adjustedMonth, int *prevMonth, + int *nextMonth); void allocDeallocClimateStructs(int action, int numYears, SW_CLIMATE_YEARLY *climateOutput, SW_CLIMATE_CLIM *climateAverages); void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, unsigned int n_years); diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 09f6184c3..c0700aab2 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -464,11 +464,11 @@ namespace { // Climate variables used for cheatgrass cover EXPECT_NEAR(climateAverages.PPT7thMon_mm, 65.916666, tol6); - EXPECT_NEAR(climateAverages.meanTempDriestQtr_C, 11.524859, 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.171922, tol6); + EXPECT_NEAR(climateAverages.sdCheatgrass[1], 7.260851, tol6); EXPECT_NEAR(climateAverages.sdCheatgrass[2], 1.639639, tol6); From 93dd0723524519dbd70c40d6406b98ea270b1a1f Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Mon, 26 Sep 2022 10:02:46 -0400 Subject: [PATCH 157/326] Fix `LogError()` for rSOILWAT2 - commit 969d428443f5a42ede3c6f167247ef649703c703 (2022-Sep-13, "Modified `LogError()` to use `error()` and `warning()`") replaced the rSOILWAT2 call to `REvprintf()` with `error()` or `warning()` respectively - however, R API `error()` and `warning()` don't take format string and va_list but instead format string and arguments: this resulted that all values were printed as 0s (see example below) -> now, call `vsnprintf()` to create correctly formatted string with values which is then passed to `error()` or `warning()` For example: * previous message: "WARNING:  : Layer 0 calculated `swcBulk_halfwiltpt` (0.0000 cm / 0.00 MPa) <= `swcBulk_min` (0.0000 cm / 0.00 MPa). `swcBulk_halfwiltpt` was set to `swcBulk_min`." * now: "WARNING: (null) : Layer 7 calculated `swcBulk_halfwiltpt` (0.2682 cm / -10.00 MPa) <= `swcBulk_min` (0.2757 cm / -8.89 MPa). `swcBulk_halfwiltpt` was set to `swcBulk_min`." --- filefuncs.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/filefuncs.c b/filefuncs.c index d5a9f83e4..e754b51b3 100644 --- a/filefuncs.c +++ b/filefuncs.c @@ -166,10 +166,14 @@ void LogError(FILE *fp, const int mode, const char *fmt, ...) { strcat(outfmt, "\n"); #ifdef RSOILWAT + char buf[1024]; + + vsnprintf(buf, sizeof buf, outfmt, args); + if(LOGEXIT & mode) { - error(outfmt, args); + error(buf); } else if(fp != NULL) { - warning(outfmt, args); + warning(buf); } #else From 38e5d6d92f8f9d0658ee4757497fe40a84f648be Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Thu, 22 Sep 2022 19:58:57 -0600 Subject: [PATCH 158/326] Fix unit tests `ReadAllWeatherTest.SomeMissingValuesDays` - previously, unit tests checked that values of maximum daily air temperature on certain days was not missing; however, some of those days contained valid input values (e.g., base1-doy 3 and 4 (see "Input/data_weather_missing/weath.1980") -> now, updated tests check only days for which values are actually missing from the inputs --- test/test_SW_Weather.cc | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index d492219cd..8c5946bd3 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -64,16 +64,16 @@ namespace { SW_MKV_setup(); SW_WTH_read(); - - // With the use of 1980's missing values, test a few days of the year - // to make sure they are filled using the weather generator - EXPECT_TRUE(!missing(SW_Weather.allHist[0]->temp_max[0])); - EXPECT_TRUE(!missing(SW_Weather.allHist[0]->temp_max[1])); - EXPECT_TRUE(!missing(SW_Weather.allHist[0]->temp_max[2])); - EXPECT_TRUE(!missing(SW_Weather.allHist[0]->temp_max[3])); - EXPECT_TRUE(!missing(SW_Weather.allHist[0]->temp_max[4])); - EXPECT_TRUE(!missing(SW_Weather.allHist[0]->temp_max[365])); + + + // 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(); } From 80d9478e49f9f955760d3367de7b191abba65394 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 27 Sep 2022 00:54:02 -0700 Subject: [PATCH 159/326] Removed "Yesterday" values in SW_WEATHER_2DAYS - Variables within SW_WEATHER_2DAYS are now single value variables, holding the current day's value - Renamed SW_WEATHER_2DAYS to SW_WEATHER_NOW - Changes are active on feature_climate_predictors and feature_veg_estimation, not feature_read_weather * Changes were committed on feature_read_weather but never pushed * feature_read_weather has been merged into the two other branches --- SW_Control.c | 1 - SW_Flow.c | 12 ++++++------ SW_Output.c | 10 +++++----- SW_SoilWater.c | 2 +- SW_VegEstab.c | 4 ++-- SW_Weather.c | 49 ++++++++++--------------------------------------- SW_Weather.h | 11 +++++------ 7 files changed, 29 insertions(+), 60 deletions(-) diff --git a/SW_Control.c b/SW_Control.c index 1ee1c4878..960012265 100644 --- a/SW_Control.c +++ b/SW_Control.c @@ -76,7 +76,6 @@ static void _begin_day(void) { static void _end_day(void) { _collect_values(); - SW_WTH_end_day(); SW_SWC_end_day(); } diff --git a/SW_Flow.c b/SW_Flow.c index 9bcf5f6cb..293009bd8 100644 --- a/SW_Flow.c +++ b/SW_Flow.c @@ -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, @@ -408,7 +408,7 @@ void SW_Water_Flow(void) { x, SW_Sky.cloudcov_daily[doy], SW_Sky.r_humidity_daily[doy], - w->now.temp_avg[Today], + w->now.temp_avg, &sw->H_oh, &sw->H_ot, &sw->H_gh @@ -416,7 +416,7 @@ 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], @@ -444,7 +444,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 +837,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_Output.c b/SW_Output.c index f01717815..c6a3b481a 100644 --- a/SW_Output.c +++ b/SW_Output.c @@ -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; diff --git a/SW_SoilWater.c b/SW_SoilWater.c index 2b952ac36..78c7388f0 100644 --- a/SW_SoilWater.c +++ b/SW_SoilWater.c @@ -194,7 +194,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; diff --git a/SW_VegEstab.c b/SW_VegEstab.c index ccbf07805..3347a774c 100644 --- a/SW_VegEstab.c +++ b/SW_VegEstab.c @@ -231,11 +231,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) { diff --git a/SW_Weather.c b/SW_Weather.c index 00bd016ed..3f566915a 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -79,25 +79,6 @@ static char *MyFileName; /* 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]; -} - - /* =================================================== */ /* Global Function Definitions */ @@ -493,24 +474,14 @@ void SW_WTH_init_run(void) { * (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.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 Updates 'yesterday'. -*/ -void SW_WTH_end_day(void) { - /* =================================================== */ - _update_yesterday(); -} - /** @brief Guarantees that today's weather will not be invalid via -_todays_weth(). */ @@ -529,7 +500,7 @@ void SW_WTH_new_day(void) { */ SW_WEATHER *w = &SW_Weather; - SW_WEATHER_2DAYS *wn = &SW_Weather.now; + SW_WEATHER_NOW *wn = &SW_Weather.now; TimeInt day = SW_Model.doy - 1, year = SW_Model.year - SW_Model.startyr; #ifdef STEPWAT @@ -554,21 +525,21 @@ void SW_WTH_new_day(void) { ); } - wn->temp_max[Today] = w->allHist[year]->temp_max[day]; - wn->temp_min[Today] = w->allHist[year]->temp_min[day]; - wn->ppt[Today] = w->allHist[year]->ppt[day]; + wn->temp_max = w->allHist[year]->temp_max[day]; + wn->temp_min = w->allHist[year]->temp_min[day]; + wn->ppt = w->allHist[year]->ppt[day]; - wn->temp_avg[Today] = w->allHist[year]->temp_avg[day]; + wn->temp_avg = w->allHist[year]->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[Today], wn->temp_max[Today], wn->ppt[Today], - &wn->rain[Today], &w->snow, &w->snowmelt); + SW_SWC_adjust_snow(wn->temp_min, wn->temp_max, wn->ppt, + &wn->rain, &w->snow, &w->snowmelt); } else { - wn->rain[Today] = wn->ppt[Today]; + wn->rain = wn->ppt; } } diff --git a/SW_Weather.h b/SW_Weather.h index 81be3ed69..10b4aec40 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -9,9 +9,9 @@ (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 + 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_2DAYS and to SW_WEATHER_OUTPUTS + 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 @@ -41,9 +41,8 @@ extern "C" { 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; + RealD temp_avg, temp_max, temp_min, ppt, rain; +} SW_WEATHER_NOW; typedef struct { /* comes from historical weather files */ @@ -94,7 +93,7 @@ typedef struct { *p_oagg[SW_OUTNPERIODS]; // output aggregator: mean or sum for each time periods SW_WEATHER_HIST hist; SW_WEATHER_HIST **allHist; - SW_WEATHER_2DAYS now; + SW_WEATHER_NOW now; } SW_WEATHER; From 488eb437778fee03d24fe6eddbc74dc99c9695c7 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 2 Oct 2022 21:30:51 -0700 Subject: [PATCH 160/326] Renamed "inNorth" to be more descriptive --- SW_Weather.c | 22 +++++++++++----------- SW_Weather.h | 4 ++-- test/test_SW_Weather.cc | 24 ++++++++++++------------ 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 4d8dca3ae..bb3c18f6c 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -154,7 +154,7 @@ void averageClimateAcrossYears(SW_CLIMATE_YEARLY *climateOutput, int numYears, */ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, - SW_CLIMATE_YEARLY *climateOutput, Bool isNorth) { + SW_CLIMATE_YEARLY *climateOutput, Bool inNorthHem) { int month, yearIndex, year, day, numDaysYear, currMonDay; int numDaysMonth, adjustedDoy, adjustedYear = 0, secondMonth, seventhMonth, @@ -178,7 +178,7 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, calcSiteClimateLatInvariants(allHist, numYears, startYear, climateOutput); // Set starting conditions that are dependent on north/south before main loop is entered - if(isNorth) { + if(inNorthHem) { secondMonth = Feb; seventhMonth = Jul; numDaysMonth = Time_days_in_month(Jan); @@ -199,7 +199,7 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, year = yearIndex + startYear; Time_new_year(year); numDaysYear = Time_get_lastdoy_y(year); - month = (isNorth) ? Jan : Jul; + month = (inNorthHem) ? Jan : Jul; currMonDay = 0; currentJulyMin = SW_MISSING; totalAbove65 = 0; @@ -207,14 +207,14 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, consecNonFrost = 0; JulyPPT = 0; - if(!isNorth) { + 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(isNorth) { + if(inNorthHem) { adjustedDoy = day; adjustedYear = yearIndex; } else { @@ -228,7 +228,7 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, } } - if(month == Jul && adjustedYear >= numYears - 1 && !isNorth) { + 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 @@ -302,7 +302,7 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, } findDriestQtr(climateOutput->meanTempDriestQtr_C, numYears, - climateOutput->meanTempMon_C, climateOutput->PPTMon_cm, isNorth); + climateOutput->meanTempMon_C, climateOutput->PPTMon_cm, inNorthHem); } /** @@ -357,10 +357,10 @@ void calcSiteClimateLatInvariants(SW_WEATHER_HIST **allHist, int numYears, int s @param[out] meanTempDriestQtr_C Array of size numYears holding the average temperature of the driest quarter of the year for every year */ void findDriestQtr(double *meanTempDriestQtr_C, int numYears, double **meanTempMon_C, - double **PPTMon_cm, Bool isNorth) { + double **PPTMon_cm, Bool inNorthHem) { int yearIndex, month, prevMonth, nextMonth, adjustedMonth = 0, - numQuarterMonths = 3, endNumYears = (isNorth) ? numYears : numYears - 1; + numQuarterMonths = 3, endNumYears = (inNorthHem) ? numYears : numYears - 1; // NOTE: These variables are the same throughout the program if site is in // northern hempisphere @@ -378,7 +378,7 @@ void findDriestQtr(double *meanTempDriestQtr_C, int numYears, double **meanTempM for(month = 0; month < MAX_MONTHS; month++) { - if(isNorth) { + if(inNorthHem) { adjustedMonth = month; prevMonth = (adjustedMonth == Jan) ? Dec : adjustedMonth - 1; @@ -411,7 +411,7 @@ void findDriestQtr(double *meanTempDriestQtr_C, int numYears, double **meanTempM } - if(!isNorth) meanTempDriestQtr_C[numYears - 1] = SW_MISSING; + if(!inNorthHem) meanTempDriestQtr_C[numYears - 1] = SW_MISSING; } diff --git a/SW_Weather.h b/SW_Weather.h index e2fffed92..37344acce 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -165,11 +165,11 @@ Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather); void averageClimateAcrossYears(SW_CLIMATE_YEARLY *climateOutput, int numYears, SW_CLIMATE_CLIM *climateAverages); void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, - SW_CLIMATE_YEARLY *climateOutput, Bool isNorth); + SW_CLIMATE_YEARLY *climateOutput, Bool inNorthHem); void calcSiteClimateLatInvariants(SW_WEATHER_HIST **allHist, int numYears, int startYear, SW_CLIMATE_YEARLY *climateOutput); void findDriestQtr(double *meanTempDriestQtr_C, int numYears, double **meanTempMon_C, - double **meanPPTMon_cm, Bool isNorth); + double **meanPPTMon_cm, Bool inNorthHem); void driestQtrSouthAdjMonYears(int month, int *adjustedYearZero, int *adjustedYearOne, int *adjustedYearTwo, int *adjustedMonth, int *prevMonth, int *nextMonth); diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 5e8c3ee43..3c6bd30db 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -159,7 +159,7 @@ namespace { int deallocate = 0; int allocate = 1; - Bool isNorth = swTRUE; + Bool inNorthHem = swTRUE; // Allocate memory // 31 = number of years used in test @@ -183,7 +183,7 @@ namespace { // ) // ``` - calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput, isNorth); + calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput, inNorthHem); EXPECT_NEAR(climateOutput.meanTempMon_C[Jan][0], -8.432581, tol6); EXPECT_NEAR(climateOutput.maxTempMon_C[Jan][0], -2.562581, tol6); @@ -264,7 +264,7 @@ namespace { int deallocate = 0; int allocate = 1; - Bool isNorth = swTRUE; + Bool inNorthHem = swTRUE; // Allocate memory // 1 = number of years used in test @@ -283,7 +283,7 @@ namespace { // ) // ``` - calcSiteClimate(SW_Weather.allHist, 1, 1980, &climateOutput, isNorth); + calcSiteClimate(SW_Weather.allHist, 1, 1980, &climateOutput, inNorthHem); averageClimateAcrossYears(&climateOutput, 1, &climateAverages); // Expect that aggregated values across one year are identical @@ -382,7 +382,7 @@ namespace { int deallocate = 0; int allocate = 1; - Bool isNorth = swFALSE; + Bool inNorthHem = swFALSE; // Allocate memory // 31 = number of years used in test @@ -406,7 +406,7 @@ namespace { // ) // ``` - calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput, isNorth); + calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput, inNorthHem); EXPECT_NEAR(climateOutput.meanTempMon_C[Jan][0], -8.432581, tol6); EXPECT_NEAR(climateOutput.maxTempMon_C[Jan][0], -2.562581, tol6); @@ -486,7 +486,7 @@ namespace { int allocate = 1; int deallocate = 0; - Bool isNorth = swTRUE; + Bool inNorthHem = swTRUE; // Allocate memory allocDeallocClimateStructs(allocate, 2, &climateOutput, &climateAverages); @@ -517,7 +517,7 @@ namespace { } // --- Annual time-series of climate variables ------ - calcSiteClimate(allHist, 2, 1980, &climateOutput, isNorth); + calcSiteClimate(allHist, 2, 1980, &climateOutput, inNorthHem); EXPECT_DOUBLE_EQ(climateOutput.meanTempMon_C[Jan][0], 1.); EXPECT_DOUBLE_EQ(climateOutput.maxTempMon_C[Jan][0], 1.); @@ -597,7 +597,7 @@ namespace { double **meanTempMon_C = new double*[MAX_MONTHS]; - Bool isNorth = swTRUE; + Bool inNorthHem = swTRUE; for(month = 0; month < MAX_MONTHS; month++) { PPTMon_cm[month] = new double[2]; @@ -611,7 +611,7 @@ namespace { // ------ Test for one year ------ - findDriestQtr(result, 1, meanTempMon_C, PPTMon_cm, isNorth); + findDriestQtr(result, 1, meanTempMon_C, PPTMon_cm, inNorthHem); // Value 1.433333... is the average temperature of the driest quarter of the year // In this case, the driest quarter is February-April @@ -619,7 +619,7 @@ namespace { // ------ Test for two years ------ - findDriestQtr(result, 2, meanTempMon_C, PPTMon_cm, isNorth); + findDriestQtr(result, 2, meanTempMon_C, PPTMon_cm, inNorthHem); EXPECT_NEAR(result[0], 1.4333333333333333, tol9); EXPECT_NEAR(result[1], 1.4333333333333333, tol9); @@ -633,7 +633,7 @@ namespace { } } - findDriestQtr(result, 1, meanTempMon_C, PPTMon_cm, isNorth); + findDriestQtr(result, 1, meanTempMon_C, PPTMon_cm, inNorthHem); // Expect that the driest quarter that occurs first // among all driest quarters is used From 813febc690abd86e2ae82b586ea955fab11f80b1 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 2 Oct 2022 21:37:33 -0700 Subject: [PATCH 161/326] Renamed `inNorth` to be consistent and more descriptive - In the branch `feature_climate_predictors`, these names were consistent with "isNorth" but the naming got off track - Instead of keeping it "isNorth" it was decided to make it more descriptive by specifying it's the northern hemisphere --- SW_VegProd.c | 22 ++++++++++------------ SW_VegProd.h | 2 +- test/test_SW_VegProd.cc | 27 ++++++++++++++------------- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index 5c609dc0d..0bd9c48a4 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -893,18 +893,16 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe RelAbundanceL0[8], RelAbundanceL1[5]; Bool fillEmptyWithBareGround = swFALSE, warnExtrapolation = swTRUE; - Bool inNorth; + Bool inNorthHem = swTRUE; - if(latitude > 0.0) { - inNorth = swTRUE; - } else { - inNorth = swFALSE; + if(latitude < 0.0) { + inNorthHem = swFALSE; } // Allocate climate structs' memory allocDeallocClimateStructs(allocate, numYears, &climateOutput, &climateAverages); - calcSiteClimate(SW_Weather.allHist, numYears, startYear, &climateOutput, inNorth); + calcSiteClimate(SW_Weather.allHist, numYears, startYear, &climateOutput, inNorthHem); averageClimateAcrossYears(&climateOutput, numYears, &climateAverages); @@ -917,7 +915,7 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, coverValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, - inNorth, warnExtrapolation, grassOutput, RelAbundanceL0, RelAbundanceL1); + inNorthHem, warnExtrapolation, grassOutput, RelAbundanceL0, RelAbundanceL1); ForEachVegType(k) { vegProd->veg[k].cov.fCover = RelAbundanceL1[k]; @@ -964,7 +962,7 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe @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] inNorth Bool value specifying if the current site is in the northern hemisphere + @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[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: @@ -979,7 +977,7 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe 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 inNorth, Bool warnExtrapolation, + double C4Variables[], Bool fillEmptyWithBareGround, Bool inNorthHem, Bool warnExtrapolation, double *grassOutput, double *RelAbundanceL0, double *RelAbundanceL1) { const int nTypes = 8; @@ -1105,7 +1103,7 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT } else { // Set months of winter and summer (northern/southern hemisphere) // and get their three month values in precipitation and temperature - if(inNorth) { + if(inNorthHem) { for(index = 0; index < 3; index++) { winterMonths[index] = (index + 11) % MAX_MONTHS; summerMonths[index] = (index + 5); @@ -1126,8 +1124,8 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT winterMAP /= PPT_cm; // Get the difference between July and Janurary - tempDiffJanJul = meanTempMon_C[summerMonths[1]] - - meanTempMon_C[winterMonths[1]]; + tempDiffJanJul = fabs(meanTempMon_C[summerMonths[1]] - + meanTempMon_C[winterMonths[1]]); if(warnExtrapolation) { if(meanTemp_C < 1) { diff --git a/SW_VegProd.h b/SW_VegProd.h index 4941cda43..524620abc 100644 --- a/SW_VegProd.h +++ b/SW_VegProd.h @@ -268,7 +268,7 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe 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 inNorth, Bool warnExtrapolation, + double C4Variables[], Bool fillEmptyWithBareGround, Bool inNorthHem, Bool warnExtrapolation, double *grassOutput, double *RelAbundanceL0, double *RelAbundanceL1); double cutZeroInf(double value); void uniqueIndices(int arrayOne[], int arrayTwo[], int arrayOneSize, int arrayTwoSize, diff --git a/test/test_SW_VegProd.cc b/test/test_SW_VegProd.cc index 104afcb5f..032fc88a5 100644 --- a/test/test_SW_VegProd.cc +++ b/test/test_SW_VegProd.cc @@ -190,9 +190,9 @@ namespace { double RelAbundanceL1Expected[5] = {0.0, 0.3084547, 0.2608391, 0.4307062, 0.0}; Bool fillEmptyWithBareGround = swTRUE; - Bool inNorth = swTRUE; + Bool inNorthHem = swTRUE; Bool warnExtrapolation = swTRUE; - Bool isNorth = swTRUE; + Bool inNorthHem = swTRUE; int deallocate = 0; int allocate = 1; @@ -222,7 +222,7 @@ namespace { allocDeallocClimateStructs(allocate, 31, &climateOutput, &climateAverages); // Calculate climate of the site and add results to "climateOutput" - calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput, isNorth); + calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput, inNorthHem); // Average values from "climateOutput" and put them in "climateAverages" averageClimateAcrossYears(&climateOutput, 31, &climateAverages); @@ -235,7 +235,7 @@ namespace { // Estimate vegetation based off calculated variables and "inputValues" estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, - SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorth, warnExtrapolation, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, grassOutput, RelAbundanceL0, RelAbundanceL1); /* ================================== @@ -285,7 +285,7 @@ namespace { estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, - SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorth, warnExtrapolation, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, grassOutput, RelAbundanceL0, RelAbundanceL1); // Loop through RelAbundanceL0 and test results @@ -331,7 +331,7 @@ namespace { estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, - SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorth, warnExtrapolation, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, grassOutput, RelAbundanceL0, RelAbundanceL1); // Loop through RelAbundanceL0 and test results. Since initial values @@ -362,7 +362,7 @@ namespace { estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, - SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorth, warnExtrapolation, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, grassOutput, RelAbundanceL0, RelAbundanceL1); // Loop through RelAbundanceL0 and test results. @@ -452,11 +452,12 @@ namespace { RelAbundanceL1Expected[bareGroundL1] = 0.0549; fillEmptyWithBareGround = swTRUE; + inNorthHem = swTRUE; SumGrassesFraction = .7255; estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, - SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorth, warnExtrapolation, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, grassOutput, RelAbundanceL0, RelAbundanceL1); // Loop through RelAbundanceL0 and test results. @@ -507,9 +508,9 @@ namespace { double RelAbundanceL1Expected[5] = {.1293, .0827, .2884, .4591, .0405}; Bool fillEmptyWithBareGround = swTRUE; - Bool inNorth = swTRUE; + Bool inNorthHem = swTRUE; Bool warnExtrapolation = swTRUE; - Bool isNorth = swTRUE; + Bool inNorthHem = swTRUE; int deallocate = 0; int allocate = 1; @@ -539,7 +540,7 @@ namespace { allocDeallocClimateStructs(allocate, 31, &climateOutput, &climateAverages); // Calculate climate of the site and add results to "climateOutput" - calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput, isNorth); + calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput, inNorthHem); // Average values from "climateOutput" and put them in "climateAverages" averageClimateAcrossYears(&climateOutput, 31, &climateAverages); @@ -552,7 +553,7 @@ namespace { // Estimate vegetation based off calculated variables and "inputValues" estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, - SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorth, warnExtrapolation, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, grassOutput, RelAbundanceL0, RelAbundanceL1); // All values in "RelAbundanceL0" should be exactly the same as "inputValues" @@ -598,7 +599,7 @@ namespace { estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, - SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorth, warnExtrapolation, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, grassOutput, RelAbundanceL0, RelAbundanceL1); // Loop through RelAbundanceL0 and test results From f13d9b68082da76d78547cb81ac36a9069429d50 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 2 Oct 2022 21:39:05 -0700 Subject: [PATCH 162/326] Uncommented "inNorthHem = swFALSE" unit test - The unit test in `test_SW_Weather.cc` for when "inNorthHem" is false has been uncommented and tested --- test/test_SW_VegProd.cc | 80 ++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/test/test_SW_VegProd.cc b/test/test_SW_VegProd.cc index 032fc88a5..c8ba0d1d3 100644 --- a/test/test_SW_VegProd.cc +++ b/test/test_SW_VegProd.cc @@ -380,48 +380,48 @@ namespace { EXPECT_NEAR(grassOutput[2], .333333, tol6); /* ================================== - Test with `inNorth` to be false, same input values as previous test + Test with `inNorthHem` to be false, same input values as previous test except for trees and bare ground which are both .0549 ================================== */ -// RelAbundanceL0Expected[succIndex] = 0.1098; -// RelAbundanceL0Expected[forbIndex] = 0.1098; -// RelAbundanceL0Expected[C3Index] = 0.1098; -// RelAbundanceL0Expected[C4Index] = 0.1098; -// RelAbundanceL0Expected[grassAnn] = 0.1098; -// RelAbundanceL0Expected[shrubIndex] = 0.1098; -// RelAbundanceL0Expected[treeIndex] = 0.0549; -// RelAbundanceL0Expected[bareGround] = 0.2863; -// -// RelAbundanceL1Expected[treeIndexL1] = 0.0549; -// RelAbundanceL1Expected[forbIndexL1] = 0.1098; -// RelAbundanceL1Expected[shrubIndexL1] = 0.2196; -// RelAbundanceL1Expected[grassesIndexL1] = 0.3294; -// RelAbundanceL1Expected[bareGroundL1] = 0.2863; -// -// inNorth = swFALSE; -// fillEmptyWithBareGround = swTRUE; -// -// inputValues[treeIndex] = .0549; -// inputValues[bareGround] = .0549; -// -// estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, -// climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, -// SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorth, warnExtrapolation, -// grassOutput, RelAbundanceL0, RelAbundanceL1); -// -// // Loop through RelAbundanceL0 and test results. -// for(index = 0; index < 8; 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); -// } -// -// EXPECT_NEAR(grassOutput[0], .333333, tol6); -// EXPECT_NEAR(grassOutput[1], .333333, tol6); -// EXPECT_NEAR(grassOutput[2], .333333, tol6); + RelAbundanceL0Expected[succIndex] = 0.1098; + RelAbundanceL0Expected[forbIndex] = 0.1098; + RelAbundanceL0Expected[C3Index] = 0.1098; + RelAbundanceL0Expected[C4Index] = 0.1098; + RelAbundanceL0Expected[grassAnn] = 0.1098; + RelAbundanceL0Expected[shrubIndex] = 0.1098; + RelAbundanceL0Expected[treeIndex] = 0.0549; + RelAbundanceL0Expected[bareGround] = 0.2863; + + RelAbundanceL1Expected[treeIndexL1] = 0.0549; + RelAbundanceL1Expected[forbIndexL1] = 0.1098; + RelAbundanceL1Expected[shrubIndexL1] = 0.2196; + RelAbundanceL1Expected[grassesIndexL1] = 0.3294; + RelAbundanceL1Expected[bareGroundL1] = 0.2863; + + inNorthHem = swFALSE; + fillEmptyWithBareGround = swTRUE; + + inputValues[treeIndex] = .0549; + inputValues[bareGround] = .0549; + + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, + grassOutput, RelAbundanceL0, RelAbundanceL1); + + // Loop through RelAbundanceL0 and test results. + for(index = 0; index < 8; 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); + } + + EXPECT_NEAR(grassOutput[0], .333333, tol6); + EXPECT_NEAR(grassOutput[1], .333333, tol6); + EXPECT_NEAR(grassOutput[2], .333333, tol6); /* ================================== Test with `SumGrassesFraction` being fixed, all input of previous tests From 6774687c42f366bf851b11af3dbf9f75afb96edf Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 2 Oct 2022 22:12:13 -0700 Subject: [PATCH 163/326] Added to NEWS for v7.0.0 --- NEWS.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/NEWS.md b/NEWS.md index 314759842..fa4af50ea 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,11 @@ +# SOILWAT2 v7.0.0 +* Moved `calc_SiteClimate()` and `estimate_PotNatVeg_composition()` + functionality from rSOILWAT2 to SOILWAT2. +* Yesterday values for variables in SW_WEATHER_2DAYS struct are no longer used, + which motivated the struct being renamed to SW_WEATHER_NOW. +* Weather is now read in all at once, instead of reading one year at a time, by the new function + `readAllWeather()`. +* SOILWAT2 gains `veg_method` as new user input (`"veg.in"`). # SOILWAT2 v6.6.0 * Random number generators now produce sequences that can be exactly reproduced. From ba62768b65d3e3b0c23d50c55834acb6ad44db2f Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 2 Oct 2022 22:37:52 -0700 Subject: [PATCH 164/326] Fixed duplicate "inNorthHem" --- test/test_SW_VegProd.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/test_SW_VegProd.cc b/test/test_SW_VegProd.cc index c8ba0d1d3..1b1c0bfce 100644 --- a/test/test_SW_VegProd.cc +++ b/test/test_SW_VegProd.cc @@ -190,7 +190,6 @@ namespace { double RelAbundanceL1Expected[5] = {0.0, 0.3084547, 0.2608391, 0.4307062, 0.0}; Bool fillEmptyWithBareGround = swTRUE; - Bool inNorthHem = swTRUE; Bool warnExtrapolation = swTRUE; Bool inNorthHem = swTRUE; @@ -510,7 +509,6 @@ namespace { Bool fillEmptyWithBareGround = swTRUE; Bool inNorthHem = swTRUE; Bool warnExtrapolation = swTRUE; - Bool inNorthHem = swTRUE; int deallocate = 0; int allocate = 1; From 679226f03acfb8993b09fca3659ea2e141cce3a2 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 5 Oct 2022 15:41:27 -0400 Subject: [PATCH 165/326] Isolate "allWeather" functionality from global variables - main purpose: handle daily weather (allocate, deallocate, read daily values, manage time, handle missing weather, apply monthly scaling) in isolation from setting up and running a full simulation ** in particular, enable STEPWAT2 to generate new daily weather for each combination of iteration, year (or number of years), and grid cell - new `finalizeAllWeather()` runs the weather generator and applies monthly scaling parameters -- previously, functionality was part of `readAllWeather()` - new `SW_WTH_finalize_all_weather()` is a wrapper for `finalizeAllWeather()` relying on global variable "SW_Weather" for SOILWAT2 convenience ** `SW_WTH_finalize_all_weather()` needs to be called after `SW_CTL_read_inputs_from_disk()` and before `SW_CTL_init_run()` - remove dependency on global variables "SW_Weather" and "SW_Model" ** `readAllWeather()` gains arguments "use_weathergenerator_only" and "weather_prefix" ** `_read_weather_hist()` gains argument "weather_prefix" ** `allocateAllWeather()` gains argument "w" ** `deallocateAllWeather()` gains argument "w" - struct `SW_WEATHER` ** element "hist" removed -- it was not utilized ** element "yr" removed -- it was not utilized functionally ** new element "startYear" -- to store the first calendar year represented by element "allHist" (now independent from `SW_Model.startyr` as required by STEPWAT2) - `readAllWeather()` ** now calls `_clear_hist_weather()` to set values to missing, independent of `use_weathergenerator_only` (this was done previously be `_read_weather_hist()`) ** moved calculation of average air temperature to `_read_weather_hist()` -- to avoid (third) loop over days - `_read_weather_hist()` ** now returns "void" (instead of TRUE/FALSE that was indicating if current's year weather data file exists and was read) ** no longer calls `_clear_hist_weather() -- this is now done consistently by `readAllWeather()` ** calculate average air temperature considering potentially missing min/max air temperature values -- previously, done by `readAllWeather()` - `SW_WTH_new_day()` now calculates the year-index to query current's year element of `allHist` based on `SW_Weather.startYear` (instead of `SW_Model.startyr` as required by STEPWAT2) - `SW_WTH_read()` is suitable for SOILWAT2-standalone ** now sets `SW_Weather.startYear` to `SW_Model.startyr` (previously implied) -- and removed exception for STEPWAT2 ** rSOILWAT2 (`onSet_WTH_DATA()`) and STEPWAT2 (`_sxw_generate_weather()`) will need their own custom function to deal with weather data - Removal of element "yr" from struct "SW_WEATHER" ** updated input file "weathsetup.in" -- user visible change to the interface without change in functionality ** updated `SW_WTH_setup()` to handle changes in "weathsetup.in" --- SW_Main.c | 4 +- SW_Model.c | 1 - SW_Output.h | 1 - SW_Weather.c | 247 ++++++++++++++++++------------------ SW_Weather.h | 41 ++++-- test/sw_testhelpers.cc | 1 + test/test_SW_Weather.cc | 55 ++++---- test/test_WaterBalance.cc | 2 + testing/Input/weathsetup.in | 4 - 9 files changed, 190 insertions(+), 166 deletions(-) diff --git a/SW_Main.c b/SW_Main.c index b4b71cabc..2e5d16219 100644 --- a/SW_Main.c +++ b/SW_Main.c @@ -28,7 +28,6 @@ #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" @@ -83,6 +82,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_Model.c b/SW_Model.c index bffaf0d6c..b3bb257b6 100644 --- a/SW_Model.c +++ b/SW_Model.c @@ -43,7 +43,6 @@ #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" diff --git a/SW_Output.h b/SW_Output.h index d4a2f77fa..75a489e03 100644 --- a/SW_Output.h +++ b/SW_Output.h @@ -59,7 +59,6 @@ #include "Times.h" #include "SW_Defines.h" #include "SW_SoilWater.h" -#include "SW_Weather.h" #include "SW_VegProd.h" #ifdef __cplusplus diff --git a/SW_Weather.c b/SW_Weather.c index 3f566915a..3a53e4479 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -52,13 +52,6 @@ #include "SW_Markov.h" #include "SW_Weather.h" -#ifdef RSOILWAT - #include "../rSW_Weather.h" -#endif - -#ifdef STEPWAT - #include "../ST_globals.h" // externs `SuperGlobals -#endif @@ -87,53 +80,83 @@ static char *MyFileName; /** - @brief Reads in all weather data through all years and stores them in global SW_Weather's `allHist` + @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 `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. */ -void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, unsigned int n_years) { +void readAllWeather( + SW_WEATHER_HIST **allHist, + int startYear, + unsigned int n_years, + Bool use_weathergenerator_only, + char weather_prefix[] +) { + unsigned int yearIndex; - int year; - unsigned int yearIndex, numDaysYear, day; + for(yearIndex = 0; yearIndex < n_years; yearIndex++) { - Bool weth_found = swFALSE; + // Set all daily weather values to missing + _clear_hist_weather(allHist[yearIndex]); - for(yearIndex = 0; yearIndex < n_years; yearIndex++) { + // Read daily weather values from disk + if (!use_weathergenerator_only) { - if(SW_Weather.use_weathergenerator_only) { - // Set to missing for call to `generateMissingWeather()` - _clear_hist_weather(allHist[yearIndex]); - - } else { - year = yearIndex + startYear; - - weth_found = _read_weather_hist(year, allHist[yearIndex]); - - // Calculate average air temperature - if (weth_found) { - Time_new_year(year); - numDaysYear = Time_get_lastdoy_y(year); - - for (day = 0; day < numDaysYear; day++) { - 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.; - } - } - } + _read_weather_hist( + startYear + yearIndex, + allHist[yearIndex], + weather_prefix + ); } } } + +/** + @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) { + // Impute missing values + generateMissingWeather( + w->allHist, + w->startYear, + w->n_years, + w->generateWeatherMethod, + 3 // optLOCF_nMax (TODO: make this user input) + ); + + + // 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 + ); +} + + +void SW_WTH_finalize_all_weather(void) { + finalizeAllWeather(&SW_Weather); +} + + /** @brief Apply temperature and precipitation scaling to daily weather values @@ -179,7 +202,7 @@ void scaleAllWeather( // Apply scaling parameters to each day of `allHist` for (yearIndex = 0; yearIndex < n_years; yearIndex++) { year = yearIndex + startYear; - Time_new_year(year); + Time_new_year(year); // required for `doy2month()` numDaysYear = Time_get_lastdoy_y(year); for (day = 0; day < numDaysYear; day++) { @@ -286,7 +309,6 @@ void generateMissingWeather( // Loop over years for (yearIndex = 0; yearIndex < n_years; yearIndex++) { year = yearIndex + startYear; - Time_new_year(year); numDaysYear = Time_get_lastdoy_y(year); iMissing = 0; @@ -425,39 +447,39 @@ void SW_WTH_deconstruct(void) SW_MKV_deconstruct(); } - deallocateAllWeather(); + deallocateAllWeather(&SW_Weather); } /** - @brief Allocate memory for `allHist` of `SW_Weather` based on `n_years` + @brief Allocate memory for `allHist` for `w` based on `n_years` */ -void allocateAllWeather(void) { +void allocateAllWeather(SW_WEATHER *w) { unsigned int year; - SW_Weather.allHist = (SW_WEATHER_HIST **)malloc(sizeof(SW_WEATHER_HIST *) * SW_Weather.n_years); + w->allHist = (SW_WEATHER_HIST **)malloc(sizeof(SW_WEATHER_HIST *) * w->n_years); - for (year = 0; year < SW_Weather.n_years; year++) { + for (year = 0; year < w->n_years; year++) { - SW_Weather.allHist[year] = (SW_WEATHER_HIST *)malloc(sizeof(SW_WEATHER_HIST)); + w->allHist[year] = (SW_WEATHER_HIST *)malloc(sizeof(SW_WEATHER_HIST)); } } /** - @brief Helper function to SW_WTH_deconstruct to deallocate allHist array. + @brief Helper function to SW_WTH_deconstruct to deallocate `allHist` of `w`. */ -void deallocateAllWeather(void) { +void deallocateAllWeather(SW_WEATHER *w) { unsigned int year; - if(!isnull(SW_Weather.allHist)) { - for(year = 0; year < SW_Weather.n_years; year++) { - free(SW_Weather.allHist[year]); + if(!isnull(w->allHist)) { + for(year = 0; year < w->n_years; year++) { + free(w->allHist[year]); } - free(SW_Weather.allHist); - SW_Weather.allHist = NULL; + free(w->allHist); + w->allHist = NULL; } } @@ -501,20 +523,24 @@ void SW_WTH_new_day(void) { SW_WEATHER *w = &SW_Weather; SW_WEATHER_NOW *wn = &SW_Weather.now; - TimeInt day = SW_Model.doy - 1, year = SW_Model.year - SW_Model.startyr; + /* 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 */ if ( - missing(w->allHist[year]->temp_avg[day]) || - missing(w->allHist[year]->ppt[day]) + missing(w->allHist[yearIndex]->temp_avg[day]) || + missing(w->allHist[yearIndex]->ppt[day]) ) { LogError( logfp, @@ -525,11 +551,11 @@ void SW_WTH_new_day(void) { ); } - wn->temp_max = w->allHist[year]->temp_max[day]; - wn->temp_min = w->allHist[year]->temp_min[day]; - wn->ppt = w->allHist[year]->ppt[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->temp_avg = w->allHist[year]->temp_avg[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.; @@ -549,7 +575,7 @@ void SW_WTH_new_day(void) { void SW_WTH_setup(void) { /* =================================================== */ SW_WEATHER *w = &SW_Weather; - const int nitems = 18; + const int nitems = 17; FILE *f; int lineno = 0, month, x; RealF sppt, stmax, stmin; @@ -612,13 +638,8 @@ void SW_WTH_setup(void) { 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) + if (lineno == 5 + MAX_MONTHS) break; x = sscanf( @@ -650,68 +671,41 @@ void SW_WTH_setup(void) { 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 (SW_Weather.generateWeatherMethod != 2 && 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 (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) - deallocateAllWeather(); + // `SW_WTH_construct()` sets `n_years` to zero + deallocateAllWeather(&SW_Weather); - // Determine required (new) size of new `allHist` - #ifdef STEPWAT - SW_Weather.n_years = SuperGlobals.runModelYears; - #else + // Update number of years and first calendar year represented SW_Weather.n_years = SW_Model.endyr - SW_Model.startyr + 1; - #endif + SW_Weather.startYear = SW_Model.startyr; // Allocate new `allHist` (based on current `SW_Weather.n_years`) - allocateAllWeather(); - - - // Read daily meteorological input from disk - readAllWeather(SW_Weather.allHist, SW_Model.startyr, SW_Weather.n_years); + allocateAllWeather(&SW_Weather); - - // Impute missing values - generateMissingWeather( + // Read daily meteorological input from disk (if available) + readAllWeather( SW_Weather.allHist, - SW_Model.startyr, + SW_Weather.startYear, SW_Weather.n_years, - SW_Weather.generateWeatherMethod, - 3 // optLOCF_nMax (TODO: make this user input) - ); - - - // Scale with monthly additive/multiplicative parameters - scaleAllWeather( - SW_Weather.allHist, - SW_Model.startyr, - SW_Weather.n_years, - SW_Weather.scale_temp_max, - SW_Weather.scale_temp_min, - SW_Weather.scale_precip + SW_Weather.use_weathergenerator_only, + SW_Weather.name_prefix ); } + /** @brief Read the historical (observed) weather file for a simulation year. The naming convection of the weather input files: @@ -724,11 +718,13 @@ void SW_WTH_read(void) { @param year Current year within the simulation @param yearWeather Current year's weather array that is to be filled by function - - @return `swTRUE`/`swFALSE` if historical daily meteorological inputs are - successfully/unsuccessfully read in. + @param weather_prefix File name of weather data without extension. */ -Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather) { +void _read_weather_hist( + TimeInt year, + SW_WEATHER_HIST *yearWeather, + char weather_prefix[] +) { /* =================================================== */ /* Read the historical (measured) weather files. * Format is @@ -752,12 +748,11 @@ Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather) { char fname[MAX_FILENAMESIZE]; - sprintf(fname, "%s.%4d", SW_Weather.name_prefix, year); - - _clear_hist_weather(yearWeather); // clear values before returning + // Create file name: `[weather-file prefix].[year]` + sprintf(fname, "%s.%4d", weather_prefix, year); if (NULL == (f = fopen(fname, "r"))) - return swFALSE; + return; while (GetALine(f, inbuf)) { lineno++; @@ -779,9 +774,14 @@ Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather) { doy--; // base1 -> base0 yearWeather->temp_max[doy] = tmpmax; yearWeather->temp_min[doy] = tmpmin; - yearWeather->temp_avg[doy] = (tmpmax + tmpmin) / 2.0; yearWeather->ppt[doy] = ppt; + // Calculate average air temperature if min/max not missing + if (!missing(tmpmax) && !missing(tmpmin)) { + yearWeather->temp_avg[doy] = (tmpmax + tmpmin) / 2.0; + } + + // 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 @@ -815,7 +815,6 @@ Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather) { */ fclose(f); - return swTRUE; } #ifdef DEBUG_MEM diff --git a/SW_Weather.h b/SW_Weather.h index 10b4aec40..d8c588159 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -40,12 +40,12 @@ extern "C" { */ typedef struct { - /* comes from markov weather day-to-day */ + /* Weather values of the current simulation day */ RealD temp_avg, temp_max, temp_min, ppt, rain; } SW_WEATHER_NOW; typedef struct { - /* comes from historical weather files */ + /* Daily weather values for one year */ 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; @@ -70,12 +70,10 @@ typedef struct { // 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; - unsigned int n_years; - SW_TIMES yr; RealD scale_precip[MAX_MONTHS], scale_temp_max[MAX_MONTHS], @@ -91,9 +89,14 @@ typedef struct { 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_HIST **allHist; - SW_WEATHER_NOW now; + + + /* 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; @@ -110,8 +113,19 @@ extern SW_WEATHER SW_Weather; /* --------------------------------------------------- */ void SW_WTH_setup(void); void SW_WTH_read(void); -Bool _read_weather_hist(TimeInt year, SW_WEATHER_HIST *yearWeather); -void readAllWeather(SW_WEATHER_HIST **allHist, int startYear, unsigned int n_years); +void _read_weather_hist( + TimeInt year, + SW_WEATHER_HIST *yearWeather, + char weather_prefix[] +); +void readAllWeather( + SW_WEATHER_HIST **allHist, + int startYear, + unsigned int n_years, + Bool use_weathergenerator_only, + char weather_prefix[] +); +void finalizeAllWeather(SW_WEATHER *w); void scaleAllWeather( SW_WEATHER_HIST **allHist, int startYear, @@ -127,9 +141,10 @@ void generateMissingWeather( unsigned int method, unsigned int optLOCF_nMax ); -void allocateAllWeather(void); -void deallocateAllWeather(void); +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); diff --git a/test/sw_testhelpers.cc b/test/sw_testhelpers.cc index c34054d0a..a4e88b9c1 100644 --- a/test/sw_testhelpers.cc +++ b/test/sw_testhelpers.cc @@ -46,6 +46,7 @@ void Reset_SOILWAT2_after_UnitTest(void) { SW_CTL_setup_model(_firstfile); SW_CTL_read_inputs_from_disk(); + SW_WTH_finalize_all_weather(); SW_CTL_init_run(); diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 8c5946bd3..e31e233e0 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -23,10 +23,16 @@ namespace { TEST(ReadAllWeatherTest, DefaultValues) { - + // Testing to fill allHist from `SW_Weather` - readAllWeather(SW_Weather.allHist, 1980, SW_Weather.n_years); - + readAllWeather( + SW_Weather.allHist, + 1980, + SW_Weather.n_years, + SW_Weather.use_weathergenerator_only, + SW_Weather.name_prefix + ); + // 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); @@ -64,7 +70,7 @@ namespace { SW_MKV_setup(); SW_WTH_read(); - + SW_WTH_finalize_all_weather(); // Expect that missing input values (from 1980) are filled by the weather generator @@ -75,11 +81,11 @@ namespace { 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; @@ -87,46 +93,49 @@ namespace { 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) { @@ -140,9 +149,11 @@ namespace { 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_read(), + SW_WTH_finalize_all_weather(), "" ); @@ -151,11 +162,11 @@ namespace { } TEST(WeatherReadTest, Initialization) { - + SW_WTH_read(); - + EXPECT_FLOAT_EQ(SW_Weather.allHist[0]->temp_max[0], -.52); - + } diff --git a/test/test_WaterBalance.cc b/test/test_WaterBalance.cc index edb14d3a4..4c6677d1f 100644 --- a/test/test_WaterBalance.cc +++ b/test/test_WaterBalance.cc @@ -123,6 +123,7 @@ namespace { // Prepare weather data SW_WTH_read(); + SW_WTH_finalize_all_weather(); // Run the simulation SW_CTL_main(); @@ -153,6 +154,7 @@ namespace { // Prepare weather data SW_WTH_read(); + SW_WTH_finalize_all_weather(); // Run the simulation SW_CTL_main(); diff --git a/testing/Input/weathsetup.in b/testing/Input/weathsetup.in index 3eeedd428..c463e31eb 100755 --- a/testing/Input/weathsetup.in +++ b/testing/Input/weathsetup.in @@ -15,10 +15,6 @@ 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. From 9ac8a0c7d2ef2f4e8ff93951ddcc8f30c36fbb02 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 5 Oct 2022 16:39:45 -0400 Subject: [PATCH 166/326] =?UTF-8?q?Fix=20implicit=20declaration=20of=20fun?= =?UTF-8?q?ction=20=E2=80=98SW=5FWTH=5Ffinalize=5Fall=5Fweather=E2=80=99?= =?UTF-8?q?=20in=20`main()`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SW_Main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/SW_Main.c b/SW_Main.c index 2e5d16219..358d6405e 100644 --- a/SW_Main.c +++ b/SW_Main.c @@ -28,6 +28,7 @@ #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" From d2b4c6fbe1651a2378ee24c02aa9ae78a5a93878 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sat, 8 Oct 2022 01:08:31 -0700 Subject: [PATCH 167/326] Updated generic and weather function documentation - generic.c: * Updated documentation for `mean()` and `standardDeviation()` mentioning how SW_MISSING values are handled - SW_Weather.c/SW_Weather.h * Updated documentation for `calcSiteClimate()` and helper functions like parameters and descriptions * Changed order of parameters for `calcSiteClimate()` and `findDriestQtr()` --- SW_Weather.c | 52 ++++++++++++++++++++++++++++++---------------------- SW_Weather.h | 6 +++--- generic.c | 13 +++++++++++-- 3 files changed, 44 insertions(+), 27 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 4de87b009..8564b8b5d 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -75,7 +75,7 @@ static char *MyFileName; /** @brief Takes averages through the number of years of the calculated values from calc_SiteClimate - @param[in] climateOutput Structure of type SW_CLIMATE_OUTPUT that holds all output from `calcSiteClimate()` + @param[in] climateOutput Structure of type SW_CLIMATE_YEARLY that holds all output from `calcSiteClimate()` @param[in] numYears Calendar year corresponding to first year of `allHist` @param[out] climateAverages Structure of type SW_CLIMATE_CLIM that holds averages and standard deviations output by `averageClimateAcrossYears()` @@ -118,7 +118,8 @@ void averageClimateAcrossYears(SW_CLIMATE_YEARLY *climateOutput, int numYears, } /** - @brief Calculate monthly and annual time series of climate variables from daily weather + @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: @@ -142,18 +143,19 @@ void averageClimateAcrossYears(SW_CLIMATE_YEARLY *climateOutput, int numYears, @param[in] allHist Array containing all historical data of a site @param[in] numYears Number of years represented by `allHist` @param[in] startYear Calendar year corresponding to first year of `allHist` - @param[out] climateOutput Structure of type SW_CLIMATE_CLIM that holds averages and + @param[in] inNorthHem Boolean value specifying if site is in northern hemisphere + @param[out] climateOutput Structure of type SW_CLIMATE_YEARLY that holds averages and standard deviations output by `averageClimateAcrossYears()` */ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, - SW_CLIMATE_YEARLY *climateOutput, Bool inNorthHem) { + 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, currentJulyMin, JulyPPT, + double currentTempMin, currentTempMean, totalAbove65, current7thMonMin, PPT7thMon, consecNonFrost, currentNonFrost; // Initialize accumulated value arrays to all zeros @@ -194,11 +196,11 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, numDaysYear = Time_get_lastdoy_y(year); month = (inNorthHem) ? Jan : Jul; currMonDay = 0; - currentJulyMin = SW_MISSING; + current7thMonMin = SW_MISSING; totalAbove65 = 0; currentNonFrost = 0; consecNonFrost = 0; - JulyPPT = 0; + PPT7thMon = 0; if(!inNorthHem) { // Get calendar year days only when site is in southern hemisphere @@ -226,7 +228,7 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, // 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" - JulyPPT = SW_MISSING; + PPT7thMon = SW_MISSING; totalAbove65 = SW_MISSING; currentNonFrost = SW_MISSING; consecNonFrost = SW_MISSING; @@ -240,9 +242,9 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, // Part of code that deals with gathering seventh month information if(month == seventhMonth){ - currentJulyMin = (currentTempMin < currentJulyMin) ? - currentTempMin : currentJulyMin; - JulyPPT += allHist[adjustedYear]->ppt[adjustedDoy] * 10; + current7thMonMin = (currentTempMin < current7thMonMin) ? + currentTempMin : current7thMonMin; + PPT7thMon += allHist[adjustedYear]->ppt[adjustedDoy] * 10; } // Part of code dealing with consecutive amount of days without frost @@ -285,8 +287,8 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, } // Set all values - climateOutput->minTemp7thMon_C[yearIndex] = currentJulyMin; - climateOutput->PPT7thMon_mm[yearIndex] = JulyPPT; + 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, @@ -294,13 +296,19 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, climateOutput->frostFree_days[yearIndex] = (consecNonFrost > 0) ? consecNonFrost : currentNonFrost; } - findDriestQtr(climateOutput->meanTempDriestQtr_C, numYears, - climateOutput->meanTempMon_C, climateOutput->PPTMon_cm, inNorthHem); + 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 simulation covers + @param[in] startYear Calendar year corresponding to first year of `allHist` + @param[out] climateOutput Structure of type SW_CLIMATE_YEARLY that holds averages and + standard deviations output by `averageClimateAcrossYears()` */ void calcSiteClimateLatInvariants(SW_WEATHER_HIST **allHist, int numYears, int startYear, @@ -339,18 +347,19 @@ void calcSiteClimateLatInvariants(SW_WEATHER_HIST **allHist, int numYears, int s } /** - @brief Helper function to calcsiteClimate to find the driest quarter of the year's average temperature + @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] startYear Calendar year corresponding to first year of 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 - @param[out] meanTempDriestQtr_C Array of size numYears holding the average temperature of the driest quarter of the year for every year */ -void findDriestQtr(double *meanTempDriestQtr_C, int numYears, double **meanTempMon_C, - double **PPTMon_cm, Bool inNorthHem) { +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; @@ -432,8 +441,7 @@ void driestQtrSouthAdjMonYears(int month, int *adjustedYearZero, int *adjustedYe *adjustedMonth = month + Jul; *adjustedMonth %= MAX_MONTHS; - // Adjust prevMonth, nextMonth and adjustedYear(s) with respect to the - // respective adjustedMonth + // Adjust prevMonth, nextMonth and adjustedYear(s) to the respective adjustedMonth switch(*adjustedMonth) { case Jan: adjustedYearOne++; diff --git a/SW_Weather.h b/SW_Weather.h index 1733691d0..e64baea05 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -167,11 +167,11 @@ 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, - SW_CLIMATE_YEARLY *climateOutput, Bool inNorthHem); + Bool inNorthHem, SW_CLIMATE_YEARLY *climateOutput); void calcSiteClimateLatInvariants(SW_WEATHER_HIST **allHist, int numYears, int startYear, SW_CLIMATE_YEARLY *climateOutput); -void findDriestQtr(double *meanTempDriestQtr_C, int numYears, double **meanTempMon_C, - double **meanPPTMon_cm, Bool inNorthHem); +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); diff --git a/generic.c b/generic.c index e13d50969..49dce535b 100644 --- a/generic.c +++ b/generic.c @@ -424,13 +424,18 @@ double final_running_sd(unsigned int n, double ssqr) /** @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]; @@ -439,17 +444,21 @@ double mean(double values[], int length) { 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) { From 1d7d3f2a019c20dd0f611ab533d91e5e361f3820 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sat, 8 Oct 2022 01:12:43 -0700 Subject: [PATCH 168/326] Added two tests to generic unit tests - Created one test for `mean()` and `standardDeviation()` to test that SW_MISSING is handled as expected --- test/test_generic.cc | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/test_generic.cc b/test/test_generic.cc index 3c95cd60a..1b9a95102 100644 --- a/test/test_generic.cc +++ b/test/test_generic.cc @@ -62,6 +62,7 @@ namespace { 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); @@ -77,12 +78,19 @@ namespace { // 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); @@ -93,6 +101,11 @@ namespace { // 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); } From ed3fd83a3f761952675fba2b7537313ea1f2edb1 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sat, 8 Oct 2022 01:15:47 -0700 Subject: [PATCH 169/326] Updated weather tests/test documentation - Updated `calcSiteClimate()` and `findDriestQtr()` calls to match parameter adjustment - Updated southern hemisphere test documentation to explain why values are different but similar to rSOILWAT2 before version v6.0.0 --- test/test_SW_Weather.cc | 60 ++++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 073738d86..4aa42b129 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -194,7 +194,7 @@ namespace { // ) // ``` - calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput, inNorthHem); + 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); @@ -294,7 +294,7 @@ namespace { // ) // ``` - calcSiteClimate(SW_Weather.allHist, 1, 1980, &climateOutput, inNorthHem); + calcSiteClimate(SW_Weather.allHist, 1, 1980, inNorthHem, &climateOutput); averageClimateAcrossYears(&climateOutput, 1, &climateAverages); // Expect that aggregated values across one year are identical @@ -386,6 +386,19 @@ namespace { 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; @@ -393,7 +406,8 @@ namespace { int deallocate = 0; int allocate = 1; - Bool inNorthHem = swFALSE; + // "South" and not "North" to reduce confusion when calling `calcSiteClimate()` + Bool inSouthHem = swFALSE; // Allocate memory // 31 = number of years used in test @@ -406,18 +420,19 @@ namespace { // --- Annual time-series of climate variables ------ // Here, check values for 1980 - // Expect identical output to rSOILWAT2 (e.g., v5.3.1) + // Expect similar output to rSOILWAT2 before v6.0.0 (e.g., v5.3.1) // ```{r} // rSOILWAT2::calc_SiteClimate( // weatherList = rSOILWAT2::get_WeatherHistory( // rSOILWAT2::sw_exampleData // )[1], // do_C4vars = TRUE, - // do_Cheatgrass_ClimVars = TRUE + // do_Cheatgrass_ClimVars = TRUE, + // latitude = -10 // ) // ``` - calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput, inNorthHem); + 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); @@ -429,26 +444,27 @@ namespace { // 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); // 79 - EXPECT_NEAR(climateOutput.ddAbove65F_degday[1], 16.458001, tol6); // 16.965000 + 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_NEAR(climateOutput.PPT7thMon_mm[1], 22.199999, tol6); // 22.19999 - EXPECT_NEAR(climateOutput.meanTempDriestQtr_C[0], 0.936387, tol6); // 15.8733906 - EXPECT_NEAR(climateOutput.minTemp2ndMon_C[1], 5.1445161, tol6); // 5.3467742 + EXPECT_NEAR(climateOutput.PPT7thMon_mm[1], 22.199999, tol6); + 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 identical output to rSOILWAT2 (e.g., v5.3.1) + // Expect similar output to rSOILWAT2 before v6.0.0 (e.g., v5.3.1), identical otherwise // ```{r} // rSOILWAT2::calc_SiteClimate( // weatherList = rSOILWAT2::get_WeatherHistory( // rSOILWAT2::sw_exampleData // ), // do_C4vars = TRUE, - // do_Cheatgrass_ClimVars = TRUE + // do_Cheatgrass_ClimVars = TRUE, + // latitude = -10 // ) // ``` @@ -465,13 +481,13 @@ namespace { EXPECT_NEAR(climateAverages.meanTemp_C, 4.154009, tol6); // Climate variables used for C4 grass cover - EXPECT_NEAR(climateAverages.minTemp7thMon_C, -27.199333, tol6); // -27.146999 - EXPECT_NEAR(climateAverages.frostFree_days, 72.599999, tol6); // 72.6333333 - EXPECT_NEAR(climateAverages.ddAbove65F_degday, 21.357533, tol6); // 21.2880665 + 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); // 9.4229482 - EXPECT_NEAR(climateAverages.sdC4[2], 19.550419, tol6); // 19.589081 + 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); @@ -528,7 +544,7 @@ namespace { } // --- Annual time-series of climate variables ------ - calcSiteClimate(allHist, 2, 1980, &climateOutput, inNorthHem); + calcSiteClimate(allHist, 2, 1980, inNorthHem, &climateOutput); EXPECT_DOUBLE_EQ(climateOutput.meanTempMon_C[Jan][0], 1.); EXPECT_DOUBLE_EQ(climateOutput.maxTempMon_C[Jan][0], 1.); @@ -622,7 +638,7 @@ namespace { // ------ Test for one year ------ - findDriestQtr(result, 1, meanTempMon_C, PPTMon_cm, inNorthHem); + 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 @@ -630,7 +646,7 @@ namespace { // ------ Test for two years ------ - findDriestQtr(result, 2, meanTempMon_C, PPTMon_cm, inNorthHem); + findDriestQtr(2, inNorthHem, result, meanTempMon_C, PPTMon_cm); EXPECT_NEAR(result[0], 1.4333333333333333, tol9); EXPECT_NEAR(result[1], 1.4333333333333333, tol9); @@ -644,7 +660,7 @@ namespace { } } - findDriestQtr(result, 1, meanTempMon_C, PPTMon_cm, inNorthHem); + findDriestQtr(1, inNorthHem, result, meanTempMon_C, PPTMon_cm); // Expect that the driest quarter that occurs first // among all driest quarters is used From ee68aad42a45db7f734c80ca15441175bb8c57e1 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sat, 15 Oct 2022 18:54:29 -0700 Subject: [PATCH 170/326] Fixed potential natural vegetation bugs - `estimateVegetationFromClimate()` * Set bare cover in SW_VEGPROD struct * Now defaults "fillEmptyWithBareGround" to false * Accommodated `calcSiteClimate()` call to fit the modified function header order - `estimatePotNatVegComposition()` * Some `LogError()` calls would contain a comma separating the string and causing incorrect messages. Those have been removed and messages now properly display * Chunk of code what not correcly placed in the major else branch of the function causing incorrect values * Removed all necessary `!missing(...)` to esimate all values * Would not estimate fixed values to estimate others * Added if-statements when setting RelAbundance0/1 that will set estimCover value back to fixed value is need be --- SW_VegProd.c | 330 ++++++++++++++++++++++++++------------------------- 1 file changed, 167 insertions(+), 163 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index 0bd9c48a4..49d2623db 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -879,7 +879,8 @@ void get_critical_rank(void){ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYear, int veg_method, double latitude) { - int numYears = endYear - startYear + 1, deallocate = 0, allocate = 1, k; + int numYears = endYear - startYear + 1, deallocate = 0, allocate = 1, k, + bareGroundIndex = 7; SW_CLIMATE_YEARLY climateOutput; SW_CLIMATE_CLIM climateAverages; @@ -892,7 +893,7 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe double SumGrassesFraction = SW_MISSING, C4Variables[3], grassOutput[3], RelAbundanceL0[8], RelAbundanceL1[5]; - Bool fillEmptyWithBareGround = swFALSE, warnExtrapolation = swTRUE; + Bool fillEmptyWithBareGround = swTRUE, warnExtrapolation = swTRUE; Bool inNorthHem = swTRUE; if(latitude < 0.0) { @@ -902,7 +903,7 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe // Allocate climate structs' memory allocDeallocClimateStructs(allocate, numYears, &climateOutput, &climateAverages); - calcSiteClimate(SW_Weather.allHist, numYears, startYear, &climateOutput, inNorthHem); + calcSiteClimate(SW_Weather.allHist, numYears, startYear, inNorthHem, &climateOutput); averageClimateAcrossYears(&climateOutput, numYears, &climateAverages); @@ -920,6 +921,8 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe ForEachVegType(k) { vegProd->veg[k].cov.fCover = RelAbundanceL1[k]; } + + vegProd->bare_cov.fCover = RelAbundanceL0[bareGroundIndex]; } // Deallocate climate structs' memory @@ -992,13 +995,12 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT // 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., tempValue; + 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)), - fixBareGround = (Bool) (missing(inputValues[bareGround])), fullVeg = swFALSE, - isGrassIndex = swFALSE; + fullVeg = swFALSE, isGrassIndex = swFALSE, tempShrubBool; // Loop through inputValues and get the total for(index = 0; index < nTypes; index++) { @@ -1007,11 +1009,11 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT } } - if(initialVegSum == 1.) { + if(EQ(initialVegSum, 1.)) { fullVeg = swTRUE; } else if(initialVegSum > 1.) { - LogError(logfp, LOGFATAL, "'estimate_PotNatVeg_composition': ", - "User defined relative abundance values sum to more than ", + LogError(logfp, LOGFATAL, "'estimate_PotNatVeg_composition': " + "User defined relative abundance values sum to more than " "1 = full land cover."); } @@ -1044,8 +1046,8 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT // 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 ", + 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) @@ -1062,7 +1064,8 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT // Check if there is only one grass index to be estimated if(grassEstimSize == 1) { // Set element to SumGrassesFraction - inputSumGrasses - inputValues[grassesEstim[0]] = SumGrassesFraction - inputSumGrasses; + estimCover[grassesEstim[0]] = SumGrassesFraction - inputSumGrasses; + // Set totalSumGrasses to zero totalSumGrasses = 0.; } @@ -1118,192 +1121,197 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT winterMAP += PPTMon_cm[winterMonths[index]]; } } - } - // Set summer and winter precipitations in mm - summerMAP /= PPT_cm; - winterMAP /= PPT_cm; + // Set summer and winter precipitations in mm + summerMAP /= PPT_cm; + winterMAP /= PPT_cm; - // Get the difference between July and Janurary - tempDiffJanJul = fabs(meanTempMon_C[summerMonths[1]] - - meanTempMon_C[winterMonths[1]]); + // Get the difference between July and Janurary + tempDiffJanJul = fabs(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); + 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; - } + 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(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); + 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 && !fullVeg) { - estimCover[shrubIndex] = 0.; - } else { - if(missing(inputValues[shrubIndex]) && !fullVeg) { + // 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 && !fullVeg) { - estimCover[C4Index] = 0; - } else { - if(missing(inputValues[C4Index]) && !fullVeg) { + // 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))); - } + + (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[0]) && !fullVeg) { + // This equations give percent species/vegetation -> use to limit + // Paruelo's C4 equation, i.e., where no C4 species => C4 abundance == 0 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); + (.0086 * (C4Variables[degreeAbove65] * 9 / 5)) + - (8.98 * log(C4Variables[frostFreeDays])) - 22.44) / 100); } + if(EQ(C4Species, 0.)) estimCover[C4Index] = 0; } - 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) - .2383 * 2); - } + // 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); - if(!missing(estimCover[shrubIndex]) && estimCover[shrubIndex] >= shrubLimit) { - if(missing(inputValues[C3Index]) && !fullVeg) { - estimCover[C3Index] = C3Shrubland; + C3Shrubland = cutZeroInf(1.1905 - .02909 * meanTemp_C + + .1781 * log(winterMAP) - .4766); } - } else { - if(missing(inputValues[C3Index]) && !fullVeg) { + + 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) && !fullVeg) { - estimCover[forbIndex] = 0.; - } else { - if(missing(inputValues[forbIndex]) && !fullVeg) { + // 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))); + - (.0623 * log(meanTemp_C))); } - } - // Paruelo & Lauenroth (1996): succulent climate-relationship: - if(tempDiffJanJul <= 0 || winterMAP <= 0) { - estimCover[succIndex] = 0.; - } else { - if(missing(inputValues[succIndex])) { + + // 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)))); + 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.; - } - } + // 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 && totalSumGrasses > 0) { - for(index = 0; index < grassEstimSize; index++) { - estimGrassSum += estimCover[grassesEstim[index]]; - } + if(fixSumGrasses && 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; - - 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 estimGrassSum is 0, make it 1. to prevent dividing by zero + estimGrassSum = (EQ(estimGrassSum, 0.)) ? 1. : estimGrassSum; - 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) { - tempValue = overallEstim[overallEstimSize - 1]; - overallEstim[overallEstimSize - 1] = overallEstim[index]; - overallEstim[index] = tempValue; - overallEstimSize--; + for(index = 0; index < grassEstimSize; index++) { + estimCover[grassesEstim[index]] *= (totalSumGrasses / estimGrassSum); } - } while(index != overallEstimSize - 1 && isGrassIndex); - } - overallEstimSize--; - } + } 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."); + } - // Get final estimated vegetation sum - for(index = 0; index < iFixedSize; index++) { - finalVegSum += estimCover[iFixed[index]]; - } + 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); + } + overallEstimSize--; + } - // 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 - finalVegSum) / estimCoverSum; + // 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]; + } } - } else { - if(fillEmptyWithBareGround && !fixBareGround) { - estimCover[index] = 1.; - for(index = 0; index < nTypes - 1; index++) { - estimCover[index] -= estimCover[index]; + + // 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 && EQ(inputValues[bareGround], 0.0)) { + estimCover[bareGround] = 1.; + for(index = 0; index < nTypes - 1; index++) { + estimCover[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."); + } } - } 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) - // with values from estimated array + for(index = 0; index < nTypes; index++) { + if(!missing(inputValues[index]) && index != bareGround) { + estimCover[index] = inputValues[index]; + } + + RelAbundanceL0[index] = estimCover[index]; + } grassOutput[0] = estimCover[C3Index]; grassOutput[1] = estimCover[C4Index]; @@ -1316,10 +1324,6 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT } } - for(index = 0; index < nTypes; index++) { - RelAbundanceL0[index] = estimCover[index]; - } - RelAbundanceL1[0] = estimCover[treeIndex]; RelAbundanceL1[1] = estimCover[shrubIndex]; RelAbundanceL1[2] = estimCover[forbIndex] + estimCover[succIndex]; From a4e708d9e9793f5e23427fa68997e74ffff8d0ce Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sat, 15 Oct 2022 19:02:12 -0700 Subject: [PATCH 171/326] Added documentation to vegetation unit tests - Added code snippets of equivalent R code of corresponding test - Made the index names constent from NotFullVegetation to FullVegetation - Moved test with "fillEmptyWithBareGround" set to false from NotFullVegetation to FullVegetation --- test/test_SW_VegProd.cc | 255 +++++++++++++++++++++++++++++++--------- 1 file changed, 198 insertions(+), 57 deletions(-) diff --git a/test/test_SW_VegProd.cc b/test/test_SW_VegProd.cc index 1b1c0bfce..37002649e 100644 --- a/test/test_SW_VegProd.cc +++ b/test/test_SW_VegProd.cc @@ -171,7 +171,7 @@ namespace { SW_CLIMATE_CLIM climateAverages; double inputValues[8] = {SW_MISSING, SW_MISSING, SW_MISSING, SW_MISSING, - SW_MISSING, SW_MISSING, SW_MISSING, SW_MISSING}; + 0.0, SW_MISSING, 0.0, 0.0}; double shrubLimit = .2; // Array holding only grass values @@ -185,9 +185,6 @@ namespace { double SumGrassesFraction = SW_MISSING; double C4Variables[3]; - double RelAbundanceL0Expected[8] = {0.0, 0.2608391, 0.4307062, - 0.0, 0.0, 0.3084547, 0.0, 0.0}; - double RelAbundanceL1Expected[5] = {0.0, 0.3084547, 0.2608391, 0.4307062, 0.0}; Bool fillEmptyWithBareGround = swTRUE; Bool warnExtrapolation = swTRUE; @@ -214,6 +211,10 @@ namespace { int grassesIndexL1 = 3; int bareGroundL1 = 4; + double RelAbundanceL0Expected[8] = {0.0, 0.2608391, 0.4307062, + 0.0, 0.0, 0.3084547, 0.0, 0.0}; + double RelAbundanceL1Expected[5] = {0.0, 0.3084547, 0.2608391, 0.4307062, 0.0}; + // Reset "SW_Weather.allHist" SW_WTH_read(); @@ -221,7 +222,7 @@ namespace { allocDeallocClimateStructs(allocate, 31, &climateOutput, &climateAverages); // Calculate climate of the site and add results to "climateOutput" - calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput, inNorthHem); + calcSiteClimate(SW_Weather.allHist, 31, 1980, inNorthHem, &climateOutput); // Average values from "climateOutput" and put them in "climateAverages" averageClimateAcrossYears(&climateOutput, 31, &climateAverages); @@ -237,9 +238,20 @@ namespace { SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, grassOutput, RelAbundanceL0, RelAbundanceL1); - /* ================================== - Test when all input values are "SW_MISSING" - ================================== */ + /* =============================================================== + Test when all input values are "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} + 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"]] + ) + ``` + */ // Loop through RelAbundanceL0 and test results for(index = 0; index < 8; index++) { @@ -255,9 +267,9 @@ namespace { EXPECT_DOUBLE_EQ(grassOutput[1], 0.); EXPECT_DOUBLE_EQ(grassOutput[2], 0.); - /* ================================== - Test with half of input values not "SW_MISSING" - ================================== */ + /* =============================================================== + Test with half of input values not "SW_MISSING" + =============================================================== */ inputValues[succIndex] = .376; inputValues[forbIndex] = SW_MISSING; inputValues[C3Index] = .096; @@ -267,6 +279,21 @@ namespace { inputValues[treeIndex] = .0372; 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} + 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 + ) + ``` + */ + RelAbundanceL0Expected[succIndex] = 0.3760; RelAbundanceL0Expected[forbIndex] = 0.3810; RelAbundanceL0Expected[C3Index] = 0.0960; @@ -301,9 +328,10 @@ namespace { EXPECT_DOUBLE_EQ(grassOutput[1], 0.); EXPECT_DOUBLE_EQ(grassOutput[2], 0.); - /* ================================== - Test with all input values not "SW_MISSING" - ================================== */ + /* =============================================================== + Test with all input values not "SW_MISSING" + =============================================================== */ + inputValues[succIndex] = .1098; inputValues[forbIndex] = .1098; inputValues[C3Index] = .1098; @@ -313,6 +341,25 @@ namespace { 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} + 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 + ) + ``` + */ + RelAbundanceL0Expected[succIndex] = 0.1098; RelAbundanceL0Expected[forbIndex] = 0.1098; RelAbundanceL0Expected[C3Index] = 0.1098; @@ -349,39 +396,31 @@ namespace { EXPECT_NEAR(grassOutput[1], .333333, tol6); EXPECT_NEAR(grassOutput[2], .333333, tol6); - /* ================================== - Test with `fillEmptyWithBareGround` set to false, same input values as previous test - except for bare ground, which is now .2314 - ================================== */ - fillEmptyWithBareGround = swFALSE; - - // Bare ground value is set to have the whole sum of inputValues to equal one - // Otherwise, it would cause a program-stopping error, which is not what is wanted - inputValues[bareGround] = 0.2314; - - estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, - climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, - SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - grassOutput, RelAbundanceL0, RelAbundanceL1); - - // Loop through RelAbundanceL0 and test results. - for(index = 0; index < 8; 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); - } - - EXPECT_NEAR(grassOutput[0], .333333, tol6); - EXPECT_NEAR(grassOutput[1], .333333, tol6); - EXPECT_NEAR(grassOutput[2], .333333, tol6); - - /* ================================== + /* =============================================================== Test with `inNorthHem` to be false, same input values as previous test except for trees and bare ground which are both .0549 - ================================== */ + =============================================================== */ + + /* Expect identical output to rSOILWAT2 (e.g., v5.3.1) + * NOTE: Command uses deprecated estimate_PotNatVeg_composition (rSOILWAT >= v.6.0.0) + ```{r} + 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 = 0.0549, fix_trees = TRUE, + Annuals_Fraction = .1098, fix_annuals = TRUE, + C4_Fraction = .1098, fix_C4grasses = TRUE, + Forbs_Fraction = .1098, fix_forbs = TRUE, + BareGround_Fraction = .0549, fix_BareGround = TRUE, + isNorth = FALSE + ) + ``` + */ + RelAbundanceL0Expected[succIndex] = 0.1098; RelAbundanceL0Expected[forbIndex] = 0.1098; RelAbundanceL0Expected[C3Index] = 0.1098; @@ -422,10 +461,28 @@ namespace { EXPECT_NEAR(grassOutput[1], .333333, tol6); EXPECT_NEAR(grassOutput[2], .333333, tol6); - /* ================================== + /* =============================================================== Test with `SumGrassesFraction` being fixed, all input of previous tests are halved to .0549 - ================================== */ + =============================================================== */ + + /* Expect identical output to rSOILWAT2 (e.g., v5.3.1) + * NOTE: Command uses deprecated estimate_PotNatVeg_composition (rSOILWAT >= v.6.0.0) + ```{r} + 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, + Shrubs_Fraction = .0549, fix_shrubs = TRUE, + Trees_Fraction = .0549, fix_trees = TRUE, + Forbs_Fraction = .0549, fix_forbs = TRUE, + SumGrasses_Fraction = .7255, fix_sumgrasses = TRUE, + BareGround_Fraction = .0549, fix_BareGround = TRUE + ) + ``` + */ + inputValues[succIndex] = .0549; inputValues[forbIndex] = .0549; inputValues[C3Index] = SW_MISSING; @@ -480,8 +537,8 @@ namespace { TEST(EstimateVegetationTest, FullVegetation) { /* ================================================================ - This block of tests deals with input values to - `estimatePotNatVegComposition()` that add up to 1 + This block of tests deals with input values to + `estimatePotNatVegComposition()` that add up to 1 ================================================================ */ SW_CLIMATE_YEARLY climateOutput; @@ -525,10 +582,10 @@ namespace { int bareGround = 7; // RelAbundanceL1 indices - int succIndexL1 = 0; + int treeIndexL1 = 0; int forbIndexL1 = 1; int shrubIndexL1 = 2; - int treeIndexL1 = 3; + int grassesIndexL1 = 3; int bareGroundL1 = 4; // Reset "SW_Weather.allHist" @@ -538,7 +595,7 @@ namespace { allocDeallocClimateStructs(allocate, 31, &climateOutput, &climateAverages); // Calculate climate of the site and add results to "climateOutput" - calcSiteClimate(SW_Weather.allHist, 31, 1980, &climateOutput, inNorthHem); + calcSiteClimate(SW_Weather.allHist, 31, 1980, inNorthHem, &climateOutput); // Average values from "climateOutput" and put them in "climateAverages" averageClimateAcrossYears(&climateOutput, 31, &climateAverages); @@ -568,9 +625,23 @@ namespace { EXPECT_NEAR(grassOutput[1], 0.21367894, tol6); EXPECT_NEAR(grassOutput[2], 0.70093662, tol6); - /* ================================== - Test when a couple input values are not "SW_MISSING" - ================================== */ + /* =============================================================== + Test when a couple input values are not "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} + 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 + ) + ``` + */ + inputValues[succIndex] = .5; inputValues[forbIndex] = SW_MISSING; inputValues[C3Index] = .5; @@ -589,10 +660,10 @@ namespace { RelAbundanceL0Expected[treeIndex] = 0.; RelAbundanceL0Expected[bareGround] = 0.; - RelAbundanceL1Expected[succIndexL1] = 0.; + RelAbundanceL1Expected[treeIndexL1] = 0.; RelAbundanceL1Expected[forbIndexL1] = 0.; RelAbundanceL1Expected[shrubIndexL1] = .5; - RelAbundanceL1Expected[treeIndexL1] = .5; + RelAbundanceL1Expected[grassesIndexL1] = .5; RelAbundanceL1Expected[bareGroundL1] = 0.; estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, @@ -614,6 +685,76 @@ namespace { EXPECT_DOUBLE_EQ(grassOutput[1], 0.); EXPECT_DOUBLE_EQ(grassOutput[2], 0.); + /* =============================================================== + Test with `fillEmptyWithBareGround` set to false, same input values + as previous test except for bare ground, which is now .2314 + =============================================================== */ + + /* Expect identical output to rSOILWAT2 (e.g., v5.3.1) + * NOTE: Command uses deprecated estimate_PotNatVeg_composition (rSOILWAT >= v.6.0.0) + ```{r} + 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 + ) + ``` + */ + + inputValues[succIndex] = 0.1098; + inputValues[forbIndex] = 0.1098; + inputValues[C3Index] = 0.1098; + inputValues[C4Index] = 0.1098; + inputValues[grassAnn] = 0.1098; + inputValues[shrubIndex] = 0.1098; + inputValues[treeIndex] = 0.1098; + inputValues[bareGround] = 0.2314; + + fillEmptyWithBareGround = swFALSE; + + RelAbundanceL0Expected[succIndex] = 0.1098; + RelAbundanceL0Expected[forbIndex] = 0.1098; + RelAbundanceL0Expected[C3Index] = 0.1098; + RelAbundanceL0Expected[C4Index] = 0.1098; + RelAbundanceL0Expected[grassAnn] = 0.1098; + RelAbundanceL0Expected[shrubIndex] = 0.1098; + RelAbundanceL0Expected[treeIndex] = 0.1098; + RelAbundanceL0Expected[bareGround] = 0.2314; + + RelAbundanceL1Expected[treeIndexL1] = 0.1098; + RelAbundanceL1Expected[forbIndexL1] = 0.1098; + RelAbundanceL1Expected[shrubIndexL1] = 0.2196; + RelAbundanceL1Expected[grassesIndexL1] = 0.3294; + RelAbundanceL1Expected[bareGroundL1] = 0.2314; + + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, + grassOutput, RelAbundanceL0, RelAbundanceL1); + + // Loop through RelAbundanceL0 and test results. + for(index = 0; index < 8; 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); + } + + EXPECT_NEAR(grassOutput[0], .333333, tol6); + EXPECT_NEAR(grassOutput[1], .333333, tol6); + EXPECT_NEAR(grassOutput[2], .333333, tol6); + // Deallocate structs allocDeallocClimateStructs(deallocate, 31, &climateOutput, &climateAverages); } From cdbfa8b929a1c6b34e9a1c94d9b3c5e0a483c326 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 16 Oct 2022 17:14:22 -0700 Subject: [PATCH 172/326] Removed "fullVeg" to have ubuntu tests pass --- SW_VegProd.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index 49d2623db..96a85a7bf 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -1000,7 +1000,7 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT estimCover[nTypes], initialVegSum = 0., tempSwapValue, fixedValuesSum = 0; Bool fixSumGrasses = (Bool) (!missing(SumGrassesFraction)), - fullVeg = swFALSE, isGrassIndex = swFALSE, tempShrubBool; + isGrassIndex = swFALSE, tempShrubBool; // Loop through inputValues and get the total for(index = 0; index < nTypes; index++) { @@ -1009,9 +1009,7 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT } } - if(EQ(initialVegSum, 1.)) { - fullVeg = swTRUE; - } else if(initialVegSum > 1.) { + if(initialVegSum > 1.) { LogError(logfp, LOGFATAL, "'estimate_PotNatVeg_composition': " "User defined relative abundance values sum to more than " "1 = full land cover."); From 9f727acdb14697517cf029870a8d93095dcaed7d Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 23 Oct 2022 18:58:20 -0700 Subject: [PATCH 173/326] Fixed C4 related `estimatePotNatVegComposition()` bugs - Added new parameter to `estimatePotNatVegComposition()`: "C4IsList" * rSOILWAT2 uses a test if C4 is a list before using C4 values. This value is set in `estimateVegetationFromClimate()` and default to false * The only way this value will be set to true, is if rSOILWAT2 calls it, the value will not change in SOILWAT2 - Changed "initialVegSum > 1." to "GT(initialVegSum, 1.)" - Replaced call to `fabs()` with `cutZeroInf()` - Fixed when previously a fixed sum of grasses was specified (SumGrassesFraction), it would not be included when fixed values were accounted for --- SW_VegProd.c | 28 +++++++++++++++++----------- SW_VegProd.h | 2 +- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index 96a85a7bf..baad24691 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -894,7 +894,7 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe RelAbundanceL0[8], RelAbundanceL1[5]; Bool fillEmptyWithBareGround = swTRUE, warnExtrapolation = swTRUE; - Bool inNorthHem = swTRUE; + Bool inNorthHem = swTRUE, C4IsList = swFALSE; if(latitude < 0.0) { inNorthHem = swFALSE; @@ -916,7 +916,8 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, coverValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, - inNorthHem, warnExtrapolation, grassOutput, RelAbundanceL0, RelAbundanceL1); + inNorthHem, warnExtrapolation, C4IsList, grassOutput, RelAbundanceL0, + RelAbundanceL1); ForEachVegType(k) { vegProd->veg[k].cov.fCover = RelAbundanceL1[k]; @@ -981,7 +982,7 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe 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, - double *grassOutput, double *RelAbundanceL0, double *RelAbundanceL1) { + Bool C4IsList, double *grassOutput, double *RelAbundanceL0, double *RelAbundanceL1) { const int nTypes = 8; int winterMonths[3], summerMonths[3]; @@ -1009,7 +1010,7 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT } } - if(initialVegSum > 1.) { + if(GT(initialVegSum, 1.)) { LogError(logfp, LOGFATAL, "'estimate_PotNatVeg_composition': " "User defined relative abundance values sum to more than " "1 = full land cover."); @@ -1124,7 +1125,7 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT winterMAP /= PPT_cm; // Get the difference between July and Janurary - tempDiffJanJul = fabs(meanTempMon_C[summerMonths[1]] - + tempDiffJanJul = cutZeroInf(meanTempMon_C[summerMonths[1]] - meanTempMon_C[winterMonths[1]]); if(warnExtrapolation) { @@ -1162,14 +1163,16 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT // This equations give percent species/vegetation -> use to limit // Paruelo's C4 equation, i.e., where no C4 species => C4 abundance == 0 - if(C4Variables[frostFreeDays] <= 0) { - C4Species = 0; - } else { - C4Species = cutZeroInf(((1.6 * (C4Variables[julyMin] * 9 / 5 + 32)) + + if(C4IsList) { + 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; } - if(EQ(C4Species, 0.)) estimCover[C4Index] = 0; } // Paruelo & Lauenroth (1996): C3-grass climate-relationship: @@ -1274,6 +1277,9 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT } } + // Include fixed grass sum if not missing + if(!missing(SumGrassesFraction)) fixedValuesSum += SumGrassesFraction; + // Check if the final estimated vegetation sum is equal to one if(!EQ(finalVegSum, 1.)) { for(index = 0; index < overallEstimSize; index++) { @@ -1284,7 +1290,7 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT estimCover[overallEstim[index]] *= (1 - fixedValuesSum) / estimCoverSum; } } else { - if(fillEmptyWithBareGround && EQ(inputValues[bareGround], 0.0)) { + if(fillEmptyWithBareGround && EQ(inputValues[bareGround], 0.)) { estimCover[bareGround] = 1.; for(index = 0; index < nTypes - 1; index++) { estimCover[bareGround] -= estimCover[index]; diff --git a/SW_VegProd.h b/SW_VegProd.h index 524620abc..717c8c82d 100644 --- a/SW_VegProd.h +++ b/SW_VegProd.h @@ -269,7 +269,7 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe 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, - double *grassOutput, double *RelAbundanceL0, double *RelAbundanceL1); + Bool C4IsList, double *grassOutput, double *RelAbundanceL0, double *RelAbundanceL1); double cutZeroInf(double value); void uniqueIndices(int arrayOne[], int arrayTwo[], int arrayOneSize, int arrayTwoSize, int *finalIndexArray, int *finalIndexArraySize); From 639984035b3a9fa215912e9af1da1272aae2a3a2 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 23 Oct 2022 19:57:27 -0700 Subject: [PATCH 174/326] Added to vegetation tests and documentation - Added to R code snippets to include a command to get "clim1" - Created three new tests: 1) Another southern hemisphere test, but this has different values between the northern and southern hemisphere 2) Test of `estimateVegetationFromClimate()` when "veg_method" is set to 1 3) A death test of `estimatePotNatVegComposition()` when input cover sums to greater than 1 - Swapped "EXPECT_NEAR()" for "EXPECT_DOULBLE_EQ" for most. Not all tests will accept "EXPECT_NEAR()" because of the precision of doubles - Updated "NotFullVegetation" and "FullVegetation" section headers - New southern hemisphere tests use a tolerance of tol3. This is due to double precision in C. * Noticeable differences are prominent mainly when calculating the southern hemisphere because when calculating "winterMAP" the difference is: C - .165390 and R - .1653897, "summerMAP" shows no substantial difference * Due to the precision difference, values calculated with "winterMAP" are noticeably different as well (e.g., using the same winterMAP above, Forbs: C - .228046, R - .22804838) * With higher precision on "winterMAP", the exact same values would be achieved --- test/test_SW_VegProd.cc | 216 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 196 insertions(+), 20 deletions(-) diff --git a/test/test_SW_VegProd.cc b/test/test_SW_VegProd.cc index 37002649e..ce421da57 100644 --- a/test/test_SW_VegProd.cc +++ b/test/test_SW_VegProd.cc @@ -162,8 +162,11 @@ namespace { TEST(EstimateVegetationTest, NotFullVegetation) { /* ================================================================ - This block of tests deals with input values to - `estimatePotNatVegComposition()` that do not add up to 1 + 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 ================================================================ */ @@ -245,7 +248,10 @@ namespace { /* Expect identical output to rSOILWAT2 (e.g., v5.3.1) * NOTE: Command uses deprecated estimate_PotNatVeg_composition (rSOILWAT >= v.6.0.0) ```{r} - rSOILWAT2::estimate_PotNatVeg_composition_old( + 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"]] @@ -282,7 +288,10 @@ namespace { /* Expect identical output to rSOILWAT2 (e.g., v5.3.1) * NOTE: Command uses deprecated estimate_PotNatVeg_composition (rSOILWAT >= v.6.0.0) ```{r} - rSOILWAT2::estimate_PotNatVeg_composition_old( + 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"]], @@ -316,12 +325,12 @@ namespace { // Loop through RelAbundanceL0 and test results for(index = 0; index < 8; index++) { - EXPECT_NEAR(RelAbundanceL0[index], RelAbundanceL0Expected[index], tol6); + EXPECT_DOUBLE_EQ(RelAbundanceL0[index], RelAbundanceL0Expected[index]); } // Loop through RelAbundanceL1 and test results for(index = 0; index < 5; index++) { - EXPECT_NEAR(RelAbundanceL1[index], RelAbundanceL1Expected[index], tol6); + EXPECT_DOUBLE_EQ(RelAbundanceL1[index], RelAbundanceL1Expected[index]); } EXPECT_DOUBLE_EQ(grassOutput[0], 1.); @@ -344,7 +353,10 @@ namespace { /* Expect identical output to rSOILWAT2 (e.g., v5.3.1) * NOTE: Command uses deprecated estimate_PotNatVeg_composition (rSOILWAT >= v.6.0.0) ```{r} - rSOILWAT2::estimate_PotNatVeg_composition_old( + 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"]], @@ -385,11 +397,12 @@ namespace { // than the other values (in this case, .2314) for(index = 0; index < 8; index++) { EXPECT_NEAR(RelAbundanceL0[index], RelAbundanceL0Expected[index], tol6); + EXPECT_DOUBLE_EQ(RelAbundanceL0[index], RelAbundanceL0Expected[index]); } // Loop through RelAbundanceL1 and test results for(index = 0; index < 5; index++) { - EXPECT_NEAR(RelAbundanceL1[index], RelAbundanceL1Expected[index], tol6); + EXPECT_DOUBLE_EQ(RelAbundanceL1[index], RelAbundanceL1Expected[index]); } EXPECT_NEAR(grassOutput[0], .333333, tol6); @@ -397,24 +410,33 @@ namespace { EXPECT_NEAR(grassOutput[2], .333333, tol6); /* =============================================================== - Test with `inNorthHem` to be false, same input values as previous test - except for trees and bare ground which are both .0549 + Test with `inNorthHem` to be false with two tests: + + 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 =============================================================== */ /* Expect identical output to rSOILWAT2 (e.g., v5.3.1) * NOTE: Command uses deprecated estimate_PotNatVeg_composition (rSOILWAT >= v.6.0.0) ```{r} - rSOILWAT2::estimate_PotNatVeg_composition_old( + 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, + 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, - Annuals_Fraction = .1098, fix_annuals = TRUE, - C4_Fraction = .1098, fix_C4grasses = TRUE, - Forbs_Fraction = .1098, fix_forbs = TRUE, BareGround_Fraction = .0549, fix_BareGround = TRUE, isNorth = FALSE ) @@ -449,18 +471,76 @@ namespace { // Loop through RelAbundanceL0 and test results. for(index = 0; index < 8; index++) { - EXPECT_NEAR(RelAbundanceL0[index], RelAbundanceL0Expected[index], tol6); + EXPECT_DOUBLE_EQ(RelAbundanceL0[index], RelAbundanceL0Expected[index]); } // Loop through RelAbundanceL1 and test results for(index = 0; index < 5; index++) { - EXPECT_NEAR(RelAbundanceL1[index], RelAbundanceL1Expected[index], tol6); + EXPECT_DOUBLE_EQ(RelAbundanceL1[index], RelAbundanceL1Expected[index]); } EXPECT_NEAR(grassOutput[0], .333333, tol6); EXPECT_NEAR(grassOutput[1], .333333, tol6); EXPECT_NEAR(grassOutput[2], .333333, tol6); + /* Expect similar 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"]], + isNorth = FALSE + ) + ``` + */ + + 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.; + + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, + C4IsList, grassOutput, RelAbundanceL0, RelAbundanceL1); + + RelAbundanceL0Expected[succIndex] = 0.; + RelAbundanceL0Expected[forbIndex] = .228048; + RelAbundanceL0Expected[C3Index] = .525755; + RelAbundanceL0Expected[C4Index] = .157662; + RelAbundanceL0Expected[grassAnn] = 0.; + RelAbundanceL0Expected[shrubIndex] = .088534; + RelAbundanceL0Expected[treeIndex] = 0.; + RelAbundanceL0Expected[bareGround] = 0.; + + RelAbundanceL1Expected[treeIndexL1] = 0.; + RelAbundanceL1Expected[shrubIndexL1] = .088534; + RelAbundanceL1Expected[forbIndexL1] = .228048; // Constains forbs + succulents (L0) + RelAbundanceL1Expected[grassesIndexL1] = .683417; + RelAbundanceL1Expected[bareGroundL1] = 0.; + + // Loop through RelAbundanceL0 and test results. + for(index = 0; index < nTypes; index++) { + EXPECT_NEAR(RelAbundanceL0[index], RelAbundanceL0Expected[index], tol3); + } + + // Loop through RelAbundanceL1 and test results + for(index = 0; index < 5; index++) { + EXPECT_NEAR(RelAbundanceL1[index], RelAbundanceL1Expected[index], tol3); + } + + EXPECT_NEAR(grassOutput[0], .769303, tol3); + EXPECT_NEAR(grassOutput[1], .230696, tol3); + EXPECT_DOUBLE_EQ(grassOutput[2], 0.); + /* =============================================================== Test with `SumGrassesFraction` being fixed, all input of previous tests are halved to .0549 @@ -469,7 +549,10 @@ namespace { /* Expect identical output to rSOILWAT2 (e.g., v5.3.1) * NOTE: Command uses deprecated estimate_PotNatVeg_composition (rSOILWAT >= v.6.0.0) ```{r} - rSOILWAT2::estimate_PotNatVeg_composition_old( + clim1 <- calc_SiteClimate(weatherList = + rSOILWAT2::get_WeatherHistory(rSOILWAT2::sw_exampleData), do_C4vars = TRUE) + + rSOILWAT2::estimate_PotNatVeg_composition( MAP_mm = 10 * clim1[["MAP_cm"]], MAT_C = clim1[["MAT_C"]], mean_monthly_ppt_mm = 10 * clim1[["meanMonthlyPPTcm"]], mean_monthly_Temp_C = clim1[["meanMonthlyTempC"]], @@ -519,11 +602,16 @@ namespace { // Loop through RelAbundanceL0 and test results. for(index = 0; index < 8; index++) { EXPECT_NEAR(RelAbundanceL0[index], RelAbundanceL0Expected[index], tol6); + EXPECT_DOUBLE_EQ(RelAbundanceL0[index], RelAbundanceL0Expected[index]); } // Loop through RelAbundanceL1 and test results for(index = 0; index < 5; index++) { - EXPECT_NEAR(RelAbundanceL1[index], RelAbundanceL1Expected[index], tol6); + if(index != grassesIndexL1) { + EXPECT_DOUBLE_EQ(RelAbundanceL1[index], RelAbundanceL1Expected[index]); + } else { + EXPECT_NEAR(RelAbundanceL1[grassesIndexL1], SumGrassesFraction, tol6); + } } EXPECT_NEAR(grassOutput[0], 1., tol6); @@ -539,6 +627,9 @@ namespace { /* ================================================================ 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; @@ -632,7 +723,10 @@ namespace { /* Expect identical output to rSOILWAT2 (e.g., v5.3.1) * NOTE: Command uses deprecated estimate_PotNatVeg_composition (rSOILWAT >= v.6.0.0) ```{r} - rSOILWAT2::estimate_PotNatVeg_composition_old( + 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"]], @@ -693,7 +787,10 @@ namespace { /* Expect identical output to rSOILWAT2 (e.g., v5.3.1) * NOTE: Command uses deprecated estimate_PotNatVeg_composition (rSOILWAT >= v.6.0.0) ```{r} - rSOILWAT2::estimate_PotNatVeg_composition_old( + 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"]], @@ -757,6 +854,85 @@ namespace { // Deallocate structs allocDeallocClimateStructs(deallocate, 31, &climateOutput, &climateAverages); + + /* =============================================================== + 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"]], + isNorth = FALSE + ) + ``` + */ + + RelAbundanceL1Expected[treeIndexL1] = 0.; + RelAbundanceL1Expected[shrubIndexL1] = .3084547; + RelAbundanceL1Expected[forbIndexL1] = .2608391; // Constains forbs + succulents (L0) + RelAbundanceL1Expected[grassesIndexL1] = .4307062; + RelAbundanceL1Expected[bareGroundL1] = 0.; + + RelAbundanceL0Expected[bareGround] = 0.; + + estimateVegetationFromClimate(&vegProd, startYear, endYear, + veg_method, latitude); + + // Loop through RelAbundanceL1 and test results + for(index = 0; index < 5; index++) { + EXPECT_NEAR(vegProd.veg[index].cov.fCover, RelAbundanceL1Expected[index], tol6); + } + + EXPECT_NEAR(vegProd.bare_cov.fCover, RelAbundanceL0Expected[bareGround], tol6); + } + + TEST(VegEstimationDeathTest, VegInputGreaterThanOne) { + + /* ================================================================ + Tests a death case of `estimatePotNatVegComposition()` + when input vegetation values sum to over 1 + ================================================================ */ + + SW_CLIMATE_CLIM climateAverages; + + double SumGrassesFraction = SW_MISSING; + double C4Variables[3]; + + Bool fillEmptyWithBareGround = swTRUE; + Bool inNorthHem = swTRUE; + Bool warnExtrapolation = swTRUE; + Bool C4IsList = swFALSE; + + 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 + + EXPECT_DEATH_IF_SUPPORTED( + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, + C4IsList, grassOutput, RelAbundanceL0, RelAbundanceL1);, + "" + ); + } } // namespace From 16221f6e048dc24e7a2768787c0d2e3d443c8137 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 23 Oct 2022 20:03:59 -0700 Subject: [PATCH 175/326] Updated values and function calls in tests - Updated `estimatePotNatVegComposition()` call with new parameter - Modified numbers do not have a preceding 0 unless the number is 0 in which case, is now "0." to be consistent - Fixed forb and shrub index inaccuracy, with them being swapped before - Made utilization of "nTypes" more common instead of "8" in loops - Made qualifying parts of tests use "EXPECT_DOUBLE_EQ" instead of "EXPECT_NEAR" --- test/test_SW_VegProd.cc | 228 +++++++++++++++++++++------------------- 1 file changed, 122 insertions(+), 106 deletions(-) diff --git a/test/test_SW_VegProd.cc b/test/test_SW_VegProd.cc index ce421da57..c54d59399 100644 --- a/test/test_SW_VegProd.cc +++ b/test/test_SW_VegProd.cc @@ -192,7 +192,9 @@ namespace { Bool fillEmptyWithBareGround = swTRUE; Bool warnExtrapolation = swTRUE; Bool inNorthHem = swTRUE; + Bool C4IsList = swFALSE; + int nTypes = 8; int deallocate = 0; int allocate = 1; int index; @@ -209,8 +211,8 @@ namespace { // RelAbundanceL1 indices int treeIndexL1 = 0; - int forbIndexL1 = 1; - int shrubIndexL1 = 2; + int shrubIndexL1 = 1; + int forbIndexL1 = 2; int grassesIndexL1 = 3; int bareGroundL1 = 4; @@ -239,7 +241,7 @@ namespace { estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - grassOutput, RelAbundanceL0, RelAbundanceL1); + C4IsList, grassOutput, RelAbundanceL0, RelAbundanceL1); /* =============================================================== Test when all input values are "SW_MISSING" @@ -260,7 +262,7 @@ namespace { */ // Loop through RelAbundanceL0 and test results - for(index = 0; index < 8; index++) { + for(index = 0; index < nTypes; index++) { EXPECT_NEAR(RelAbundanceL0[index], RelAbundanceL0Expected[index], tol6); } @@ -303,26 +305,25 @@ namespace { ``` */ - RelAbundanceL0Expected[succIndex] = 0.3760; - RelAbundanceL0Expected[forbIndex] = 0.3810; - RelAbundanceL0Expected[C3Index] = 0.0960; - RelAbundanceL0Expected[C4Index] = 0.0000; - RelAbundanceL0Expected[grassAnn] = 0.0000; - RelAbundanceL0Expected[shrubIndex] = 0.1098; - RelAbundanceL0Expected[treeIndex] = 0.0372; - RelAbundanceL0Expected[bareGround] = 0.0000; - - RelAbundanceL1Expected[treeIndexL1] = 0.0372; - RelAbundanceL1Expected[forbIndexL1] = 0.1098; - RelAbundanceL1Expected[shrubIndexL1] = 0.7570; - RelAbundanceL1Expected[grassesIndexL1] = 0.0960; - RelAbundanceL1Expected[bareGroundL1] = 0.0000; + RelAbundanceL0Expected[succIndex] = .3760; + RelAbundanceL0Expected[forbIndex] = .3810; + RelAbundanceL0Expected[C3Index] = .0960; + RelAbundanceL0Expected[C4Index] = 0.; + RelAbundanceL0Expected[grassAnn] = 0.; + RelAbundanceL0Expected[shrubIndex] = .1098; + RelAbundanceL0Expected[treeIndex] = .0372; + RelAbundanceL0Expected[bareGround] = 0.; + + RelAbundanceL1Expected[treeIndexL1] = .0372; + RelAbundanceL1Expected[shrubIndexL1] = .1098; + RelAbundanceL1Expected[forbIndexL1] = .7570; // Constains forbs + succulents (L0) + RelAbundanceL1Expected[grassesIndexL1] = .0960; + RelAbundanceL1Expected[bareGroundL1] = 0.; estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - grassOutput, RelAbundanceL0, RelAbundanceL1); - + C4IsList, grassOutput, RelAbundanceL0, RelAbundanceL1); // Loop through RelAbundanceL0 and test results for(index = 0; index < 8; index++) { EXPECT_DOUBLE_EQ(RelAbundanceL0[index], RelAbundanceL0Expected[index]); @@ -372,31 +373,33 @@ namespace { ``` */ - RelAbundanceL0Expected[succIndex] = 0.1098; - RelAbundanceL0Expected[forbIndex] = 0.1098; - RelAbundanceL0Expected[C3Index] = 0.1098; - RelAbundanceL0Expected[C4Index] = 0.1098; - RelAbundanceL0Expected[grassAnn] = 0.1098; - RelAbundanceL0Expected[shrubIndex] = 0.1098; - RelAbundanceL0Expected[treeIndex] = 0.1098; + RelAbundanceL0Expected[succIndex] = .1098; + RelAbundanceL0Expected[forbIndex] = .1098; + RelAbundanceL0Expected[C3Index] = .1098; + RelAbundanceL0Expected[C4Index] = .1098; + RelAbundanceL0Expected[grassAnn] = .1098; + RelAbundanceL0Expected[shrubIndex] = .1098; + RelAbundanceL0Expected[treeIndex] = .1098; + + // RelAbundanceL0Expected[bareGround] is not .1098 because + // fillEmptyWithBareGround = swTRUE RelAbundanceL0Expected[bareGround] = 0.2314; - RelAbundanceL1Expected[treeIndexL1] = 0.1098; - RelAbundanceL1Expected[forbIndexL1] = 0.1098; - RelAbundanceL1Expected[shrubIndexL1] = 0.2196; - RelAbundanceL1Expected[grassesIndexL1] = 0.3294; - RelAbundanceL1Expected[bareGroundL1] = 0.2314; + RelAbundanceL1Expected[treeIndexL1] = .1098; + RelAbundanceL1Expected[shrubIndexL1] = .1098; + RelAbundanceL1Expected[forbIndexL1] = .2196; // Constains forbs + succulents (L0) + RelAbundanceL1Expected[grassesIndexL1] = .3294; + RelAbundanceL1Expected[bareGroundL1] = .2314; estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - grassOutput, RelAbundanceL0, RelAbundanceL1); + C4IsList, grassOutput, RelAbundanceL0, RelAbundanceL1); // Loop through RelAbundanceL0 and test results. Since initial values // do not add to one and we fill empty with bare ground, bare ground should be higher // than the other values (in this case, .2314) - for(index = 0; index < 8; index++) { - EXPECT_NEAR(RelAbundanceL0[index], RelAbundanceL0Expected[index], tol6); + for(index = 0; index < nTypes; index++) { EXPECT_DOUBLE_EQ(RelAbundanceL0[index], RelAbundanceL0Expected[index]); } @@ -443,31 +446,31 @@ namespace { ``` */ - RelAbundanceL0Expected[succIndex] = 0.1098; - RelAbundanceL0Expected[forbIndex] = 0.1098; - RelAbundanceL0Expected[C3Index] = 0.1098; - RelAbundanceL0Expected[C4Index] = 0.1098; - RelAbundanceL0Expected[grassAnn] = 0.1098; - RelAbundanceL0Expected[shrubIndex] = 0.1098; - RelAbundanceL0Expected[treeIndex] = 0.0549; - RelAbundanceL0Expected[bareGround] = 0.2863; - - RelAbundanceL1Expected[treeIndexL1] = 0.0549; - RelAbundanceL1Expected[forbIndexL1] = 0.1098; - RelAbundanceL1Expected[shrubIndexL1] = 0.2196; - RelAbundanceL1Expected[grassesIndexL1] = 0.3294; - RelAbundanceL1Expected[bareGroundL1] = 0.2863; - inNorthHem = swFALSE; fillEmptyWithBareGround = swTRUE; inputValues[treeIndex] = .0549; inputValues[bareGround] = .0549; + RelAbundanceL0Expected[succIndex] = .1098; + RelAbundanceL0Expected[forbIndex] = .1098; + RelAbundanceL0Expected[C3Index] = .1098; + RelAbundanceL0Expected[C4Index] = .1098; + RelAbundanceL0Expected[grassAnn] = .1098; + RelAbundanceL0Expected[shrubIndex] = .1098; + RelAbundanceL0Expected[treeIndex] = .0549; + RelAbundanceL0Expected[bareGround] = .2863; + + RelAbundanceL1Expected[treeIndexL1] = .0549; + RelAbundanceL1Expected[shrubIndexL1] = .1098; + RelAbundanceL1Expected[forbIndexL1] = .2196; // Constains forbs + succulents (L0) + RelAbundanceL1Expected[grassesIndexL1] = .3294; + RelAbundanceL1Expected[bareGroundL1] = .2863; + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - grassOutput, RelAbundanceL0, RelAbundanceL1); + C4IsList, grassOutput, RelAbundanceL0, RelAbundanceL1); // Loop through RelAbundanceL0 and test results. for(index = 0; index < 8; index++) { @@ -577,18 +580,18 @@ namespace { RelAbundanceL0Expected[succIndex] = .0549; RelAbundanceL0Expected[forbIndex] = .0549; - RelAbundanceL0Expected[C3Index] = 0.7255; - RelAbundanceL0Expected[C4Index] = 0.0; - RelAbundanceL0Expected[grassAnn] = 0.0; + RelAbundanceL0Expected[C3Index] = .7255; + RelAbundanceL0Expected[C4Index] = 0.; + RelAbundanceL0Expected[grassAnn] = 0.; RelAbundanceL0Expected[shrubIndex] = .0549; RelAbundanceL0Expected[treeIndex] = .0549; - RelAbundanceL0Expected[bareGround] = 0.0549; + RelAbundanceL0Expected[bareGround] = .0549; RelAbundanceL1Expected[treeIndexL1] = .0549; - RelAbundanceL1Expected[forbIndexL1] = .0549; - RelAbundanceL1Expected[shrubIndexL1] = 0.1098; - RelAbundanceL1Expected[grassesIndexL1] = 0.7255; - RelAbundanceL1Expected[bareGroundL1] = 0.0549; + RelAbundanceL1Expected[shrubIndexL1] = .0549; + RelAbundanceL1Expected[forbIndexL1] = .1098; // Constains forbs + succulents (L0) + RelAbundanceL1Expected[grassesIndexL1] = .7255; + RelAbundanceL1Expected[bareGroundL1] = .0549; fillEmptyWithBareGround = swTRUE; inNorthHem = swTRUE; @@ -597,11 +600,10 @@ namespace { estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - grassOutput, RelAbundanceL0, RelAbundanceL1); + C4IsList, grassOutput, RelAbundanceL0, RelAbundanceL1); // Loop through RelAbundanceL0 and test results. - for(index = 0; index < 8; index++) { - EXPECT_NEAR(RelAbundanceL0[index], RelAbundanceL0Expected[index], tol6); + for(index = 0; index < nTypes; index++) { EXPECT_DOUBLE_EQ(RelAbundanceL0[index], RelAbundanceL0Expected[index]); } @@ -614,9 +616,10 @@ namespace { } } - EXPECT_NEAR(grassOutput[0], 1., tol6); - EXPECT_NEAR(grassOutput[1], 0.0, tol6); - EXPECT_NEAR(grassOutput[2], 0.0, tol6); + + EXPECT_DOUBLE_EQ(grassOutput[0], 1.); + EXPECT_DOUBLE_EQ(grassOutput[1], 0.); + EXPECT_DOUBLE_EQ(grassOutput[2], 0.); // Deallocate structs allocDeallocClimateStructs(deallocate, 31, &climateOutput, &climateAverages); @@ -634,6 +637,14 @@ namespace { SW_CLIMATE_YEARLY climateOutput; SW_CLIMATE_CLIM climateAverages; + SW_VEGPROD vegProd; + + int index; + int nTypes = 8; + int startYear = 1980; + int endYear = 2010; + int veg_method = 1; + double latitude = 90.0; double inputValues[8] = {.0567, .2317, .0392, .0981, .3218, .0827, .1293, .0405}; @@ -650,17 +661,17 @@ namespace { double SumGrassesFraction = SW_MISSING; double C4Variables[3]; - double RelAbundanceL0Expected[8] = {0.0567, 0.2317, .0392, - .0981, .3218, .0827, .1293, .0405}; + double RelAbundanceL0Expected[8]; + double RelAbundanceL1Expected[5] = {.1293, .0827, .2884, .4591, .0405}; Bool fillEmptyWithBareGround = swTRUE; Bool inNorthHem = swTRUE; Bool warnExtrapolation = swTRUE; + Bool C4IsList = swFALSE; int deallocate = 0; int allocate = 1; - int index; // RelAbundanceL0 and inputValues indices int succIndex = 0; @@ -674,11 +685,17 @@ namespace { // RelAbundanceL1 indices int treeIndexL1 = 0; - int forbIndexL1 = 1; - int shrubIndexL1 = 2; + int shrubIndexL1 = 1; + int forbIndexL1 = 2; int grassesIndexL1 = 3; int bareGroundL1 = 4; + // Transfer input values to RelAbundanceL0Expected since values + // are expected to be the same + for(index = 0; index < nTypes; index++) { + RelAbundanceL0Expected[index] = inputValues[index]; + } + // Reset "SW_Weather.allHist" SW_WTH_read(); @@ -700,21 +717,21 @@ namespace { estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - grassOutput, RelAbundanceL0, RelAbundanceL1); + C4IsList, grassOutput, RelAbundanceL0, RelAbundanceL1); // All values in "RelAbundanceL0" should be exactly the same as "inputValues" - for(index = 0; index < 8; index++) { - EXPECT_NEAR(RelAbundanceL0[index], RelAbundanceL0Expected[index], tol6); + for(index = 0; index < nTypes; index++) { + EXPECT_DOUBLE_EQ(RelAbundanceL0[index], inputValues[index]); } // All values in "RelAbundanceL1" should be exactly the same as "inputValues" for(index = 0; index < 5; index++) { - EXPECT_NEAR(RelAbundanceL1[index], RelAbundanceL1Expected[index], tol6); + EXPECT_DOUBLE_EQ(RelAbundanceL1[index], RelAbundanceL1Expected[index]); } - EXPECT_NEAR(grassOutput[0], 0.08538445, tol6); - EXPECT_NEAR(grassOutput[1], 0.21367894, tol6); - EXPECT_NEAR(grassOutput[2], 0.70093662, tol6); + EXPECT_NEAR(grassOutput[0], .085384, tol6); + EXPECT_NEAR(grassOutput[1], .213678, tol6); + EXPECT_NEAR(grassOutput[2], .700936, tol6); /* =============================================================== Test when a couple input values are not "SW_MISSING" @@ -755,18 +772,17 @@ namespace { RelAbundanceL0Expected[bareGround] = 0.; RelAbundanceL1Expected[treeIndexL1] = 0.; - RelAbundanceL1Expected[forbIndexL1] = 0.; - RelAbundanceL1Expected[shrubIndexL1] = .5; + RelAbundanceL1Expected[shrubIndexL1] = 0.; + RelAbundanceL1Expected[forbIndexL1] = .5; // Constains forbs + succulents (L0) RelAbundanceL1Expected[grassesIndexL1] = .5; RelAbundanceL1Expected[bareGroundL1] = 0.; estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - grassOutput, RelAbundanceL0, RelAbundanceL1); - + C4IsList, grassOutput, RelAbundanceL0, RelAbundanceL1); // Loop through RelAbundanceL0 and test results - for(index = 0; index < 8; index++) { + for(index = 0; index < nTypes; index++) { EXPECT_DOUBLE_EQ(RelAbundanceL0[index], RelAbundanceL0Expected[index]); } @@ -807,39 +823,39 @@ namespace { ``` */ - inputValues[succIndex] = 0.1098; - inputValues[forbIndex] = 0.1098; - inputValues[C3Index] = 0.1098; - inputValues[C4Index] = 0.1098; - inputValues[grassAnn] = 0.1098; - inputValues[shrubIndex] = 0.1098; - inputValues[treeIndex] = 0.1098; - inputValues[bareGround] = 0.2314; + inputValues[succIndex] = .1098; + inputValues[forbIndex] = .1098; + inputValues[C3Index] = .1098; + inputValues[C4Index] = .1098; + inputValues[grassAnn] = .1098; + inputValues[shrubIndex] = .1098; + inputValues[treeIndex] = .1098; + inputValues[bareGround] = .2314; fillEmptyWithBareGround = swFALSE; - RelAbundanceL0Expected[succIndex] = 0.1098; - RelAbundanceL0Expected[forbIndex] = 0.1098; - RelAbundanceL0Expected[C3Index] = 0.1098; - RelAbundanceL0Expected[C4Index] = 0.1098; - RelAbundanceL0Expected[grassAnn] = 0.1098; - RelAbundanceL0Expected[shrubIndex] = 0.1098; - RelAbundanceL0Expected[treeIndex] = 0.1098; - RelAbundanceL0Expected[bareGround] = 0.2314; - - RelAbundanceL1Expected[treeIndexL1] = 0.1098; - RelAbundanceL1Expected[forbIndexL1] = 0.1098; - RelAbundanceL1Expected[shrubIndexL1] = 0.2196; - RelAbundanceL1Expected[grassesIndexL1] = 0.3294; - RelAbundanceL1Expected[bareGroundL1] = 0.2314; + RelAbundanceL0Expected[succIndex] = .1098; + RelAbundanceL0Expected[forbIndex] = .1098; + RelAbundanceL0Expected[C3Index] = .1098; + RelAbundanceL0Expected[C4Index] = .1098; + RelAbundanceL0Expected[grassAnn] = .1098; + RelAbundanceL0Expected[shrubIndex] = .1098; + RelAbundanceL0Expected[treeIndex] = .1098; + RelAbundanceL0Expected[bareGround] = .2314; + + RelAbundanceL1Expected[treeIndexL1] = .1098; + RelAbundanceL1Expected[shrubIndexL1] = .1098; + RelAbundanceL1Expected[forbIndexL1] = .2196; // Constains forbs + succulents (L0) + RelAbundanceL1Expected[grassesIndexL1] = .3294; + RelAbundanceL1Expected[bareGroundL1] = .2314; estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - grassOutput, RelAbundanceL0, RelAbundanceL1); + C4IsList, grassOutput, RelAbundanceL0, RelAbundanceL1); // Loop through RelAbundanceL0 and test results. - for(index = 0; index < 8; index++) { + for(index = 0; index < nTypes; index++) { EXPECT_NEAR(RelAbundanceL0[index], RelAbundanceL0Expected[index], tol6); } From 076e7420febc3c10b87b6e5fa6dbdde75b3aa591 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 23 Oct 2022 20:35:27 -0700 Subject: [PATCH 176/326] Fixed uninitialization warning - Newest death test had the chance to throw an error for not guaranteeing certain variables would be initialized --- test/test_SW_VegProd.cc | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/test/test_SW_VegProd.cc b/test/test_SW_VegProd.cc index c54d59399..d88de5bb7 100644 --- a/test/test_SW_VegProd.cc +++ b/test/test_SW_VegProd.cc @@ -904,7 +904,7 @@ namespace { veg_method, latitude); // Loop through RelAbundanceL1 and test results - for(index = 0; index < 5; index++) { + for(index = 0; index < 4; index++) { EXPECT_NEAR(vegProd.veg[index].cov.fCover, RelAbundanceL1Expected[index], tol6); } @@ -919,6 +919,7 @@ namespace { ================================================================ */ SW_CLIMATE_CLIM climateAverages; + SW_CLIMATE_YEARLY climateOutput; double SumGrassesFraction = SW_MISSING; double C4Variables[3]; @@ -928,6 +929,9 @@ namespace { Bool warnExtrapolation = swTRUE; Bool C4IsList = swFALSE; + int allocate = 1; + int deallocate = 0; + double inputValues[8] = {.0567, .5, .0392, .0981, .3218, .0827, .1293, .0405}; double shrubLimit = .2; @@ -941,6 +945,18 @@ namespace { // Array holding all values from estimation minus grasses double RelAbundanceL1[5]; // 5 = Number of types minus grasses + // Reset "SW_Weather.allHist" + SW_WTH_read(); + + // Allocate arrays needed for `calcSiteClimate()` and `averageClimateAcrossYears()` + allocDeallocClimateStructs(allocate, 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); + EXPECT_DEATH_IF_SUPPORTED( estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, @@ -948,6 +964,7 @@ namespace { C4IsList, grassOutput, RelAbundanceL0, RelAbundanceL1);, "" ); + allocDeallocClimateStructs(deallocate, 31, &climateOutput, &climateAverages); } From 7df7092da4e9ca8e57ab1834259ca703ffcfb56a Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 6 Nov 2022 20:37:41 -0700 Subject: [PATCH 177/326] Fixed more bugs in `estimatePotNatVegComposition()` - Grasses were handled differently in value-setting and calculations than originally thought, now if the sum of grasses is fixed: * If one or more grasses (C3, C4, annuals) are fixed: - The final grass sum value is based on fixed grass values (C3, C4, annuals) - Fixed input grass values are subtracted from "fixedValuesSum" if not SW_MISSING * If zero grass values are fixed: - The final grass sum value is that of the fixed input sum of grasses (SumGrassFraction) - Lines 1241 to 1253 in SW_VegProd.c were incorrectly indented and connected to an if-else block - Moved bare ground calculations from "estimCover" array to "inputValues" array - Added new parameter to `estimatePotNatVegComposition()`, "fixBareGround" * Going off this function's counterpart on the R side, information about if the bare ground was fixed is needed, but hard to determine on C side due to the default value being 0.0 instead of SW_MISSING * "fixBareGround" relieves this difficulty since it is not changed unless called from rSOILWAT * Default value set to swTRUE - Added documentation for "C4IsList" and "fixBareGround" for function `estimatePotNatVegComposition()` --- SW_VegProd.c | 97 ++++++++++++++++++++++++++++++++++++---------------- SW_VegProd.h | 3 +- 2 files changed, 69 insertions(+), 31 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index baad24691..879c6cf02 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -895,6 +895,7 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe Bool fillEmptyWithBareGround = swTRUE, warnExtrapolation = swTRUE; Bool inNorthHem = swTRUE, C4IsList = swFALSE; + Bool fixBareGround = swTRUE; if(latitude < 0.0) { inNorthHem = swFALSE; @@ -916,8 +917,8 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, coverValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, - inNorthHem, warnExtrapolation, C4IsList, grassOutput, RelAbundanceL0, - RelAbundanceL1); + inNorthHem, warnExtrapolation, C4IsList, fixBareGround, grassOutput, + RelAbundanceL0, RelAbundanceL1); ForEachVegType(k) { vegProd->veg[k].cov.fCover = RelAbundanceL1[k]; @@ -968,6 +969,8 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe @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] C4IsList Bool value specifying if C4 is a list (false when not running rSOILWAT2) + @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 @@ -982,7 +985,8 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe 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 C4IsList, double *grassOutput, double *RelAbundanceL0, double *RelAbundanceL1) { + Bool C4IsList, Bool fixBareGround, double *grassOutput, double *RelAbundanceL0, + double *RelAbundanceL1) { const int nTypes = 8; int winterMonths[3], summerMonths[3]; @@ -1051,7 +1055,7 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT } // Find indices to estimate related to grass (i.e., C3, C4 and annual grasses) for(index = C3Index; index < grassAnn; index++) { - if(inputValues[index] == SW_MISSING) { + if(missing(inputValues[index])) { grassesEstim[grassEstimSize] = index; grassEstimSize++; } @@ -1062,8 +1066,9 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT // Check if there is only one grass index to be estimated if(grassEstimSize == 1) { + // Set element to SumGrassesFraction - inputSumGrasses - estimCover[grassesEstim[0]] = SumGrassesFraction - inputSumGrasses; + inputValues[grassesEstim[0]] = SumGrassesFraction - inputSumGrasses; // Set totalSumGrasses to zero totalSumGrasses = 0.; @@ -1086,7 +1091,7 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT if(fillEmptyWithBareGround) { // Set estimCover at index `bareGround` to 1 - (all values execpt // at index `bareGround`) - estimCover[bareGround] = 1 - (initialVegSum - estimCover[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, " @@ -1225,7 +1230,7 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT } } - if(fixSumGrasses && totalSumGrasses > 0) { + if(fixSumGrasses && GT(totalSumGrasses, 0.)) { for(index = 0; index < grassEstimSize; index++) { estimGrassSum += estimCover[grassesEstim[index]]; } @@ -1233,16 +1238,20 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT // If estimGrassSum is 0, make it 1. to prevent dividing by zero estimGrassSum = (EQ(estimGrassSum, 0.)) ? 1. : estimGrassSum; - 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); + 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."); } - LogError(logfp, LOGWARN, "'estimate_PotNatVeg_composition': " - "Total grass cover set, but no grass cover estimated; " - "requested cover evenly divided among grass types."); } if(fixSumGrasses) { @@ -1264,7 +1273,12 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT } } while(index != overallEstimSize - 1 && isGrassIndex); } - overallEstimSize--; + + isGrassIndex = (Bool) (overallEstim[index - 1] == C3Index + || overallEstim[index - 1] == C4Index + || overallEstim[index - 1] == grassAnn); + + if(isGrassIndex) overallEstimSize--; } // Get final estimated vegetation sum @@ -1276,10 +1290,19 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT fixedValuesSum += inputValues[index]; } } - // Include fixed grass sum if not missing - if(!missing(SumGrassesFraction)) fixedValuesSum += SumGrassesFraction; + if(fixSumGrasses && grassEstimSize > 0) { + fixedValuesSum -= missing(inputValues[C3Index]) ? + 0. : inputValues[C3Index]; + + fixedValuesSum -= missing(inputValues[C4Index]) ? + 0. : inputValues[C4Index]; + fixedValuesSum -= missing(inputValues[grassAnn]) ? + 0. : inputValues[grassAnn]; + + fixedValuesSum += SumGrassesFraction; + } // Check if the final estimated vegetation sum is equal to one if(!EQ(finalVegSum, 1.)) { for(index = 0; index < overallEstimSize; index++) { @@ -1290,10 +1313,10 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT estimCover[overallEstim[index]] *= (1 - fixedValuesSum) / estimCoverSum; } } else { - if(fillEmptyWithBareGround && EQ(inputValues[bareGround], 0.)) { - estimCover[bareGround] = 1.; + if(fillEmptyWithBareGround && !fixBareGround) { + inputValues[bareGround] = 1.; for(index = 0; index < nTypes - 1; index++) { - estimCover[bareGround] -= estimCover[index]; + inputValues[bareGround] -= estimCover[index]; } } else { LogError(logfp, LOGFATAL, "'estimate_PotNatVeg_composition': " @@ -1310,29 +1333,43 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT // Fill in all output arrays (grassOutput, RelAbundanceL0, RelAbundanceL1) for(index = 0; index < nTypes; index++) { - if(!missing(inputValues[index]) && index != bareGround) { + // 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] = estimCover[C3Index]; - grassOutput[1] = estimCover[C4Index]; - grassOutput[2] = estimCover[grassAnn]; + 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]; - tempSumGrasses = mean(grassOutput, 3) * 3; if(tempSumGrasses > 0) { for(index = 0; index < 3; index++) { - grassOutput[index] /= tempSumGrasses; + grassOutput[index] /= (fixSumGrasses && overallEstimSize <= 1) + ? SumGrassesFraction : tempSumGrasses; } } RelAbundanceL1[0] = estimCover[treeIndex]; RelAbundanceL1[1] = estimCover[shrubIndex]; RelAbundanceL1[2] = estimCover[forbIndex] + estimCover[succIndex]; - RelAbundanceL1[3] = tempSumGrasses; - RelAbundanceL1[4] = estimCover[bareGround]; + + if(fixSumGrasses && grassEstimSize > 0) { + RelAbundanceL1[3] = SumGrassesFraction; + } else { + RelAbundanceL1[3] = tempSumGrasses; + } + + RelAbundanceL1[4] = inputValues[bareGround]; } /** diff --git a/SW_VegProd.h b/SW_VegProd.h index 717c8c82d..22f6d1ff8 100644 --- a/SW_VegProd.h +++ b/SW_VegProd.h @@ -269,7 +269,8 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe 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 C4IsList, double *grassOutput, double *RelAbundanceL0, double *RelAbundanceL1); + Bool C4IsList, Bool fixBareGround, double *grassOutput, double *RelAbundanceL0, + double *RelAbundanceL1); double cutZeroInf(double value); void uniqueIndices(int arrayOne[], int arrayTwo[], int arrayOneSize, int arrayTwoSize, int *finalIndexArray, int *finalIndexArraySize); From 000b42c02f75d4aa5e8678f95166d2a533bdabf7 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 6 Nov 2022 20:41:31 -0700 Subject: [PATCH 178/326] Updated unit tests for new vegetation parameter --- test/test_SW_VegProd.cc | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/test/test_SW_VegProd.cc b/test/test_SW_VegProd.cc index d88de5bb7..7dd1eb334 100644 --- a/test/test_SW_VegProd.cc +++ b/test/test_SW_VegProd.cc @@ -193,6 +193,7 @@ namespace { Bool warnExtrapolation = swTRUE; Bool inNorthHem = swTRUE; Bool C4IsList = swFALSE; + Bool fixBareGround = swTRUE; int nTypes = 8; int deallocate = 0; @@ -241,7 +242,7 @@ namespace { estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - C4IsList, grassOutput, RelAbundanceL0, RelAbundanceL1); + C4IsList, fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); /* =============================================================== Test when all input values are "SW_MISSING" @@ -323,7 +324,8 @@ namespace { estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - C4IsList, grassOutput, RelAbundanceL0, RelAbundanceL1); + C4IsList, fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); + // Loop through RelAbundanceL0 and test results for(index = 0; index < 8; index++) { EXPECT_DOUBLE_EQ(RelAbundanceL0[index], RelAbundanceL0Expected[index]); @@ -394,7 +396,7 @@ namespace { estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - C4IsList, grassOutput, RelAbundanceL0, RelAbundanceL1); + C4IsList, fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); // Loop through RelAbundanceL0 and test results. Since initial values // do not add to one and we fill empty with bare ground, bare ground should be higher @@ -470,7 +472,7 @@ namespace { estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - C4IsList, grassOutput, RelAbundanceL0, RelAbundanceL1); + C4IsList, fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); // Loop through RelAbundanceL0 and test results. for(index = 0; index < 8; index++) { @@ -513,7 +515,7 @@ namespace { estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - C4IsList, grassOutput, RelAbundanceL0, RelAbundanceL1); + C4IsList, fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); RelAbundanceL0Expected[succIndex] = 0.; RelAbundanceL0Expected[forbIndex] = .228048; @@ -600,7 +602,7 @@ namespace { estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - C4IsList, grassOutput, RelAbundanceL0, RelAbundanceL1); + C4IsList, fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); // Loop through RelAbundanceL0 and test results. for(index = 0; index < nTypes; index++) { @@ -669,6 +671,7 @@ namespace { Bool inNorthHem = swTRUE; Bool warnExtrapolation = swTRUE; Bool C4IsList = swFALSE; + Bool fixBareGround = swTRUE; int deallocate = 0; int allocate = 1; @@ -717,7 +720,7 @@ namespace { estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - C4IsList, grassOutput, RelAbundanceL0, RelAbundanceL1); + C4IsList, fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); // All values in "RelAbundanceL0" should be exactly the same as "inputValues" for(index = 0; index < nTypes; index++) { @@ -780,7 +783,8 @@ namespace { estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - C4IsList, grassOutput, RelAbundanceL0, RelAbundanceL1); + C4IsList, fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); + // Loop through RelAbundanceL0 and test results for(index = 0; index < nTypes; index++) { EXPECT_DOUBLE_EQ(RelAbundanceL0[index], RelAbundanceL0Expected[index]); @@ -852,7 +856,7 @@ namespace { estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - C4IsList, grassOutput, RelAbundanceL0, RelAbundanceL1); + C4IsList, fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); // Loop through RelAbundanceL0 and test results. for(index = 0; index < nTypes; index++) { @@ -928,6 +932,7 @@ namespace { Bool inNorthHem = swTRUE; Bool warnExtrapolation = swTRUE; Bool C4IsList = swFALSE; + Bool fixBareGround = swTRUE; int allocate = 1; int deallocate = 0; @@ -961,7 +966,7 @@ namespace { estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - C4IsList, grassOutput, RelAbundanceL0, RelAbundanceL1);, + C4IsList, fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1);, "" ); allocDeallocClimateStructs(deallocate, 31, &climateOutput, &climateAverages); From fa9b335fbaf135a9f3abb63f15f3140102efb6ff Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 6 Nov 2022 20:45:54 -0700 Subject: [PATCH 179/326] Set input values to 0.0 for incorrectly being SW_MISSING --- test/test_SW_VegProd.cc | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/test/test_SW_VegProd.cc b/test/test_SW_VegProd.cc index 7dd1eb334..aa3b2e44b 100644 --- a/test/test_SW_VegProd.cc +++ b/test/test_SW_VegProd.cc @@ -283,10 +283,10 @@ namespace { inputValues[forbIndex] = SW_MISSING; inputValues[C3Index] = .096; inputValues[C4Index] = SW_MISSING; - inputValues[grassAnn] = SW_MISSING; + inputValues[grassAnn] = 0.; inputValues[shrubIndex] = .1098; inputValues[treeIndex] = .0372; - inputValues[bareGround] = SW_MISSING; + 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) @@ -575,7 +575,7 @@ namespace { inputValues[forbIndex] = .0549; inputValues[C3Index] = SW_MISSING; inputValues[C4Index] = SW_MISSING; - inputValues[grassAnn] = SW_MISSING; + inputValues[grassAnn] = 0.; inputValues[shrubIndex] = .0549; inputValues[treeIndex] = .0549; inputValues[bareGround] = .0549; @@ -760,10 +760,10 @@ namespace { inputValues[forbIndex] = SW_MISSING; inputValues[C3Index] = .5; inputValues[C4Index] = SW_MISSING; - inputValues[grassAnn] = SW_MISSING; + inputValues[grassAnn] = 0.; inputValues[shrubIndex] = SW_MISSING; - inputValues[treeIndex] = SW_MISSING; - inputValues[bareGround] = SW_MISSING; + inputValues[treeIndex] = 0.; + inputValues[bareGround] = 0.; RelAbundanceL0Expected[succIndex] = .5; RelAbundanceL0Expected[forbIndex] = 0.; @@ -904,8 +904,7 @@ namespace { RelAbundanceL0Expected[bareGround] = 0.; - estimateVegetationFromClimate(&vegProd, startYear, endYear, - veg_method, latitude); + estimateVegetationFromClimate(&vegProd, startYear, endYear, veg_method, latitude); // Loop through RelAbundanceL1 and test results for(index = 0; index < 4; index++) { From 1e8d1d5e28c62883660214d01f58b8d28778fb52 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 13 Nov 2022 16:15:01 -0700 Subject: [PATCH 180/326] Undid `C4IsList` variable in veg estimation - Removed "C4IsList" variable from the function call/parameters and use within the vegetation estimation function - rSOILWAT2 incorrectly tested for whether C4 variables are a list - rSOILWAT2 meant to test if C4 variables are available, as a result, "C4IsList" part of the code is being reverted to "!missing(C4Variables[julyMin])" --- SW_VegProd.c | 11 +++++------ SW_VegProd.h | 3 +-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index 879c6cf02..3036380ed 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -893,8 +893,8 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe double SumGrassesFraction = SW_MISSING, C4Variables[3], grassOutput[3], RelAbundanceL0[8], RelAbundanceL1[5]; - Bool fillEmptyWithBareGround = swTRUE, warnExtrapolation = swTRUE; - Bool inNorthHem = swTRUE, C4IsList = swFALSE; + Bool fillEmptyWithBareGround = swFALSE, warnExtrapolation = swTRUE; + Bool inNorthHem = swTRUE; Bool fixBareGround = swTRUE; if(latitude < 0.0) { @@ -917,7 +917,7 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, coverValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, - inNorthHem, warnExtrapolation, C4IsList, fixBareGround, grassOutput, + inNorthHem, warnExtrapolation, fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); ForEachVegType(k) { @@ -985,8 +985,7 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe 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 C4IsList, Bool fixBareGround, double *grassOutput, double *RelAbundanceL0, - double *RelAbundanceL1) { + Bool fixBareGround, double *grassOutput, double *RelAbundanceL0, double *RelAbundanceL1) { const int nTypes = 8; int winterMonths[3], summerMonths[3]; @@ -1168,7 +1167,7 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT // This equations give percent species/vegetation -> use to limit // Paruelo's C4 equation, i.e., where no C4 species => C4 abundance == 0 - if(C4IsList) { + if(!missing(C4Variables[julyMin])) { if(C4Variables[frostFreeDays] <= 0) { C4Species = 0; } else { diff --git a/SW_VegProd.h b/SW_VegProd.h index 22f6d1ff8..f2883c5e2 100644 --- a/SW_VegProd.h +++ b/SW_VegProd.h @@ -269,8 +269,7 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe 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 C4IsList, Bool fixBareGround, double *grassOutput, double *RelAbundanceL0, - double *RelAbundanceL1); + Bool fixBareGround, double *grassOutput, double *RelAbundanceL0, double *RelAbundanceL1); double cutZeroInf(double value); void uniqueIndices(int arrayOne[], int arrayTwo[], int arrayOneSize, int arrayTwoSize, int *finalIndexArray, int *finalIndexArraySize); From 489a39c58840403ac2428ca117d3393ae18ae043 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 13 Nov 2022 16:22:09 -0700 Subject: [PATCH 181/326] Fixed more rSOILWAT2-related bugs - rSOILWAT2 previously did not factor in "SumGrassFraction" if defined, resulting in possible negative results, so program now accounts for "SumGrassFraction" if defined - Moved the for-loop responsible for counting fixed values and "initialVegSum" > 1 if-statement to after the grass section has been dealt with (if fixSumGrasses is true) - Compacted the section where "SumGrassFraction" is accounted for (old lines 1294 to 1303) before "finalVegSum" is checked --- SW_VegProd.c | 61 ++++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index 3036380ed..fc6199d5f 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -1013,26 +1013,6 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT } } - if(GT(initialVegSum, 1.)) { - LogError(logfp, LOGFATAL, "'estimate_PotNatVeg_composition': " - "User defined relative abundance values sum to more than " - "1 = full land cover."); - } - - // 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.; - } - } - // Check if grasses are fixed if(fixSumGrasses) { // Set SumGrassesFraction @@ -1076,14 +1056,40 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT // Otherwise, totalSumGrasses is zero or below for(index = 0; index < grassEstimSize; index++) { // Set all found ids to estimate to zero - estimCover[grassesEstim[index]] = 0.; + 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 @@ -1289,19 +1295,12 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT fixedValuesSum += inputValues[index]; } } + // Include fixed grass sum if not missing if(fixSumGrasses && grassEstimSize > 0) { - fixedValuesSum -= missing(inputValues[C3Index]) ? - 0. : inputValues[C3Index]; - - fixedValuesSum -= missing(inputValues[C4Index]) ? - 0. : inputValues[C4Index]; - - fixedValuesSum -= missing(inputValues[grassAnn]) ? - 0. : inputValues[grassAnn]; - - fixedValuesSum += SumGrassesFraction; + fixedValuesSum += totalSumGrasses; } + // Check if the final estimated vegetation sum is equal to one if(!EQ(finalVegSum, 1.)) { for(index = 0; index < overallEstimSize; index++) { From c43df4a6caf058616d9c9d8f18ec978ece5dee32 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 13 Nov 2022 16:24:52 -0700 Subject: [PATCH 182/326] Undid C4-related code in unit tests --- test/test_SW_VegProd.cc | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/test/test_SW_VegProd.cc b/test/test_SW_VegProd.cc index aa3b2e44b..07ec9d38a 100644 --- a/test/test_SW_VegProd.cc +++ b/test/test_SW_VegProd.cc @@ -192,7 +192,6 @@ namespace { Bool fillEmptyWithBareGround = swTRUE; Bool warnExtrapolation = swTRUE; Bool inNorthHem = swTRUE; - Bool C4IsList = swFALSE; Bool fixBareGround = swTRUE; int nTypes = 8; @@ -242,7 +241,7 @@ namespace { estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - C4IsList, fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); + fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); /* =============================================================== Test when all input values are "SW_MISSING" @@ -324,7 +323,7 @@ namespace { estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - C4IsList, fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); + fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); // Loop through RelAbundanceL0 and test results for(index = 0; index < 8; index++) { @@ -396,7 +395,7 @@ namespace { estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - C4IsList, fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); + fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); // Loop through RelAbundanceL0 and test results. Since initial values // do not add to one and we fill empty with bare ground, bare ground should be higher @@ -472,7 +471,7 @@ namespace { estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - C4IsList, fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); + fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); // Loop through RelAbundanceL0 and test results. for(index = 0; index < 8; index++) { @@ -515,7 +514,7 @@ namespace { estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - C4IsList, fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); + fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); RelAbundanceL0Expected[succIndex] = 0.; RelAbundanceL0Expected[forbIndex] = .228048; @@ -670,7 +669,6 @@ namespace { Bool fillEmptyWithBareGround = swTRUE; Bool inNorthHem = swTRUE; Bool warnExtrapolation = swTRUE; - Bool C4IsList = swFALSE; Bool fixBareGround = swTRUE; int deallocate = 0; @@ -720,7 +718,7 @@ namespace { estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - C4IsList, fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); + fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); // All values in "RelAbundanceL0" should be exactly the same as "inputValues" for(index = 0; index < nTypes; index++) { @@ -783,7 +781,7 @@ namespace { estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - C4IsList, fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); + fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); // Loop through RelAbundanceL0 and test results for(index = 0; index < nTypes; index++) { @@ -856,7 +854,7 @@ namespace { estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - C4IsList, fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); + fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); // Loop through RelAbundanceL0 and test results. for(index = 0; index < nTypes; index++) { @@ -930,7 +928,6 @@ namespace { Bool fillEmptyWithBareGround = swTRUE; Bool inNorthHem = swTRUE; Bool warnExtrapolation = swTRUE; - Bool C4IsList = swFALSE; Bool fixBareGround = swTRUE; int allocate = 1; @@ -965,7 +962,7 @@ namespace { estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - C4IsList, fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1);, + fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1);, "" ); allocDeallocClimateStructs(deallocate, 31, &climateOutput, &climateAverages); From 23a0f7184ef4f8146bc338cbdb14fcab9cf02e3f Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 13 Nov 2022 19:07:42 -0700 Subject: [PATCH 183/326] Fixed incorrectly set values in veg estimation - One of the previous commits accidentally set "fillEmptyWithBareGround" to false instead of true - "degreeAbove65" and "frostFreeDays" indices were swapped in their assigned value for "C4Variables" --- SW_VegProd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index fc6199d5f..a613ced19 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -893,7 +893,7 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe double SumGrassesFraction = SW_MISSING, C4Variables[3], grassOutput[3], RelAbundanceL0[8], RelAbundanceL1[5]; - Bool fillEmptyWithBareGround = swFALSE, warnExtrapolation = swTRUE; + Bool fillEmptyWithBareGround = swTRUE, warnExtrapolation = swTRUE; Bool inNorthHem = swTRUE; Bool fixBareGround = swTRUE; @@ -993,7 +993,7 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT // 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, frostFreeDays = 1, degreeAbove65 = 2, estimIndicesNotNA = 0, grassesEstim[3], + julyMin = 0, degreeAbove65 = 1, frostFreeDays = 2, estimIndicesNotNA = 0, grassesEstim[3], overallEstim[nTypes], iFixed[nTypes], iFixedSize = 0, isetIndices[3] = {grassAnn, treeIndex, bareGround}; From 651547e5598deb879044b74db32af075ae983266 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 13 Nov 2022 19:55:47 -0700 Subject: [PATCH 184/326] Added/moved to unit tests - Moved test for `estimateVegetationFromClimate()` to "NotFullVegetation" since its default values are all zero or SW_MISSING - Added a test to address the issue where C4 variables were not being used correctly (in "NotFullVegeation") - Added two tests within "FullVegetation" to make sure "SumGrassFraction" is added to the initial input sum and grasses are estimated when needed - R code snippets contain "fix_issue218" or "fix_issue219" to use in the most recent version of deprecated vegetation estimation function on rSOILWAT2 --- test/test_SW_VegProd.cc | 364 +++++++++++++++++++++++++++++----------- 1 file changed, 265 insertions(+), 99 deletions(-) diff --git a/test/test_SW_VegProd.cc b/test/test_SW_VegProd.cc index 07ec9d38a..9faec05ac 100644 --- a/test/test_SW_VegProd.cc +++ b/test/test_SW_VegProd.cc @@ -172,6 +172,7 @@ namespace { SW_CLIMATE_YEARLY climateOutput; SW_CLIMATE_CLIM climateAverages; + SW_VEGPROD vegProd; double inputValues[8] = {SW_MISSING, SW_MISSING, SW_MISSING, SW_MISSING, 0.0, SW_MISSING, 0.0, 0.0}; @@ -189,6 +190,11 @@ namespace { 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; @@ -468,6 +474,9 @@ namespace { RelAbundanceL1Expected[grassesIndexL1] = .3294; RelAbundanceL1Expected[bareGroundL1] = .2863; + // Recalculate climate of the site in southern hemisphere and add results to "climateOutput" + calcSiteClimate(SW_Weather.allHist, 31, 1980, inNorthHem, &climateOutput); + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, @@ -487,7 +496,13 @@ namespace { EXPECT_NEAR(grassOutput[1], .333333, tol6); EXPECT_NEAR(grassOutput[2], .333333, tol6); - /* Expect similar output to rSOILWAT2 (e.g., v5.3.1) + /* =============================================================== + 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 = @@ -502,124 +517,90 @@ namespace { ``` */ - 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.; - - estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, - climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, - SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); - - RelAbundanceL0Expected[succIndex] = 0.; - RelAbundanceL0Expected[forbIndex] = .228048; - RelAbundanceL0Expected[C3Index] = .525755; - RelAbundanceL0Expected[C4Index] = .157662; - RelAbundanceL0Expected[grassAnn] = 0.; - RelAbundanceL0Expected[shrubIndex] = .088534; - RelAbundanceL0Expected[treeIndex] = 0.; - RelAbundanceL0Expected[bareGround] = 0.; - RelAbundanceL1Expected[treeIndexL1] = 0.; - RelAbundanceL1Expected[shrubIndexL1] = .088534; - RelAbundanceL1Expected[forbIndexL1] = .228048; // Constains forbs + succulents (L0) - RelAbundanceL1Expected[grassesIndexL1] = .683417; + RelAbundanceL1Expected[shrubIndexL1] = .3084547; + RelAbundanceL1Expected[forbIndexL1] = .2608391; // Constains forbs + succulents (L0) + RelAbundanceL1Expected[grassesIndexL1] = .4307062; RelAbundanceL1Expected[bareGroundL1] = 0.; - // Loop through RelAbundanceL0 and test results. - for(index = 0; index < nTypes; index++) { - EXPECT_NEAR(RelAbundanceL0[index], RelAbundanceL0Expected[index], tol3); - } + RelAbundanceL0Expected[bareGround] = 0.; + + estimateVegetationFromClimate(&vegProd, startYear, endYear, veg_method, latitude); // Loop through RelAbundanceL1 and test results - for(index = 0; index < 5; index++) { - EXPECT_NEAR(RelAbundanceL1[index], RelAbundanceL1Expected[index], tol3); + for(index = 0; index < 4; index++) { + EXPECT_NEAR(vegProd.veg[index].cov.fCover, RelAbundanceL1Expected[index], tol6); } - EXPECT_NEAR(grassOutput[0], .769303, tol3); - EXPECT_NEAR(grassOutput[1], .230696, tol3); - EXPECT_DOUBLE_EQ(grassOutput[2], 0.); + EXPECT_NEAR(vegProd.bare_cov.fCover, RelAbundanceL0Expected[bareGround], tol6); /* =============================================================== - Test with `SumGrassesFraction` being fixed, all input of previous tests - are halved to .0549 + 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] =============================================================== */ /* 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::get_WeatherHistory(rSOILWAT2::sw_exampleData), + do_C4vars = TRUE, latitude = -90) - rSOILWAT2::estimate_PotNatVeg_composition( + 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, - Shrubs_Fraction = .0549, fix_shrubs = TRUE, - Trees_Fraction = .0549, fix_trees = TRUE, - Forbs_Fraction = .0549, fix_forbs = TRUE, - SumGrasses_Fraction = .7255, fix_sumgrasses = TRUE, - BareGround_Fraction = .0549, fix_BareGround = TRUE + isNorth = FALSE, + fix_issue218 = FALSE ) ``` */ - inputValues[succIndex] = .0549; - inputValues[forbIndex] = .0549; + inputValues[succIndex] = SW_MISSING; + inputValues[forbIndex] = SW_MISSING; inputValues[C3Index] = SW_MISSING; inputValues[C4Index] = SW_MISSING; inputValues[grassAnn] = 0.; - inputValues[shrubIndex] = .0549; - inputValues[treeIndex] = .0549; - inputValues[bareGround] = .0549; - - RelAbundanceL0Expected[succIndex] = .0549; - RelAbundanceL0Expected[forbIndex] = .0549; - RelAbundanceL0Expected[C3Index] = .7255; - RelAbundanceL0Expected[C4Index] = 0.; - RelAbundanceL0Expected[grassAnn] = 0.; - RelAbundanceL0Expected[shrubIndex] = .0549; - RelAbundanceL0Expected[treeIndex] = .0549; - RelAbundanceL0Expected[bareGround] = .0549; - - RelAbundanceL1Expected[treeIndexL1] = .0549; - RelAbundanceL1Expected[shrubIndexL1] = .0549; - RelAbundanceL1Expected[forbIndexL1] = .1098; // Constains forbs + succulents (L0) - RelAbundanceL1Expected[grassesIndexL1] = .7255; - RelAbundanceL1Expected[bareGroundL1] = .0549; + inputValues[shrubIndex] = SW_MISSING; + inputValues[treeIndex] = 0.; + inputValues[bareGround] = 0.; - fillEmptyWithBareGround = swTRUE; - inNorthHem = swTRUE; - SumGrassesFraction = .7255; + C4Variables[0] = SW_MISSING; estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - C4IsList, fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); + fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); + + RelAbundanceL0Expected[succIndex] = 0.; + RelAbundanceL0Expected[forbIndex] = .22804606; + RelAbundanceL0Expected[C3Index] = .52575060; + RelAbundanceL0Expected[C4Index] = .15766932; + RelAbundanceL0Expected[grassAnn] = 0.; + RelAbundanceL0Expected[shrubIndex] = .08853402; + RelAbundanceL0Expected[treeIndex] = 0.; + RelAbundanceL0Expected[bareGround] = 0.; + + RelAbundanceL1Expected[treeIndexL1] = 0.; + RelAbundanceL1Expected[shrubIndexL1] = .08853402; + RelAbundanceL1Expected[forbIndexL1] = .22804606; // Constains forbs + succulents (L0) + RelAbundanceL1Expected[grassesIndexL1] = .68341992; + RelAbundanceL1Expected[bareGroundL1] = 0.; // Loop through RelAbundanceL0 and test results. for(index = 0; index < nTypes; index++) { - EXPECT_DOUBLE_EQ(RelAbundanceL0[index], RelAbundanceL0Expected[index]); + EXPECT_NEAR(RelAbundanceL0[index], RelAbundanceL0Expected[index], tol3); } // Loop through RelAbundanceL1 and test results for(index = 0; index < 5; index++) { - if(index != grassesIndexL1) { - EXPECT_DOUBLE_EQ(RelAbundanceL1[index], RelAbundanceL1Expected[index]); - } else { - EXPECT_NEAR(RelAbundanceL1[grassesIndexL1], SumGrassesFraction, tol6); - } + EXPECT_NEAR(RelAbundanceL1[index], RelAbundanceL1Expected[index], tol3); } - - EXPECT_DOUBLE_EQ(grassOutput[0], 1.); - EXPECT_DOUBLE_EQ(grassOutput[1], 0.); + EXPECT_NEAR(grassOutput[0], 0.7692936, tol3); + EXPECT_NEAR(grassOutput[1], 0.2307064, tol3); EXPECT_DOUBLE_EQ(grassOutput[2], 0.); // Deallocate structs @@ -638,14 +619,9 @@ namespace { SW_CLIMATE_YEARLY climateOutput; SW_CLIMATE_CLIM climateAverages; - SW_VEGPROD vegProd; int index; int nTypes = 8; - int startYear = 1980; - int endYear = 2010; - int veg_method = 1; - double latitude = 90.0; double inputValues[8] = {.0567, .2317, .0392, .0981, .3218, .0827, .1293, .0405}; @@ -870,46 +846,236 @@ namespace { EXPECT_NEAR(grassOutput[1], .333333, tol6); EXPECT_NEAR(grassOutput[2], .333333, tol6); - // Deallocate structs - allocDeallocClimateStructs(deallocate, 31, &climateOutput, &climateAverages); + /* =============================================================== + Test with `SumGrassesFraction` being fixed, all input of previous tests + are halved to .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 + ) + ``` + */ + + 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; + + RelAbundanceL0Expected[succIndex] = .0549; + RelAbundanceL0Expected[forbIndex] = .0549; + RelAbundanceL0Expected[C3Index] = .7255; + RelAbundanceL0Expected[C4Index] = 0.; + RelAbundanceL0Expected[grassAnn] = 0.; + RelAbundanceL0Expected[shrubIndex] = .0549; + RelAbundanceL0Expected[treeIndex] = .0549; + RelAbundanceL0Expected[bareGround] = .0549; + + RelAbundanceL1Expected[treeIndexL1] = .0549; + RelAbundanceL1Expected[shrubIndexL1] = .0549; + RelAbundanceL1Expected[forbIndexL1] = .1098; // Constains forbs + succulents (L0) + RelAbundanceL1Expected[grassesIndexL1] = .7255; + RelAbundanceL1Expected[bareGroundL1] = .0549; + + fillEmptyWithBareGround = swTRUE; + inNorthHem = swTRUE; + SumGrassesFraction = .7255; + + 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++) { + if(index != grassesIndexL1) { + EXPECT_DOUBLE_EQ(RelAbundanceL1[index], RelAbundanceL1Expected[index]); + } else { + EXPECT_NEAR(RelAbundanceL1[grassesIndexL1], SumGrassesFraction, tol6); + } + } + + + EXPECT_DOUBLE_EQ(grassOutput[0], 1.); + EXPECT_DOUBLE_EQ(grassOutput[1], 0.); + EXPECT_DOUBLE_EQ(grassOutput[2], 0.); /* =============================================================== - 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] + 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 `SumGrassFraction` + is set to 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::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"]], - isNorth = FALSE + 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 ) ``` */ + 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.; + + SumGrassesFraction = 0.; + + RelAbundanceL0Expected[succIndex] = 0.; + RelAbundanceL0Expected[forbIndex] = 0.; + RelAbundanceL0Expected[C3Index] = 0.; + RelAbundanceL0Expected[C4Index] = 0.; + RelAbundanceL0Expected[grassAnn] = 0.; + RelAbundanceL0Expected[shrubIndex] = 1.; + RelAbundanceL0Expected[treeIndex] = 0.; + RelAbundanceL0Expected[bareGround] = 0.; + RelAbundanceL1Expected[treeIndexL1] = 0.; - RelAbundanceL1Expected[shrubIndexL1] = .3084547; - RelAbundanceL1Expected[forbIndexL1] = .2608391; // Constains forbs + succulents (L0) - RelAbundanceL1Expected[grassesIndexL1] = .4307062; + RelAbundanceL1Expected[shrubIndexL1] = 1.; + RelAbundanceL1Expected[forbIndexL1] = 0.; // Constains forbs + succulents (L0) + RelAbundanceL1Expected[grassesIndexL1] = 0.; RelAbundanceL1Expected[bareGroundL1] = 0.; - RelAbundanceL0Expected[bareGround] = 0.; + estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, + climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, + SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, + fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); - estimateVegetationFromClimate(&vegProd, startYear, endYear, veg_method, latitude); + // 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 < 4; index++) { - EXPECT_NEAR(vegProd.veg[index].cov.fCover, RelAbundanceL1Expected[index], tol6); + for(index = 0; index < 5; index++) { + if(index != grassesIndexL1) { + EXPECT_DOUBLE_EQ(RelAbundanceL1[index], RelAbundanceL1Expected[index]); + } else { + EXPECT_NEAR(RelAbundanceL1[grassesIndexL1], SumGrassesFraction, tol6); + } } - EXPECT_NEAR(vegProd.bare_cov.fCover, RelAbundanceL0Expected[bareGround], tol6); + /* =============================================================== + Test when input sum is 1, including `SumGrassFraction`, and + grass needs to be estimated + =============================================================== */ + + /* 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 + ) + ``` + */ + + 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; + + SumGrassesFraction = .5; + + RelAbundanceL0Expected[succIndex] = 0.; + RelAbundanceL0Expected[forbIndex] = 0.; + RelAbundanceL0Expected[C3Index] = .5; + RelAbundanceL0Expected[C4Index] = 0.; + RelAbundanceL0Expected[grassAnn] = 0.; + RelAbundanceL0Expected[shrubIndex] = 0.; + RelAbundanceL0Expected[treeIndex] = 0.; + RelAbundanceL0Expected[bareGround] = .5; + + RelAbundanceL1Expected[treeIndexL1] = 0.; + RelAbundanceL1Expected[shrubIndexL1] = 0.; + RelAbundanceL1Expected[forbIndexL1] = 0.; // Constains forbs + succulents (L0) + RelAbundanceL1Expected[grassesIndexL1] = .5; + RelAbundanceL1Expected[bareGroundL1] = .5; + + 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++) { + if(index != grassesIndexL1) { + EXPECT_DOUBLE_EQ(RelAbundanceL1[index], RelAbundanceL1Expected[index]); + } else { + EXPECT_NEAR(RelAbundanceL1[grassesIndexL1], SumGrassesFraction, tol6); + } + } + + // Deallocate structs + allocDeallocClimateStructs(deallocate, 31, &climateOutput, &climateAverages); + } TEST(VegEstimationDeathTest, VegInputGreaterThanOne) { From d28e3bfa82603f7e810b91819c789640ac5be955 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 13 Nov 2022 20:02:12 -0700 Subject: [PATCH 185/326] Updated existing unit test R code snippets - rSOILWAT2's deprecated vegetation function was updated to fix two previously unnoticed bugs, so code snippets now send in proper boolean value to get correct data from R side --- test/test_SW_VegProd.cc | 47 +++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/test/test_SW_VegProd.cc b/test/test_SW_VegProd.cc index 9faec05ac..d18fcb302 100644 --- a/test/test_SW_VegProd.cc +++ b/test/test_SW_VegProd.cc @@ -257,12 +257,15 @@ namespace { * 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::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"]] + mean_monthly_Temp_C = clim1[["meanMonthlyTempC"]], + dailyC4vars = clim1[["dailyC4vars"]], + fix_issue218 = TRUE ) ``` */ @@ -297,7 +300,8 @@ namespace { * 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::get_WeatherHistory(rSOILWAT2::sw_exampleData), + do_C4vars = TRUE) rSOILWAT2:::estimate_PotNatVeg_composition_old( MAP_mm = 10 * clim1[["MAP_cm"]], MAT_C = clim1[["MAT_C"]], @@ -306,7 +310,9 @@ namespace { Succulents_Fraction = .376, fix_succulents = TRUE, C3_Fraction = .096, fix_C3grasses = TRUE, Shrubs_Fraction = .1098, fix_shrubs = TRUE, - Trees_Fraction = .0372, fix_trees = TRUE + Trees_Fraction = .0372, fix_trees = TRUE, + dailyC4vars = clim1[["dailyC4vars"]], + fix_issue218 = TRUE ) ``` */ @@ -362,7 +368,8 @@ namespace { * 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::get_WeatherHistory(rSOILWAT2::sw_exampleData), + do_C4vars = TRUE) rSOILWAT2:::estimate_PotNatVeg_composition_old( MAP_mm = 10 * clim1[["MAP_cm"]], MAT_C = clim1[["MAT_C"]], @@ -375,7 +382,9 @@ namespace { Annuals_Fraction = .1098, fix_annuals = TRUE, C4_Fraction = .1098, fix_C4grasses = TRUE, Forbs_Fraction = .1098, fix_forbs = TRUE, - BareGround_Fraction = .1098, fix_BareGround = TRUE + BareGround_Fraction = .1098, fix_BareGround = TRUE, + dailyC4vars = clim1[["dailyC4vars"]], + fix_issue218 = TRUE ) ``` */ @@ -434,7 +443,8 @@ namespace { * 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::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"]], @@ -448,7 +458,8 @@ namespace { Shrubs_Fraction = .1098, fix_shrubs = TRUE, Trees_Fraction = 0.0549, fix_trees = TRUE, BareGround_Fraction = .0549, fix_BareGround = TRUE, - isNorth = FALSE + isNorth = FALSE, dailyC4vars = clim1[["dailyC4vars"]], + fix_issue218 = TRUE ) ``` */ @@ -506,13 +517,15 @@ namespace { * 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::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"]], - isNorth = FALSE + dailyC4vars = clim1[["dailyC4vars"]], + fix_issue218 = TRUE ) ``` */ @@ -718,14 +731,17 @@ namespace { * 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::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 + C3_Fraction = .5, fix_C3grasses = TRUE, + dailyC4vars = clim1[["dailyC4vars"]], + fix_issue218 = TRUE ) ``` */ @@ -782,7 +798,8 @@ namespace { * 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::get_WeatherHistory(rSOILWAT2::sw_exampleData), + do_C4vars = TRUE) rSOILWAT2:::estimate_PotNatVeg_composition_old( MAP_mm = 10 * clim1[["MAP_cm"]], MAT_C = clim1[["MAT_C"]], @@ -796,7 +813,9 @@ namespace { C4_Fraction = .1098, fix_C4grasses = TRUE, Forbs_Fraction = .1098, fix_forbs = TRUE, BareGround_Fraction = 0.2314, fix_BareGround = TRUE, - fill_empty_with_BareGround = TRUE + fill_empty_with_BareGround = TRUE, + dailyC4vars = clim1[["dailyC4vars"]], + fix_issue218 = TRUE ) ``` */ From e8afdb2b53cde727101023877b48cf0e1b835a56 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Tue, 15 Nov 2022 15:14:27 -0500 Subject: [PATCH 186/326] Add `NEWS` for v7.0.0-devel --- NEWS.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 NEWS.md diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 000000000..97c980428 --- /dev/null +++ b/NEWS.md @@ -0,0 +1,9 @@ +# NEWS + +# SOILWAT2 v7.0.0-9000 +* The type of soil density is a new input. + Soil density inputs can now represent either matric or bulk density; + the code converts automatically as needed (issue #280; @dschlaep). + +## Changes to inputs +* File `siteparam.in` gained new input for `type_soilDensityInput`. From d2bd2fb65904aea150ad7d86761771cdad76103d Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Tue, 15 Nov 2022 15:27:22 -0500 Subject: [PATCH 187/326] Update documentation of `generateMissingWeather()` - commit 679226f03acfb8993b09fca3659ea2e141cce3a2 (Oct 5, 2022) removed the "first year to begin historical weather" from inputs --- SW_Weather.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 3a53e4479..0d2f39600 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -254,14 +254,9 @@ void scaleAllWeather( - error if more than `optLOCF_nMax` days per calendar year are missing 3. First-order Markov weather generator (`method` = 2) - SOILWAT2 may be set up such that weather is generated exclusively - (i.e., without an attempt to read data from files on disk): - - 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 - + 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); From 638a1073e902ac2a84123a7137348e451df1157e Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 15 Nov 2022 22:02:07 -0700 Subject: [PATCH 188/326] Fixed "numYears" documentation - Previous "numYears" documentation was describing "startYear" instead for `averageClimateAcrossYears()` - Made "numYears" description consistent for rest of climate calculating functions --- SW_Weather.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index 60b41ce6b..a63fd9dce 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -76,7 +76,7 @@ static char *MyFileName; @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()` - @param[in] numYears Calendar year corresponding to first year of `allHist` + @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()` */ @@ -141,7 +141,7 @@ void averageClimateAcrossYears(SW_CLIMATE_YEARLY *climateOutput, int numYears, | 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 by `allHist` + @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 averages and @@ -305,7 +305,7 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, being in the northern/southern hemisphere. @param[in] allHist Array containing all historical data of a site - @param[in] numYears Number of years simulation covers + @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 averages and standard deviations output by `averageClimateAcrossYears()` From db9ef7ba80d3cee87a727df5b5570dd234428176 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 15 Nov 2022 22:08:07 -0700 Subject: [PATCH 189/326] Fixed "climateOutput" documentation - Previous "climateOutput" documentation was along the lines of "climateAverages" in a couple of cases - Appended a brief example of what is contained within SW_CLIMATE_YEARLY type * Instead of listing all, only monthly/yearly temperature and precipitation values were mentioned as examples --- SW_Weather.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index a63fd9dce..ef3018dea 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -76,6 +76,7 @@ static char *MyFileName; @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()` @@ -144,8 +145,8 @@ void averageClimateAcrossYears(SW_CLIMATE_YEARLY *climateOutput, int numYears, @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 averages and - standard deviations output by `averageClimateAcrossYears()` + @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, @@ -307,12 +308,12 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, @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 averages and - standard deviations output by `averageClimateAcrossYears()` + @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) { + SW_CLIMATE_YEARLY *climateOutput) { int month = Jan, numDaysMonth = Time_days_in_month(month), yearIndex, day, numDaysYear, currMonDay, year; From d2102487f2d7bf398bc0063714f3d900d48b3743 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 15 Nov 2022 22:18:27 -0700 Subject: [PATCH 190/326] Removed "C4IsList" in documentation - The parameter to `estimatePotNatVegComposition()`, "C4IsList", was removed in a previous commit, the documentation for it has now been removed --- SW_VegProd.c | 1 - 1 file changed, 1 deletion(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index a613ced19..df186948c 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -969,7 +969,6 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe @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] C4IsList Bool value specifying if C4 is a list (false when not running rSOILWAT2) @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: From 47174edbb18809e0886a989d472562164f8daa50 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 15 Nov 2022 22:20:22 -0700 Subject: [PATCH 191/326] Updated `estimateVegetationFromClimate()` documentation - The parameter "latitude" for `estimateVegetationFromClimate()` has existed for awhile and hasn't gotten documentation, so it has now been added --- SW_VegProd.c | 1 + 1 file changed, 1 insertion(+) diff --git a/SW_VegProd.c b/SW_VegProd.c index df186948c..b045853cb 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -874,6 +874,7 @@ void get_critical_rank(void){ @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, From 60f22d987f39d87a8aba9ac21cfb6fc49eb139ce Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 15 Nov 2022 22:32:59 -0700 Subject: [PATCH 192/326] Updated `cutZeroInf()` code/documentation - Renamed parameter from "value" to "testValue" to be a little more descriptive - Added documentation for `cutZeroInf()` parameter, which wasn't existent previously - Replaced "value = 0." with use of macro `LT()` to account for floating-point arithmetic --- SW_VegProd.c | 8 +++++--- SW_VegProd.h | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index b045853cb..aa055939a 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -1373,14 +1373,16 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT /** @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 value) { - if(value < 0.) { +double cutZeroInf(double testValue) { + if(LT(testValue, 0.)) { return 0.; } else { - return value; + return testValue; } } diff --git a/SW_VegProd.h b/SW_VegProd.h index f2883c5e2..9c71a25b8 100644 --- a/SW_VegProd.h +++ b/SW_VegProd.h @@ -269,8 +269,8 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe 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 value); + 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); From 3e01bb6d0c646b93cfadfd6c0fd8410e5cfcf249 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 16 Nov 2022 12:31:06 -0500 Subject: [PATCH 193/326] Simplify tests for `estimatePotNatVegComposition()` - new test helper functions * `copyL0()`: copies L0-cover inputs to expected L0-output array * `calcVegCoverL1FromL0()`: calculates L1-cover from L0-cover array * `calcGrassCoverFromL0()`: calculates grass cover components relative to total grass cover - organize tests: (i) comment; (ii) set inputs; (iii) set or calculate expected output; (iv) calculate cover; (v) test expectations - calculate expected output from (fixed) inputs or from more detailed expected output where possible -- instead of repeating hard-coded values in several places (up to three copies per value) - loop over expected grass cover array -> all but one test are passing -> the one failing test is because L1-cover output for bare ground is incorrect if bare ground input is `SW_MISSING` (see https://github.com/DrylandEcology/SOILWAT2/pull/321/files/cdbfa8b929a1c6b34e9a1c94d9b3c5e0a483c326#r1024294338) --- test/test_SW_VegProd.cc | 696 +++++++++++++++++++++------------------- 1 file changed, 372 insertions(+), 324 deletions(-) diff --git a/test/test_SW_VegProd.cc b/test/test_SW_VegProd.cc index d18fcb302..2a2a390fc 100644 --- a/test/test_SW_VegProd.cc +++ b/test/test_SW_VegProd.cc @@ -62,6 +62,54 @@ static void assert_decreasing_SWPcrit(void) } +// 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; @@ -174,8 +222,7 @@ namespace { SW_CLIMATE_CLIM climateAverages; SW_VEGPROD vegProd; - double inputValues[8] = {SW_MISSING, SW_MISSING, SW_MISSING, SW_MISSING, - 0.0, SW_MISSING, 0.0, 0.0}; + double inputValues[8]; double shrubLimit = .2; // Array holding only grass values @@ -205,26 +252,11 @@ namespace { int allocate = 1; int index; - // 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; - - double RelAbundanceL0Expected[8] = {0.0, 0.2608391, 0.4307062, - 0.0, 0.0, 0.3084547, 0.0, 0.0}; - double RelAbundanceL1Expected[5] = {0.0, 0.3084547, 0.2608391, 0.4307062, 0.0}; + + double RelAbundanceL0Expected[8]; + double RelAbundanceL1Expected[5]; + double grassOutputExpected[3]; + // Reset "SW_Weather.allHist" SW_WTH_read(); @@ -243,15 +275,18 @@ namespace { C4Variables[1] = climateAverages.ddAbove65F_degday; C4Variables[2] = climateAverages.frostFree_days; - // Estimate vegetation based off calculated variables and "inputValues" - estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, - climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, - SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); /* =============================================================== 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) @@ -270,6 +305,28 @@ namespace { ``` */ + // 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); @@ -279,14 +336,18 @@ namespace { for(index = 0; index < 5; index++) { EXPECT_NEAR(RelAbundanceL1[index], RelAbundanceL1Expected[index], tol6); } - - EXPECT_DOUBLE_EQ(grassOutput[0], 1.); - EXPECT_DOUBLE_EQ(grassOutput[1], 0.); - EXPECT_DOUBLE_EQ(grassOutput[2], 0.); + + // Loop through grassOutput and test results + for(index = 0; index < 3; index++) { + EXPECT_NEAR(grassOutput[index], grassOutputExpected[index], tol6); + } + /* =============================================================== - Test with half of input values not "SW_MISSING" + 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; @@ -317,21 +378,16 @@ namespace { ``` */ - RelAbundanceL0Expected[succIndex] = .3760; + // Set or calculate expected outputs + copyL0(RelAbundanceL0Expected, inputValues); RelAbundanceL0Expected[forbIndex] = .3810; - RelAbundanceL0Expected[C3Index] = .0960; RelAbundanceL0Expected[C4Index] = 0.; - RelAbundanceL0Expected[grassAnn] = 0.; - RelAbundanceL0Expected[shrubIndex] = .1098; - RelAbundanceL0Expected[treeIndex] = .0372; - RelAbundanceL0Expected[bareGround] = 0.; - RelAbundanceL1Expected[treeIndexL1] = .0372; - RelAbundanceL1Expected[shrubIndexL1] = .1098; - RelAbundanceL1Expected[forbIndexL1] = .7570; // Constains forbs + succulents (L0) - RelAbundanceL1Expected[grassesIndexL1] = .0960; - RelAbundanceL1Expected[bareGroundL1] = 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, @@ -347,9 +403,11 @@ namespace { EXPECT_DOUBLE_EQ(RelAbundanceL1[index], RelAbundanceL1Expected[index]); } - EXPECT_DOUBLE_EQ(grassOutput[0], 1.); - EXPECT_DOUBLE_EQ(grassOutput[1], 0.); - EXPECT_DOUBLE_EQ(grassOutput[2], 0.); + // 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" @@ -389,32 +447,24 @@ namespace { ``` */ - RelAbundanceL0Expected[succIndex] = .1098; - RelAbundanceL0Expected[forbIndex] = .1098; - RelAbundanceL0Expected[C3Index] = .1098; - RelAbundanceL0Expected[C4Index] = .1098; - RelAbundanceL0Expected[grassAnn] = .1098; - RelAbundanceL0Expected[shrubIndex] = .1098; - RelAbundanceL0Expected[treeIndex] = .1098; + // Set or calculate expected outputs + copyL0(RelAbundanceL0Expected, inputValues); // RelAbundanceL0Expected[bareGround] is not .1098 because // fillEmptyWithBareGround = swTRUE RelAbundanceL0Expected[bareGround] = 0.2314; - RelAbundanceL1Expected[treeIndexL1] = .1098; - RelAbundanceL1Expected[shrubIndexL1] = .1098; - RelAbundanceL1Expected[forbIndexL1] = .2196; // Constains forbs + succulents (L0) - RelAbundanceL1Expected[grassesIndexL1] = .3294; - RelAbundanceL1Expected[bareGroundL1] = .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. Since initial values - // do not add to one and we fill empty with bare ground, bare ground should be higher - // than the other values (in this case, .2314) + // Loop through RelAbundanceL0 and test results. for(index = 0; index < nTypes; index++) { EXPECT_DOUBLE_EQ(RelAbundanceL0[index], RelAbundanceL0Expected[index]); } @@ -424,12 +474,56 @@ namespace { EXPECT_DOUBLE_EQ(RelAbundanceL1[index], RelAbundanceL1Expected[index]); } - EXPECT_NEAR(grassOutput[0], .333333, tol6); - EXPECT_NEAR(grassOutput[1], .333333, tol6); - EXPECT_NEAR(grassOutput[2], .333333, tol6); + // Loop through grassOutput and test results + for(index = 0; index < 3; index++) { + EXPECT_NEAR(grassOutput[index], grassOutputExpected[index], tol6); + } + + /* =============================================================== - Test with `inNorthHem` to be false with two tests: + 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 @@ -439,6 +533,14 @@ namespace { 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} @@ -464,30 +566,15 @@ namespace { ``` */ - inNorthHem = swFALSE; - fillEmptyWithBareGround = swTRUE; - - inputValues[treeIndex] = .0549; - inputValues[bareGround] = .0549; - - RelAbundanceL0Expected[succIndex] = .1098; - RelAbundanceL0Expected[forbIndex] = .1098; - RelAbundanceL0Expected[C3Index] = .1098; - RelAbundanceL0Expected[C4Index] = .1098; - RelAbundanceL0Expected[grassAnn] = .1098; - RelAbundanceL0Expected[shrubIndex] = .1098; - RelAbundanceL0Expected[treeIndex] = .0549; + // Set or calculate expected outputs + copyL0(RelAbundanceL0Expected, inputValues); RelAbundanceL0Expected[bareGround] = .2863; - RelAbundanceL1Expected[treeIndexL1] = .0549; - RelAbundanceL1Expected[shrubIndexL1] = .1098; - RelAbundanceL1Expected[forbIndexL1] = .2196; // Constains forbs + succulents (L0) - RelAbundanceL1Expected[grassesIndexL1] = .3294; - RelAbundanceL1Expected[bareGroundL1] = .2863; + calcVegCoverL1FromL0(RelAbundanceL1Expected, RelAbundanceL0Expected); + calcGrassCoverFromL0(grassOutputExpected, RelAbundanceL0Expected); - // Recalculate climate of the site in southern hemisphere and add results to "climateOutput" - calcSiteClimate(SW_Weather.allHist, 31, 1980, inNorthHem, &climateOutput); + // Estimate vegetation estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, @@ -503,49 +590,12 @@ namespace { EXPECT_DOUBLE_EQ(RelAbundanceL1[index], RelAbundanceL1Expected[index]); } - EXPECT_NEAR(grassOutput[0], .333333, tol6); - EXPECT_NEAR(grassOutput[1], .333333, tol6); - EXPECT_NEAR(grassOutput[2], .333333, 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] = .4307062; - RelAbundanceL1Expected[bareGroundL1] = 0.; - - RelAbundanceL0Expected[bareGround] = 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); + // Loop through grassOutput and test results + for(index = 0; index < 3; index++) { + EXPECT_NEAR(grassOutput[index], grassOutputExpected[index], tol6); } - EXPECT_NEAR(vegProd.bare_cov.fCover, RelAbundanceL0Expected[bareGround], tol6); + /* =============================================================== Test "C4Variables" not being defined (faked by setting july min (index zero) to SW_MISSING) @@ -554,6 +604,17 @@ namespace { [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} @@ -571,22 +632,8 @@ namespace { ``` */ - 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; - - estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, - climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, - SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, - fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1); - + // Set or calculate expected outputs + copyL0(RelAbundanceL0Expected, inputValues); RelAbundanceL0Expected[succIndex] = 0.; RelAbundanceL0Expected[forbIndex] = .22804606; RelAbundanceL0Expected[C3Index] = .52575060; @@ -596,25 +643,32 @@ namespace { RelAbundanceL0Expected[treeIndex] = 0.; RelAbundanceL0Expected[bareGround] = 0.; - RelAbundanceL1Expected[treeIndexL1] = 0.; - RelAbundanceL1Expected[shrubIndexL1] = .08853402; - RelAbundanceL1Expected[forbIndexL1] = .22804606; // Constains forbs + succulents (L0) - RelAbundanceL1Expected[grassesIndexL1] = .68341992; - RelAbundanceL1Expected[bareGroundL1] = 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], tol3); + 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], tol3); + 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); } - EXPECT_NEAR(grassOutput[0], 0.7692936, tol3); - EXPECT_NEAR(grassOutput[1], 0.2307064, tol3); - EXPECT_DOUBLE_EQ(grassOutput[2], 0.); // Deallocate structs allocDeallocClimateStructs(deallocate, 31, &climateOutput, &climateAverages); @@ -625,7 +679,7 @@ namespace { /* ================================================================ 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 ================================================================ */ @@ -636,8 +690,7 @@ namespace { int index; int nTypes = 8; - double inputValues[8] = {.0567, .2317, .0392, .0981, - .3218, .0827, .1293, .0405}; + double inputValues[8]; double shrubLimit = .2; // Array holding only grass values @@ -652,8 +705,8 @@ namespace { double SumGrassesFraction = SW_MISSING; double C4Variables[3]; double RelAbundanceL0Expected[8]; - - double RelAbundanceL1Expected[5] = {.1293, .0827, .2884, .4591, .0405}; + double RelAbundanceL1Expected[5]; + double grassOutputExpected[3]; Bool fillEmptyWithBareGround = swTRUE; Bool inNorthHem = swTRUE; @@ -663,28 +716,6 @@ namespace { int deallocate = 0; int allocate = 1; - // 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; - - // Transfer input values to RelAbundanceL0Expected since values - // are expected to be the same - for(index = 0; index < nTypes; index++) { - RelAbundanceL0Expected[index] = inputValues[index]; - } // Reset "SW_Weather.allHist" SW_WTH_read(); @@ -703,29 +734,64 @@ namespace { C4Variables[1] = climateAverages.ddAbove65F_degday; C4Variables[2] = climateAverages.frostFree_days; - // Estimate vegetation based off calculated variables and "inputValues" + + /* =============================================================== + 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); - // All values in "RelAbundanceL0" should be exactly the same as "inputValues" + + // 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); } - // All values in "RelAbundanceL1" should be exactly the same as "inputValues" + // Loop through RelAbundanceL1 and test results for(index = 0; index < 5; index++) { EXPECT_DOUBLE_EQ(RelAbundanceL1[index], RelAbundanceL1Expected[index]); } - EXPECT_NEAR(grassOutput[0], .085384, tol6); - EXPECT_NEAR(grassOutput[1], .213678, tol6); - EXPECT_NEAR(grassOutput[2], .700936, tol6); + // Loop through grassOutput and test results + for(index = 0; index < 3; index++) { + EXPECT_DOUBLE_EQ(grassOutput[index], grassOutputExpected[index]); + } + + /* =============================================================== - Test when a couple input values are not "SW_MISSING" + 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) @@ -746,36 +812,24 @@ namespace { ``` */ - 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.; - - RelAbundanceL0Expected[succIndex] = .5; + // Set or calculate expected outputs + copyL0(RelAbundanceL0Expected, inputValues); RelAbundanceL0Expected[forbIndex] = 0.; - RelAbundanceL0Expected[C3Index] = .5; RelAbundanceL0Expected[C4Index] = 0.; - RelAbundanceL0Expected[grassAnn] = 0.; RelAbundanceL0Expected[shrubIndex] = 0.; - RelAbundanceL0Expected[treeIndex] = 0.; - RelAbundanceL0Expected[bareGround] = 0.; - RelAbundanceL1Expected[treeIndexL1] = 0.; - RelAbundanceL1Expected[shrubIndexL1] = 0.; - RelAbundanceL1Expected[forbIndexL1] = .5; // Constains forbs + succulents (L0) - RelAbundanceL1Expected[grassesIndexL1] = .5; - RelAbundanceL1Expected[bareGroundL1] = 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 + + // Loop through RelAbundanceL0 and test results. for(index = 0; index < nTypes; index++) { EXPECT_DOUBLE_EQ(RelAbundanceL0[index], RelAbundanceL0Expected[index]); } @@ -785,14 +839,26 @@ namespace { EXPECT_DOUBLE_EQ(RelAbundanceL1[index], RelAbundanceL1Expected[index]); } - EXPECT_DOUBLE_EQ(grassOutput[0], 1.); - EXPECT_DOUBLE_EQ(grassOutput[1], 0.); - EXPECT_DOUBLE_EQ(grassOutput[2], 0.); + // 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) @@ -820,32 +886,14 @@ namespace { ``` */ - inputValues[succIndex] = .1098; - inputValues[forbIndex] = .1098; - inputValues[C3Index] = .1098; - inputValues[C4Index] = .1098; - inputValues[grassAnn] = .1098; - inputValues[shrubIndex] = .1098; - inputValues[treeIndex] = .1098; - inputValues[bareGround] = .2314; + // Set or calculate expected outputs + copyL0(RelAbundanceL0Expected, inputValues); - fillEmptyWithBareGround = swFALSE; + calcVegCoverL1FromL0(RelAbundanceL1Expected, RelAbundanceL0Expected); + calcGrassCoverFromL0(grassOutputExpected, RelAbundanceL0Expected); - RelAbundanceL0Expected[succIndex] = .1098; - RelAbundanceL0Expected[forbIndex] = .1098; - RelAbundanceL0Expected[C3Index] = .1098; - RelAbundanceL0Expected[C4Index] = .1098; - RelAbundanceL0Expected[grassAnn] = .1098; - RelAbundanceL0Expected[shrubIndex] = .1098; - RelAbundanceL0Expected[treeIndex] = .1098; - RelAbundanceL0Expected[bareGround] = .2314; - - RelAbundanceL1Expected[treeIndexL1] = .1098; - RelAbundanceL1Expected[shrubIndexL1] = .1098; - RelAbundanceL1Expected[forbIndexL1] = .2196; // Constains forbs + succulents (L0) - RelAbundanceL1Expected[grassesIndexL1] = .3294; - RelAbundanceL1Expected[bareGroundL1] = .2314; + // Estimate vegetation estimatePotNatVegComposition(climateAverages.meanTemp_C, climateAverages.PPT_cm, climateAverages.meanTempMon_C, climateAverages.PPTMon_cm, inputValues, shrubLimit, SumGrassesFraction, C4Variables, fillEmptyWithBareGround, inNorthHem, warnExtrapolation, @@ -853,23 +901,36 @@ namespace { // Loop through RelAbundanceL0 and test results. for(index = 0; index < nTypes; index++) { - EXPECT_NEAR(RelAbundanceL0[index], RelAbundanceL0Expected[index], tol6); + EXPECT_DOUBLE_EQ(RelAbundanceL0[index], RelAbundanceL0Expected[index]); } // Loop through RelAbundanceL1 and test results for(index = 0; index < 5; index++) { - EXPECT_NEAR(RelAbundanceL1[index], RelAbundanceL1Expected[index], tol6); + 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_NEAR(grassOutput[0], .333333, tol6); - EXPECT_NEAR(grassOutput[1], .333333, tol6); - EXPECT_NEAR(grassOutput[2], .333333, tol6); /* =============================================================== 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} @@ -893,34 +954,16 @@ namespace { ``` */ - 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; - - RelAbundanceL0Expected[succIndex] = .0549; - RelAbundanceL0Expected[forbIndex] = .0549; + // Set or calculate expected outputs + copyL0(RelAbundanceL0Expected, inputValues); RelAbundanceL0Expected[C3Index] = .7255; RelAbundanceL0Expected[C4Index] = 0.; - RelAbundanceL0Expected[grassAnn] = 0.; - RelAbundanceL0Expected[shrubIndex] = .0549; - RelAbundanceL0Expected[treeIndex] = .0549; - RelAbundanceL0Expected[bareGround] = .0549; - - RelAbundanceL1Expected[treeIndexL1] = .0549; - RelAbundanceL1Expected[shrubIndexL1] = .0549; - RelAbundanceL1Expected[forbIndexL1] = .1098; // Constains forbs + succulents (L0) - RelAbundanceL1Expected[grassesIndexL1] = .7255; - RelAbundanceL1Expected[bareGroundL1] = .0549; - - fillEmptyWithBareGround = swTRUE; - inNorthHem = swTRUE; - SumGrassesFraction = .7255; + 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, @@ -933,17 +976,18 @@ namespace { // Loop through RelAbundanceL1 and test results for(index = 0; index < 5; index++) { - if(index != grassesIndexL1) { - EXPECT_DOUBLE_EQ(RelAbundanceL1[index], RelAbundanceL1Expected[index]); - } else { - EXPECT_NEAR(RelAbundanceL1[grassesIndexL1], SumGrassesFraction, tol6); - } + 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); + - EXPECT_DOUBLE_EQ(grassOutput[0], 1.); - EXPECT_DOUBLE_EQ(grassOutput[1], 0.); - EXPECT_DOUBLE_EQ(grassOutput[2], 0.); /* =============================================================== Test where one input value is fixed at 1 and 5/7 are fixed to 0, @@ -951,6 +995,17 @@ namespace { 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} @@ -974,32 +1029,16 @@ namespace { ``` */ - 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.; - - SumGrassesFraction = 0.; - - RelAbundanceL0Expected[succIndex] = 0.; - RelAbundanceL0Expected[forbIndex] = 0.; + // Set or calculate expected outputs + copyL0(RelAbundanceL0Expected, inputValues); RelAbundanceL0Expected[C3Index] = 0.; RelAbundanceL0Expected[C4Index] = 0.; - RelAbundanceL0Expected[grassAnn] = 0.; - RelAbundanceL0Expected[shrubIndex] = 1.; - RelAbundanceL0Expected[treeIndex] = 0.; - RelAbundanceL0Expected[bareGround] = 0.; - RelAbundanceL1Expected[treeIndexL1] = 0.; - RelAbundanceL1Expected[shrubIndexL1] = 1.; - RelAbundanceL1Expected[forbIndexL1] = 0.; // Constains forbs + succulents (L0) - RelAbundanceL1Expected[grassesIndexL1] = 0.; - RelAbundanceL1Expected[bareGroundL1] = 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, @@ -1012,18 +1051,35 @@ namespace { // Loop through RelAbundanceL1 and test results for(index = 0; index < 5; index++) { - if(index != grassesIndexL1) { - EXPECT_DOUBLE_EQ(RelAbundanceL1[index], RelAbundanceL1Expected[index]); - } else { - EXPECT_NEAR(RelAbundanceL1[grassesIndexL1], SumGrassesFraction, tol6); - } + 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 `SumGrassFraction`, 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} @@ -1047,37 +1103,23 @@ namespace { ``` */ - 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; - SumGrassesFraction = .5; - - RelAbundanceL0Expected[succIndex] = 0.; - RelAbundanceL0Expected[forbIndex] = 0.; - RelAbundanceL0Expected[C3Index] = .5; + // Set or calculate expected outputs + copyL0(RelAbundanceL0Expected, inputValues); + RelAbundanceL0Expected[C3Index] = 0.5; RelAbundanceL0Expected[C4Index] = 0.; - RelAbundanceL0Expected[grassAnn] = 0.; - RelAbundanceL0Expected[shrubIndex] = 0.; - RelAbundanceL0Expected[treeIndex] = 0.; - RelAbundanceL0Expected[bareGround] = .5; - RelAbundanceL1Expected[treeIndexL1] = 0.; - RelAbundanceL1Expected[shrubIndexL1] = 0.; - RelAbundanceL1Expected[forbIndexL1] = 0.; // Constains forbs + succulents (L0) - RelAbundanceL1Expected[grassesIndexL1] = .5; - RelAbundanceL1Expected[bareGroundL1] = .5; + 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]); @@ -1085,13 +1127,19 @@ namespace { // Loop through RelAbundanceL1 and test results for(index = 0; index < 5; index++) { - if(index != grassesIndexL1) { - EXPECT_DOUBLE_EQ(RelAbundanceL1[index], RelAbundanceL1Expected[index]); - } else { - EXPECT_NEAR(RelAbundanceL1[grassesIndexL1], SumGrassesFraction, tol6); - } + 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 allocDeallocClimateStructs(deallocate, 31, &climateOutput, &climateAverages); From c3b40fc96faae51b852bc0de8cac453532758903 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 18 Nov 2022 01:04:07 -0700 Subject: [PATCH 194/326] Updated `estimatePotNatVegComposition()` documentation - Appended to "inputValues" documentation to explain how SW_MISSING is handled, and that a value of 0-1 means the value is fixed - Appended to "SumGrassesFraction" documentation to elaborate on how a value that isn't SW_MISSING is handled including that grasses are estimated relative to that value --- SW_VegProd.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index aa055939a..ee3e6fb4a 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -960,11 +960,14 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe @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 starting values for the function to start with. - The elements of compositions are: 0) Succulents 1) Forbs 2) C3 3) C4 4) Grass Annuals 5) Shrubs 6) Trees 7) Bare ground + @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 grass if user would like it to be fixed + @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 From dc837f695071d930edcd42b167f15255c47c279d Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 18 Nov 2022 01:27:16 -0700 Subject: [PATCH 195/326] Added new death test - Created a new death test handling when SumGrassesFraction is added to the initial input sum and is greater than 1 - Added documentation specifying what the two different death cases are doing --- test/test_SW_VegProd.cc | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/test_SW_VegProd.cc b/test/test_SW_VegProd.cc index 2a2a390fc..987be6588 100644 --- a/test/test_SW_VegProd.cc +++ b/test/test_SW_VegProd.cc @@ -1191,6 +1191,39 @@ namespace { // 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);, + "" + ); + + /* =============================================================== + 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, From 1afc73dbcf1c8a3642e70ac80f069052b426c5e2 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 18 Nov 2022 01:30:04 -0700 Subject: [PATCH 196/326] Fixed unit tests/documentation - Fixed "SumGrassFraction" to "SumGrassesFraction" where needed - Changed SW_MISSING with 0.0 when needed (within lines 283-289), fixing a failing test --- test/test_SW_VegProd.cc | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/test_SW_VegProd.cc b/test/test_SW_VegProd.cc index 987be6588..9dc6a0009 100644 --- a/test/test_SW_VegProd.cc +++ b/test/test_SW_VegProd.cc @@ -283,10 +283,10 @@ namespace { inputValues[forbIndex] = SW_MISSING; inputValues[C3Index] = SW_MISSING; inputValues[C4Index] = SW_MISSING; - inputValues[grassAnn] = SW_MISSING; + inputValues[grassAnn] = 0.; inputValues[shrubIndex] = SW_MISSING; - inputValues[treeIndex] = SW_MISSING; - inputValues[bareGround] = 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) @@ -991,7 +991,7 @@ namespace { /* =============================================================== 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 `SumGrassFraction` + with the rest being SW_MISSING (C3 and C4 values), and `SumGrassesFraction` is set to 0 =============================================================== */ @@ -1065,7 +1065,7 @@ namespace { /* =============================================================== - Test when input sum is 1, including `SumGrassFraction`, and + Test when input sum is 1, including `SumGrassesFraction`, and grass needs to be estimated =============================================================== */ @@ -1231,6 +1231,8 @@ namespace { fixBareGround, grassOutput, RelAbundanceL0, RelAbundanceL1);, "" ); + + // Free allocated data allocDeallocClimateStructs(deallocate, 31, &climateOutput, &climateAverages); } From 4a7b06dfdbe9229c5cce15bd62eb15e1e2caf640 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 18 Nov 2022 09:29:45 -0500 Subject: [PATCH 197/326] `estimatePotNatVegComposition()`: handle cover types without implemented equations - documented behavior: "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)." -> explicitly set cover of trees, annual grasses, or bare ground to 0 if their input is `SW_MISSING` (user request to estimate) -> this addresses failed unit test identified by commit 3e01bb6d0c646b93cfadfd6c0fd8410e5cfcf249 (2022-Nov-16) and by comment https://github.com/DrylandEcology/SOILWAT2/pull/321/files/cdbfa8b929a1c6b34e9a1c94d9b3c5e0a483c326#r1024294338 --- SW_VegProd.c | 23 +++++++++++++++++++++-- test/test_SW_VegProd.cc | 6 +++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index ee3e6fb4a..e99ed1529 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -1000,6 +1000,8 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT 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, @@ -1009,6 +1011,23 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT 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])) { @@ -1048,7 +1067,7 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT // Check if there is only one grass index to be estimated if(grassEstimSize == 1) { - + // Set element to SumGrassesFraction - inputSumGrasses inputValues[grassesEstim[0]] = SumGrassesFraction - inputSumGrasses; @@ -1298,7 +1317,7 @@ void estimatePotNatVegComposition(double meanTemp_C, double PPT_cm, double meanT fixedValuesSum += inputValues[index]; } } - + // Include fixed grass sum if not missing if(fixSumGrasses && grassEstimSize > 0) { fixedValuesSum += totalSumGrasses; diff --git a/test/test_SW_VegProd.cc b/test/test_SW_VegProd.cc index 9dc6a0009..be30a8f74 100644 --- a/test/test_SW_VegProd.cc +++ b/test/test_SW_VegProd.cc @@ -283,10 +283,10 @@ namespace { inputValues[forbIndex] = SW_MISSING; inputValues[C3Index] = SW_MISSING; inputValues[C4Index] = SW_MISSING; - inputValues[grassAnn] = 0.; + inputValues[grassAnn] = SW_MISSING; inputValues[shrubIndex] = SW_MISSING; - inputValues[treeIndex] = 0.; - inputValues[bareGround] = 0.; + 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) From 211ae558df725634969fe2b1528491b32b4ca65d Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 18 Nov 2022 09:39:04 -0500 Subject: [PATCH 198/326] New simulation test with vegetation estimated from climate - "WaterBalanceTest" that turns on estimation of vegetation from climate (currently vegetation cover) --- test/test_WaterBalance.cc | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/test_WaterBalance.cc b/test/test_WaterBalance.cc index 4c6677d1f..c1e8f6657 100644 --- a/test/test_WaterBalance.cc +++ b/test/test_WaterBalance.cc @@ -199,4 +199,27 @@ namespace { } + 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(); + } + } // namespace From ab7c3eeda124bdf69983deb45d37593db5e34abd Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 18 Nov 2022 09:44:56 -0500 Subject: [PATCH 199/326] Clarify description of `veg_method` in "veg.in" input file --- testing/Input/veg.in | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/testing/Input/veg.in b/testing/Input/veg.in index b08f58af7..0bf64c419 100755 --- a/testing/Input/veg.in +++ b/testing/Input/veg.in @@ -6,11 +6,12 @@ # USER: Most of the other values in this file are parameters that # 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 - Read in the values from file - # 1 - Estimate fixed vegetation composition (fractional cover) from long-term climate conditions +# 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 From f91298639db9927f4d7ff90af77af8fedfb84d2f Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sat, 19 Nov 2022 20:27:13 -0700 Subject: [PATCH 200/326] Modified climate structs' documentation - Appended to the documentation of variables with names with "7thMon" or "2ndMon" to mention the targeted months in the two hemispheres - Removed all but one mention of units of a variable within the documentation * For the most part, SW_CLIMATE_YEARLY contained units in documentation and SW_CLIMATE_CLIM did not * Variables already have their unit at the end of their name * Kept unit specification in the documentation for "ddAbove65F_degday" for a bit more explanation on unit * Added a note for the structs pointing out the unit of data are within the variables' name --- SW_Weather.h | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/SW_Weather.h b/SW_Weather.h index e64baea05..3353d812c 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -64,21 +64,25 @@ typedef struct { @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. + @note Units of data are embedded within the variable name. */ 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 (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 temperature [C] of the driest quarter of the year*/ - *minTemp2ndMon_C, /**< Array containing the mean daily minimum temperature in February [C] */ - *minTemp7thMon_C, /**< Array containing minimum July temperatures [C] */ - - *frostFree_days, /**< Array containing the maximum consecutive days [-] without frost*/ + RealD **PPTMon_cm, /**< 2D array containing monthly amount precipitation*/ + *PPT_cm, /**< Array containing annual precipitation amount*/ + *PPT7thMon_mm, /**< Array containing July precipitation amount in July (northern hemisphere) + or January (southern hemisphere) */ + + **meanTempMon_C, /**< 2D array containing monthly mean average daily air temperature*/ + **maxTempMon_C, /**< 2D array containing monthly mean max daily air temperature*/ + **minTempMon_C, /**< 2D array containing monthly mean min daily air temperature*/ + *meanTemp_C, /**< Array containing annual mean temperatures*/ + *meanTempDriestQtr_C, /**< Array containing the average temperatureof the driest quarter of the year*/ + *minTemp2ndMon_C, /**< Array containing the mean daily minimum temperature in August (southern hemisphere) + or February (northern hemisphere)*/ + *minTemp7thMon_C, /**< Array containing minimum July temperatures in July (northern hisphere) + or Janurary (southern hemisphere)*/ + + *frostFree_days, /**< Array containing the maximum consecutive days without frost*/ *ddAbove65F_degday; /**< Array containing the amount of degree days [C x day] above 65 F */ } SW_CLIMATE_YEARLY; @@ -88,6 +92,7 @@ typedef struct { @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()`. + @note Units of data are embedded within the variable name. */ typedef struct { RealD *meanTempMon_C, /**< Array of size MAX_MONTHS containing sum of monthly mean temperatures*/ @@ -100,12 +105,15 @@ typedef struct { temperature of dry quarter (1), mean minimum temperature of February (2)*/ meanTemp_C, /**< Value containing the average of yearly temperatures*/ PPT_cm, /**< Value containing the average of yearly precipitation*/ - PPT7thMon_mm, /**< Value containing average of July precipitation (mm)*/ + PPT7thMon_mm, /**< Value containing average precipitation in July (northern hemisphere) + or January (southern hemisphere)*/ meanTempDriestQtr_C, /**< Value containing average of mean temperatures in the driest quarters of years*/ - minTemp2ndMon_C, /**< Value containing average of minimum temperatures in February*/ + minTemp2ndMon_C, /**< Value containing average of minimum temperatures in August (southern hemisphere) or + February (northern hemisphere)*/ ddAbove65F_degday, /**< Value containing average of total degrees above 65F (18.33C) throughout the year*/ frostFree_days, /**< Value containing average of most consectutive days in a year without frost*/ - minTemp7thMon_C; /**< Value containing the average of lowest temperature in July*/ + minTemp7thMon_C; /**< Value containing the average of lowest temperature in July (northern hisphere) + or Janurary (southern hemisphere)*/ } SW_CLIMATE_CLIM; typedef struct { From 7ad9bd57ced58699133f81a8de5b6919600d1926 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sat, 19 Nov 2022 20:40:01 -0700 Subject: [PATCH 201/326] R snippets use deprecated function and multi-line comments --- test/test_SW_Weather.cc | 118 ++++++++++++++++++++++------------------ 1 file changed, 64 insertions(+), 54 deletions(-) diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 4aa42b129..6e18270f0 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -183,16 +183,18 @@ namespace { // --- Annual time-series of climate variables ------ // Here, check values for 1980 - // Expect identical output to rSOILWAT2 (e.g., v5.3.1) - // ```{r} - // rSOILWAT2::calc_SiteClimate( - // weatherList = rSOILWAT2::get_WeatherHistory( - // rSOILWAT2::sw_exampleData - // )[1], - // do_C4vars = TRUE, - // do_Cheatgrass_ClimVars = TRUE - // ) - // ``` + /* 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); @@ -217,17 +219,19 @@ namespace { 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) - // ```{r} - // rSOILWAT2::calc_SiteClimate( - // weatherList = rSOILWAT2::get_WeatherHistory( - // rSOILWAT2::sw_exampleData - // ), - // do_C4vars = TRUE, - // do_Cheatgrass_ClimVars = TRUE - // ) - // ``` + /* --- 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); @@ -283,16 +287,18 @@ namespace { // ------ Check climate variables for one year of default weather ------ - // Expect identical output to rSOILWAT2 (e.g., v5.3.1) - // ```{r} - // rSOILWAT2::calc_SiteClimate( - // weatherList = rSOILWAT2::get_WeatherHistory( - // rSOILWAT2::sw_exampleData - // )[1], - // do_C4vars = TRUE, - // do_Cheatgrass_ClimVars = TRUE - // ) - // ``` + /* 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); @@ -420,17 +426,19 @@ namespace { // --- 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) - // ```{r} - // rSOILWAT2::calc_SiteClimate( - // weatherList = rSOILWAT2::get_WeatherHistory( - // rSOILWAT2::sw_exampleData - // )[1], - // do_C4vars = TRUE, - // do_Cheatgrass_ClimVars = TRUE, - // latitude = -10 - // ) - // ``` + /* 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); @@ -455,18 +463,20 @@ namespace { 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 - // ```{r} - // rSOILWAT2::calc_SiteClimate( - // weatherList = rSOILWAT2::get_WeatherHistory( - // rSOILWAT2::sw_exampleData - // ), - // do_C4vars = TRUE, - // do_Cheatgrass_ClimVars = TRUE, - // latitude = -10 - // ) - // ``` + /* --- 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); From fcbbafea5426cb248c8475fbbd8858cd40d62e54 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sat, 19 Nov 2022 21:25:28 -0700 Subject: [PATCH 202/326] Split `allocDeallocClimateStructs()` into two functions - Created `allocateClimateStructs()` and `deallocateClimateStructs()` to specifically allocate or deallocate climate structs - Allows the removal of "allocate = 1;" and "deallocate = 0;" lines of code in current and future uses --- SW_Weather.c | 122 ++++++++++++++++++++++++++------------------------- SW_Weather.h | 6 ++- 2 files changed, 66 insertions(+), 62 deletions(-) diff --git a/SW_Weather.c b/SW_Weather.c index ef3018dea..4f9191622 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -1217,68 +1217,70 @@ void _read_weather_hist( fclose(f); } -void allocDeallocClimateStructs(int action, int numYears, SW_CLIMATE_YEARLY *climateOutput, - SW_CLIMATE_CLIM *climateAverages) { - - int deallocate = 0, month; - - if(action == deallocate) { - - 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]); - } +void allocateClimateStructs(int numYears, SW_CLIMATE_YEARLY *climateOutput, + SW_CLIMATE_CLIM *climateAverages) { - free(climateOutput->PPTMon_cm); - free(climateOutput->meanTempMon_C); - free(climateOutput->maxTempMon_C); - free(climateOutput->minTempMon_C); - } else { - 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); + 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 diff --git a/SW_Weather.h b/SW_Weather.h index 3353d812c..25b99f1c2 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -183,8 +183,10 @@ void findDriestQtr(int numYears, Bool inNorthHem, double *meanTempDriestQtr_C, void driestQtrSouthAdjMonYears(int month, int *adjustedYearZero, int *adjustedYearOne, int *adjustedYearTwo, int *adjustedMonth, int *prevMonth, int *nextMonth); -void allocDeallocClimateStructs(int action, int numYears, SW_CLIMATE_YEARLY *climateOutput, - SW_CLIMATE_CLIM *climateAverages); +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, From dee027253e3a0722e397a491b235f55a855515bb Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sat, 19 Nov 2022 21:25:47 -0700 Subject: [PATCH 203/326] Tests now use split `allocDeallocClimateStructs()` functions --- test/test_SW_Weather.cc | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/test/test_SW_Weather.cc b/test/test_SW_Weather.cc index 6e18270f0..5a3890e4d 100644 --- a/test/test_SW_Weather.cc +++ b/test/test_SW_Weather.cc @@ -166,15 +166,12 @@ namespace { // This test relies on allHist from `SW_WEATHER` being already filled SW_CLIMATE_YEARLY climateOutput; SW_CLIMATE_CLIM climateAverages; - - int deallocate = 0; - int allocate = 1; Bool inNorthHem = swTRUE; // Allocate memory // 31 = number of years used in test - allocDeallocClimateStructs(allocate, 31, &climateOutput, &climateAverages); + allocateClimateStructs(31, &climateOutput, &climateAverages); // ------ Check climate variables for default weather ------ @@ -265,7 +262,7 @@ namespace { // ------ Reset and deallocate - allocDeallocClimateStructs(deallocate, 31, &climateOutput, &climateAverages); + deallocateClimateStructs(&climateOutput, &climateAverages); } @@ -275,15 +272,12 @@ namespace { // This test relies on allHist from `SW_WEATHER` being already filled SW_CLIMATE_YEARLY climateOutput; SW_CLIMATE_CLIM climateAverages; - - int deallocate = 0; - int allocate = 1; Bool inNorthHem = swTRUE; // Allocate memory // 1 = number of years used in test - allocDeallocClimateStructs(allocate, 1, &climateOutput, &climateAverages); + allocateClimateStructs(1, &climateOutput, &climateAverages); // ------ Check climate variables for one year of default weather ------ @@ -386,7 +380,7 @@ namespace { // ------ Reset and deallocate - allocDeallocClimateStructs(deallocate, 1, &climateOutput, &climateAverages); + deallocateClimateStructs(&climateOutput, &climateAverages); } @@ -409,15 +403,12 @@ namespace { SW_CLIMATE_YEARLY climateOutput; SW_CLIMATE_CLIM climateAverages; - int deallocate = 0; - int allocate = 1; - // "South" and not "North" to reduce confusion when calling `calcSiteClimate()` Bool inSouthHem = swFALSE; // Allocate memory // 31 = number of years used in test - allocDeallocClimateStructs(allocate, 31, &climateOutput, &climateAverages); + allocateClimateStructs(31, &climateOutput, &climateAverages); // ------ Check climate variables for default weather ------ @@ -510,7 +501,7 @@ namespace { // ------ Reset and deallocate - allocDeallocClimateStructs(deallocate, 31, &climateOutput, &climateAverages); + deallocateClimateStructs(&climateOutput, &climateAverages); } @@ -519,14 +510,11 @@ namespace { SW_CLIMATE_YEARLY climateOutput; SW_CLIMATE_CLIM climateAverages; SW_WEATHER_HIST **allHist; - - int allocate = 1; - int deallocate = 0; - + Bool inNorthHem = swTRUE; // Allocate memory - allocDeallocClimateStructs(allocate, 2, &climateOutput, &climateAverages); + allocateClimateStructs(2, &climateOutput, &climateAverages); allHist = (SW_WEATHER_HIST **)malloc(sizeof(SW_WEATHER_HIST *) * 2); @@ -611,7 +599,7 @@ namespace { // ------ Reset and deallocate - allocDeallocClimateStructs(deallocate, 2, &climateOutput, &climateAverages); + deallocateClimateStructs(&climateOutput, &climateAverages); for (int year = 0; year < 2; year++) { free(allHist[year]); From a5e8ce83a94524f811bac130403553a759c7ccb9 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sat, 19 Nov 2022 21:51:56 -0700 Subject: [PATCH 204/326] A fixed mistake within `veg.in` - The program tries to read a line of text that is not commented out, so it is interpreting it as values for vegetation type values, causing an error --- testing/Input/veg.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/Input/veg.in b/testing/Input/veg.in index 0bf64c419..0bde58b47 100755 --- a/testing/Input/veg.in +++ b/testing/Input/veg.in @@ -11,7 +11,7 @@ #---- 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) + # (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 From b035816e00803f662229dff6767ce38165d4da90 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 20 Nov 2022 00:49:53 -0700 Subject: [PATCH 205/326] Veg estimation/tests use de/allocateClimateStructs() --- SW_VegProd.c | 7 +++---- test/test_SW_VegProd.cc | 22 +++++++--------------- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index e99ed1529..0b3f887b7 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -880,8 +880,7 @@ void get_critical_rank(void){ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYear, int veg_method, double latitude) { - int numYears = endYear - startYear + 1, deallocate = 0, allocate = 1, k, - bareGroundIndex = 7; + int numYears = endYear - startYear + 1, k, bareGroundIndex = 7; SW_CLIMATE_YEARLY climateOutput; SW_CLIMATE_CLIM climateAverages; @@ -903,7 +902,7 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe } // Allocate climate structs' memory - allocDeallocClimateStructs(allocate, numYears, &climateOutput, &climateAverages); + allocateClimateStructs(numYears, &climateOutput, &climateAverages); calcSiteClimate(SW_Weather.allHist, numYears, startYear, inNorthHem, &climateOutput); @@ -929,7 +928,7 @@ void estimateVegetationFromClimate(SW_VEGPROD *vegProd, int startYear, int endYe } // Deallocate climate structs' memory - allocDeallocClimateStructs(deallocate, numYears, &climateOutput, &climateAverages); + deallocateClimateStructs(&climateOutput, &climateAverages); } diff --git a/test/test_SW_VegProd.cc b/test/test_SW_VegProd.cc index be30a8f74..24239cf0e 100644 --- a/test/test_SW_VegProd.cc +++ b/test/test_SW_VegProd.cc @@ -248,8 +248,6 @@ namespace { Bool fixBareGround = swTRUE; int nTypes = 8; - int deallocate = 0; - int allocate = 1; int index; @@ -262,7 +260,7 @@ namespace { SW_WTH_read(); // Allocate arrays needed for `calcSiteClimate()` and `averageClimateAcrossYears()` - allocDeallocClimateStructs(allocate, 31, &climateOutput, &climateAverages); + allocateClimateStructs(31, &climateOutput, &climateAverages); // Calculate climate of the site and add results to "climateOutput" calcSiteClimate(SW_Weather.allHist, 31, 1980, inNorthHem, &climateOutput); @@ -671,7 +669,7 @@ namespace { // Deallocate structs - allocDeallocClimateStructs(deallocate, 31, &climateOutput, &climateAverages); + deallocateClimateStructs(&climateOutput, &climateAverages); } TEST(EstimateVegetationTest, FullVegetation) { @@ -712,16 +710,13 @@ namespace { Bool inNorthHem = swTRUE; Bool warnExtrapolation = swTRUE; Bool fixBareGround = swTRUE; - - int deallocate = 0; - int allocate = 1; - + // Reset "SW_Weather.allHist" SW_WTH_read(); // Allocate arrays needed for `calcSiteClimate()` and `averageClimateAcrossYears()` - allocDeallocClimateStructs(allocate, 31, &climateOutput, &climateAverages); + allocateClimateStructs(31, &climateOutput, &climateAverages); // Calculate climate of the site and add results to "climateOutput" calcSiteClimate(SW_Weather.allHist, 31, 1980, inNorthHem, &climateOutput); @@ -1141,7 +1136,7 @@ namespace { // Deallocate structs - allocDeallocClimateStructs(deallocate, 31, &climateOutput, &climateAverages); + deallocateClimateStructs(&climateOutput, &climateAverages); } @@ -1163,9 +1158,6 @@ namespace { Bool warnExtrapolation = swTRUE; Bool fixBareGround = swTRUE; - int allocate = 1; - int deallocate = 0; - double inputValues[8] = {.0567, .5, .0392, .0981, .3218, .0827, .1293, .0405}; double shrubLimit = .2; @@ -1183,7 +1175,7 @@ namespace { SW_WTH_read(); // Allocate arrays needed for `calcSiteClimate()` and `averageClimateAcrossYears()` - allocDeallocClimateStructs(allocate, 31, &climateOutput, &climateAverages); + allocateClimateStructs(31, &climateOutput, &climateAverages); // Calculate climate of the site and add results to "climateOutput" calcSiteClimate(SW_Weather.allHist, 31, 1980, inNorthHem, &climateOutput); @@ -1233,7 +1225,7 @@ namespace { ); // Free allocated data - allocDeallocClimateStructs(deallocate, 31, &climateOutput, &climateAverages); + deallocateClimateStructs(&climateOutput, &climateAverages); } From 70d280726242d3ccd7107d0394a53046491270b2 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Mon, 21 Nov 2022 16:22:42 -0700 Subject: [PATCH 206/326] Backtracked documentation of climate structs - Previously, the units within the two climate structs, SW_CLIMATE_YEARLY and SW_CLIMATE_CLIM, were deleted, which was found not to be preferable - Documentation was backtracked to the most recent version that contained units within SW_CLIMATE_YEARLY - Units within SW_CLIMATE_CLIM were added to add consistency as the deletion of units was meant to do - Units within SW_CLIMATE_CLIM documentation mirror the units in the variable name, even for averages. * For example, the unit for "meanTemp_C" in the documentation is [C] and not [C / year] --- SW_Weather.h | 62 +++++++++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/SW_Weather.h b/SW_Weather.h index 25b99f1c2..ef2649344 100644 --- a/SW_Weather.h +++ b/SW_Weather.h @@ -64,26 +64,25 @@ typedef struct { @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. - @note Units of data are embedded within the variable name. */ typedef struct { - RealD **PPTMon_cm, /**< 2D array containing monthly amount precipitation*/ - *PPT_cm, /**< Array containing annual precipitation amount*/ + 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) */ + or January (southern hemisphere) [mm]*/ - **meanTempMon_C, /**< 2D array containing monthly mean average daily air temperature*/ - **maxTempMon_C, /**< 2D array containing monthly mean max daily air temperature*/ - **minTempMon_C, /**< 2D array containing monthly mean min daily air temperature*/ - *meanTemp_C, /**< Array containing annual mean temperatures*/ - *meanTempDriestQtr_C, /**< Array containing the average temperatureof the driest quarter of the year*/ + **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)*/ + or February (northern hemisphere) [C]*/ *minTemp7thMon_C, /**< Array containing minimum July temperatures in July (northern hisphere) - or Janurary (southern hemisphere)*/ + or Janurary (southern hemisphere) [C]*/ - *frostFree_days, /**< Array containing the maximum consecutive days without frost*/ - *ddAbove65F_degday; /**< Array containing the amount of degree days [C x day] above 65 F */ + *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; /** @@ -92,28 +91,31 @@ typedef struct { @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()`. - @note Units of data are embedded within the variable name. */ typedef struct { - RealD *meanTempMon_C, /**< Array of size MAX_MONTHS containing sum of monthly mean temperatures*/ - *maxTempMon_C, /**< Array of size MAX_MONTHS containing sum of monthly maximum temperatures*/ - *minTempMon_C, /**< Array of size MAX_MONTHS containing sum of monthly minimum temperatures*/ - *PPTMon_cm, /**< Array of size MAX_MONTHS containing sum of monthly mean precipitation*/ - *sdC4, /**< Array of size three holding the standard deviations of minimum July temperature (0), - frost free days (1), number of days above 65F (2)*/ - *sdCheatgrass, /**< Array of size three holding the standard deviations of July precipitation (0), mean - temperature of dry quarter (1), mean minimum temperature of February (2)*/ - meanTemp_C, /**< Value containing the average of yearly temperatures*/ - PPT_cm, /**< Value containing the average of yearly precipitation*/ + 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)*/ - meanTempDriestQtr_C, /**< Value containing average of mean temperatures in the driest quarters of years*/ + 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)*/ - ddAbove65F_degday, /**< Value containing average of total degrees above 65F (18.33C) throughout the year*/ - frostFree_days, /**< Value containing average of most consectutive days in a year without frost*/ + 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)*/ + or Janurary (southern hemisphere) [C]*/ } SW_CLIMATE_CLIM; typedef struct { From 3d3d30cb76a7556d8ebd5f8a5f5705b694ec4503 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Mon, 28 Nov 2022 09:05:22 -0500 Subject: [PATCH 207/326] Reformat and clarify NEWS --- NEWS.md | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/NEWS.md b/NEWS.md index 0f13e3f16..2fc5507ab 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,24 +1,31 @@ # NEWS # SOILWAT2 v7.0.0-9000 -* The type of soil density is a new input. - Soil density inputs can now represent either matric or bulk density; - the code converts automatically as needed (issue #280; @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`. ## Changes to inputs -* File `siteparam.in` gained new input for `type_soilDensityInput`. +* SOILWAT2 gains `type_soilDensityInput` as new user input (`siteparam.in`) + with default value 0 (matric soil density) that reproduces previous behavior. # 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. From 622426fcc14e4a1ed1a99f063769e61c3a1762f2 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Tue, 29 Nov 2022 15:30:33 -0500 Subject: [PATCH 208/326] More detailed NEWS entries for feature_veg_estimation - More detailed entries for developments towards issues #311 (daily weather), #317 (climate predictors), and #318 (estimate fractional cover of vegetation types from climate conditions) --- NEWS.md | 61 +++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/NEWS.md b/NEWS.md index ee15686c7..a0e1de658 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,17 +5,62 @@ (issue #280; @dschlaep). * Automatic conversion between matric and bulk density as needed using the new input `type_soilDensityInput`. -* Moved `calc_SiteClimate()` and `estimate_PotNatVeg_composition()` - functionality from rSOILWAT2 to SOILWAT2. -* Yesterday values for variables in SW_WEATHER_2DAYS struct are no longer used, - which motivated the struct being renamed to SW_WEATHER_NOW. -* Weather is now read in all at once, instead of reading one year at a time, by the new function - `readAllWeather()`. + +* 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()`. + +* 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. + * Variables `Month7th_PPT_mm` 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 * SOILWAT2 gains `type_soilDensityInput` as new user input (`siteparam.in`) - with default value 0 (matric soil density) that reproduces previous behavior. -* SOILWAT2 gains `veg_method` as new user input (`"veg.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.6.0 From abcea9e59339d35a2f53af5ff04f3cabf735255d Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 30 Nov 2022 14:28:13 -0500 Subject: [PATCH 209/326] Fix script that creates doxygen documentation and reports warnings/errors - close #301 (CI: documentation checks fail on github actions) - the problem arose * because commit 3ecbdc9d0ab7bd7e668dbcdbe8725331591d1d1d removed all exceptions from `doc/doxygen_exceptions.txt` (doxygen warnings not to be reported by the script) and * because the script `tools/run_doxygen.sh` incorrectly tested whether the file listing exceptions was empty (it reported an empty file but with return characters as not empty) by using `wc -l` - As a consequence, the script removed all content of the doxygen log file (in error): grep was set up to match everything if the pattern inputs was empty or contained an empty line -> now, the script `tools/run_doxygen.sh` correctly identifies if `doc/doxygen_exceptions.txt` contains exceptions or is empty (but for whitespace) by using `wc -w` (instead of `wc -l`) -> now, the script also sends an error with a hopefully meaningful message (instead of none) if the doxygen log file turns out to be empty --- tools/run_doxygen.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/run_doxygen.sh b/tools/run_doxygen.sh index 79215e9fa..5fc5cef11 100755 --- a/tools/run_doxygen.sh +++ b/tools/run_doxygen.sh @@ -32,14 +32,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 "")" From 01ef75a01c9171aca7c4308bcfa3999d9fbcff54 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 30 Nov 2022 14:29:03 -0500 Subject: [PATCH 210/326] Add NEWS and README to list of doxygen warnings that can be ignored --- doc/doxygen_exceptions.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 From 224f9e6e58752bf0398b65f9b98580f694f32cc9 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 30 Nov 2022 14:30:06 -0500 Subject: [PATCH 211/326] Fix documentation of `soil_temperature_today()` - added documentation for argument `depthsR` which was previously not documented --- SW_Flow_lib.c | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/SW_Flow_lib.c b/SW_Flow_lib.c index 8184a0fee..9c6215a28 100644 --- a/SW_Flow_lib.c +++ b/SW_Flow_lib.c @@ -1797,25 +1797,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. From 9bcc46bac0cef3182c8d889636736a70bd99b26e Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 30 Nov 2022 17:07:22 -0500 Subject: [PATCH 212/326] Fix equation in documentation of `RandBeta()` --- rands.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rands.c b/rands.c index f9a3e3605..2f171f619 100644 --- a/rands.c +++ b/rands.c @@ -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 From 9f7e034beb6feea3481e0713653117c978635d4c Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 30 Nov 2022 17:18:43 -0500 Subject: [PATCH 213/326] Fix GHA "check_documentation" - currently, github action `check_doc.yml` fails with message "Error: /undefined in getenv" - currently, github actions uses doxygen v1.8.17 and GPL Ghostscript 9.50 -> this error is documented for doxygen v1.8.17 and GPL Ghostscript 9.50 with https://github.com/doxygen/doxygen/issues/7484 - the bug in gs is avoided with doxygen v1.8.18 or by switching to MathJax -> here, attempt using MathJax --- doc/Doxyfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Doxyfile b/doc/Doxyfile index 36aeabcff..6b4faa526 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -1620,7 +1620,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 From b8f8524b3427f6b82565abf480cc72e7bd56e46d Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Thu, 1 Dec 2022 13:30:46 -0500 Subject: [PATCH 214/326] GHA: update actions to use node16 replacing deprecated node12 "Node.js 12 actions are deprecated. For more information see: https://github.blog/changelog/2022-09-22-github-actions-all-actions-will-begin-running-on-node16-instead-of-node12/. Please update the following actions to use Node.js 16: actions/checkout@v2, codecov/codecov-action@v2" -> https://github.com/actions/checkout/releases/tag/v3.0.0 -> https://github.com/codecov/codecov-action/releases/tag/v3.0.0 --- .github/workflows/check_doc.yml | 2 +- .github/workflows/main_nix.yml | 6 +++--- .github/workflows/main_win.yml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/check_doc.yml b/.github/workflows/check_doc.yml index 723d4cc20..ea7d132e3 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 diff --git a/.github/workflows/main_nix.yml b/.github/workflows/main_nix.yml index da0e8091f..0fbbbd997 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 @@ -50,7 +50,7 @@ jobs: steps: - name: Checkout repository and submodules - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: submodules: recursive @@ -58,6 +58,6 @@ jobs: run: make clean cov cov_run - 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 diff --git a/.github/workflows/main_win.yml b/.github/workflows/main_win.yml index 97b5424ac..e6af38842 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 From 6ce3a2a8cb74fbf348e1a41d1e9b874dac06f01d Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 2 Dec 2022 17:15:55 -0500 Subject: [PATCH 215/326] clang-15 warns more strictly for `unused-but-set-parameter` - `driestQtrSouthAdjMonYears()`: reads, sets, and returns parameters depending on time of year; some calls do not read some parameters * we can either dissolve this function as it is used only once or silence the compile warnings `-Wunused-but-set-parameter` -> silence warnings with coercion to void --- SW_Weather.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/SW_Weather.c b/SW_Weather.c index 4f9191622..511e97112 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -475,6 +475,11 @@ void driestQtrSouthAdjMonYears(int month, int *adjustedYearZero, int *adjustedYe *nextMonth = *adjustedMonth + 1; break; } + + /* coerce to void to silence `-Wunused-but-set-parameter` [clang-15] */ + (void) adjustedYearZero; + (void) adjustedYearOne; + (void) adjustedYearTwo; } From c302b52f09c0fe91e063217dbb1f8189d4d15184 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Tue, 6 Dec 2022 09:43:54 -0500 Subject: [PATCH 216/326] Attempt to fix codecov upload error error: ['error'] There was an error running the uploader: Error uploading to https://codecov.io: Error: There was an error fetching the storage URL during POST: 404 - {'detail': ErrorDetail(string='Unable to locate build via Github Actions API. Please upload with the Codecov repository upload token to resolve issue.', code='not_found')} - One suggested remedy is to send the token even for public repositories (see codecov/codecov-action#557) * yaml script now pass token to codecov-action * set token in webpage github.com > Settings > Secrets --- .github/workflows/main_nix.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main_nix.yml b/.github/workflows/main_nix.yml index 0fbbbd997..f6f4122d1 100644 --- a/.github/workflows/main_nix.yml +++ b/.github/workflows/main_nix.yml @@ -61,3 +61,4 @@ jobs: uses: codecov/codecov-action@v3 with: fail_ci_if_error: false # just report, don't fail checks + token: ${{ secrets.CODECOV_TOKEN }} From 9789a380849864c535226ffe64a2dd5d07121fb5 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Tue, 6 Dec 2022 10:53:32 -0500 Subject: [PATCH 217/326] Add `SW_Flow_lib_PET.c` to code coverage report --- tools/run_gcov.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/run_gcov.sh b/tools/run_gcov.sh index fcaac86c9..57d76b825 100755 --- a/tools/run_gcov.sh +++ b/tools/run_gcov.sh @@ -4,7 +4,7 @@ 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 From a01aaec4a1071491dad55a8f6503b63ef6b98ffc Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 7 Dec 2022 17:18:04 -0500 Subject: [PATCH 218/326] Clarify NEWS entry for issue #317: new `calcSiteClimate()` --- NEWS.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index a0e1de658..9bcfcaa9d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -30,8 +30,9 @@ 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. - * Variables `Month7th_PPT_mm` and `MinTemp_of2ndMonth_C` are now adjusted - for location by hemisphere. + * 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 From 089519e88cf718cde00f57b302acc0aa39bfd0d8 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Thu, 8 Dec 2022 09:07:23 -0500 Subject: [PATCH 219/326] Fix documentation - use correct citation from "wang2022wrr" to "wang2022WRRa" - fix links to group "swrc_pdf" - fix documentation of `swc_min` in input file "siteparam.in" to prevent "preprocessing issue while doing constant expression evaluation: syntax error" with doxygen v1.9.3 --- SW_Site.c | 6 +++--- testing/Input/siteparam.in | 11 +++++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/SW_Site.c b/SW_Site.c index 6b82af563..717e5bd00 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -96,7 +96,7 @@ RealD @note Code maintenance: - Values must exactly match those provided in `siteparam.in`. - Order must exactly match "indices of `swrc2str`" - - See details in section #swrc_pdf + - See details in section \ref swrc_pdf */ char const *swrc2str[N_SWRCs] = { "Campbell1974", @@ -109,7 +109,7 @@ char const *swrc2str[N_SWRCs] = { @note Code maintenance: - Values must exactly match those provided in `siteparam.in`. - Order must exactly match "indices of `pdf2str`" - - See details in section #swrc_pdf + - See details in section \ref swrc_pdf - `rSOILWAT2` may implemented additional PDFs */ char const *pdf2str[N_PDFs] = { @@ -913,7 +913,7 @@ Bool SWRC_check_parameters_for_vanGenuchten1980(double *swrcp) { (\cite fredlund1994CGJa, \cite wang2018wrr, \cite rudiyanto2021G). Acceptable parameter values are partially based on - Table 1 in \cite wang2022wrr. + Table 1 in \cite wang2022WRRa. @param[in] *swrcp Vector of SWRC parameters diff --git a/testing/Input/siteparam.in b/testing/Input/siteparam.in index afe105af0..79764fb5d 100644 --- a/testing/Input/siteparam.in +++ b/testing/Input/siteparam.in @@ -9,12 +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 - # (from realistic SWP limit, or Rawls et al. 1985) -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 From e836ce3365293fdbe8eca37191a8b907cfcf0018 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Thu, 8 Dec 2022 14:14:02 -0500 Subject: [PATCH 220/326] Clarify representation of CO2 effects on tree biomass - close #330 (the issue raised was not a problem but on purpose) -> clarify CO2 effects on tree biomass: CO2 effects on tree biomass restricted to percent live biomass, i.e., total tree biomass is constant while live biomass is increasing -> simplify struct `VegType`: remove "CO2_biomass" and "CO2_pct_live" because they were used only in `SW_VPD_new_year()` --- SW_VegProd.c | 31 +++++++++++++++++++++---------- SW_VegProd.h | 5 ----- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/SW_VegProd.c b/SW_VegProd.c index 0b3f887b7..1f4265da5 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -679,6 +679,9 @@ void SW_VPD_new_year(void) { TimeInt doy; /* base1 */ int k; + double + biomass_after_CO2[MAX_MONTHS]; /* Monthly biomass after CO2 effects */ + // Grab the real year so we can access CO2 data ForEachVegType(k) @@ -687,19 +690,27 @@ void SW_VPD_new_year(void) { { 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); + // 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, 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); + // 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, v->veg[k].biomass_daily); interpolate_monthlyValues(v->veg[k].pct_live, v->veg[k].pct_live_daily); } diff --git a/SW_VegProd.h b/SW_VegProd.h index 9c71a25b8..530dd267c 100644 --- a/SW_VegProd.h +++ b/SW_VegProd.h @@ -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]; From a26f1f1438d0592d0a56223b193b65d82dd9fb44 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Thu, 15 Dec 2022 14:05:28 -0500 Subject: [PATCH 221/326] clang-15 warns more strictly for `strict-prototypes` - addressing "warning: a function declaration without a prototype is deprecated in all versions of C [-Wstrict-prototypes]" -> explicitly state that function takes zero arguments (i.e., void) Note: the same warning occurs in "pcg" submodule ``` pcg/pcg_basic.c:69:22: warning: a function declaration without a prototype is deprecated in all versions of C [-Wstrict-prototypes] uint32_t pcg32_random() ``` --- SW_Site.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SW_Site.c b/SW_Site.c index 088beeaa3..b0b59fe13 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -418,7 +418,7 @@ RealD calculate_soilMatricDensity(RealD bulkDensity, RealD fractionGravel) { The count stops at first layer with 0. */ -LyrIndex nlayers_bsevap() { +LyrIndex nlayers_bsevap(void) { SW_SITE *v = &SW_Site; LyrIndex s, n = 0; From 37ff21fea60542221639e72989baf380ac4c012f Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 16 Dec 2022 09:03:24 -0500 Subject: [PATCH 222/326] Replace `sprintf()` with `snprintf()` - motivated by upcoming changes in R-devel (>= v4.3) - see https://cran.r-project.org/doc/manuals/r-devel/NEWS.html: "The use of sprintf and vsprintf from C/C++ has been deprecated in macOS 13 and is a known security risk. R CMD check now reports (on all platforms) if their use is found in the compiled code: replace by snprintf or vsnprintf" -> see corresponding commit in rSOILWAT2 72845bb020a9c4d8cbe5a513f6a472af19d7c7d1 --- SW_Carbon.c | 18 ++++-- SW_Markov.c | 97 ++++++++++++++++++++++++++------ SW_Model.c | 2 +- SW_Output.c | 77 ++++++++++++++++--------- SW_Output.h | 2 +- SW_Output_get_functions.c | 114 ++++++++++++++++++++------------------ SW_Output_outtext.c | 46 ++++++++------- SW_Output_outtext.h | 2 +- SW_Sky.c | 2 +- SW_SoilWater.c | 6 +- SW_VegEstab.c | 79 +++++++++++++++----------- SW_VegProd.c | 85 ++++++++++++++++------------ SW_Weather.c | 30 +++++----- generic.h | 2 +- 14 files changed, 346 insertions(+), 216 deletions(-) diff --git a/SW_Carbon.c b/SW_Carbon.c index 1f62080fa..63cfff88b 100644 --- a/SW_Carbon.c +++ b/SW_Carbon.c @@ -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_Markov.c b/SW_Markov.c index 3fd4c5156..ee083faa9 100644 --- a/SW_Markov.c +++ b/SW_Markov.c @@ -382,8 +382,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: @@ -392,8 +397,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] @@ -401,18 +412,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: @@ -465,34 +490,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 @@ -500,9 +552,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: diff --git a/SW_Model.c b/SW_Model.c index b3bb257b6..c111c8e83 100644 --- a/SW_Model.c +++ b/SW_Model.c @@ -194,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/SW_Output.c index c6a3b481a..b5ae5605b 100644 --- a/SW_Output.c +++ b/SW_Output.c @@ -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 @@ -1947,7 +1947,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 last, char msg[], size_t sizeof_msg) { int res = 0; // return value indicating type of message if any @@ -1970,8 +1970,14 @@ int SW_OUT_read_onekey(OutKey k, OutSum sumtype, char period[], int first, { 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; } @@ -1997,8 +2003,13 @@ int SW_OUT_read_onekey(OutKey k, OutSum sumtype, char period[], int first, { 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); } @@ -2007,9 +2018,14 @@ int SW_OUT_read_onekey(OutKey k, OutSum sumtype, char period[], int first, { 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); } @@ -2019,8 +2035,14 @@ int SW_OUT_read_onekey(OutKey k, OutSum sumtype, char period[], int first, 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); } @@ -2159,10 +2181,15 @@ 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); + period, + first, + !Str_CompareI("END", (char *)last) ? 366 : atoi(last), + msg, + sizeof msg + ); if (msg_type != 0) { if (msg_type > 0) { @@ -2477,7 +2504,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]) { @@ -2548,9 +2575,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"); } diff --git a/SW_Output.h b/SW_Output.h index 75a489e03..e83539c10 100644 --- a/SW_Output.h +++ b/SW_Output.h @@ -247,7 +247,7 @@ 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 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_get_functions.c b/SW_Output_get_functions.c index 38b8de7d0..d31fdc482 100644 --- a/SW_Output_get_functions.c +++ b/SW_Output_get_functions.c @@ -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); } } @@ -978,7 +982,7 @@ void get_swpMatric_text(OutPeriod pd) val = SW_SWCbulk2SWPmatric(SW_Site.lyr[i]->fractionVolBulk_gravel, vo->swpMatric[i], i); - sprintf(str, "%c%.*f", _Sep, OUT_DIGITS, val); + snprintf(str, OUTSTRLEN, "%c%.*f", _Sep, OUT_DIGITS, val); strcat(sw_outstr, str); } } @@ -1058,7 +1062,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 +1139,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 +1215,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 +1277,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 +1358,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 +1367,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 +1500,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 +1572,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 +1662,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 +1746,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 +1811,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 +1888,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 +1897,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 +1996,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 +2094,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 +2175,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 +2184,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 +2273,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 +2336,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 +2397,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 +2426,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 +2450,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 +2483,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_outtext.c b/SW_Output_outtext.c index 83e6d9411..581ba1b30 100644 --- a/SW_Output_outtext.c +++ b/SW_Output_outtext.c @@ -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_Output_outtext.h b/SW_Output_outtext.h index 442148488..5eca8a185 100644 --- a/SW_Output_outtext.h +++ b/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_Sky.c b/SW_Sky.c index 1d39c8f97..d2d1d4ca3 100644 --- a/SW_Sky.c +++ b/SW_Sky.c @@ -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); } diff --git a/SW_SoilWater.c b/SW_SoilWater.c index 78c7388f0..0467818a9 100644 --- a/SW_SoilWater.c +++ b/SW_SoilWater.c @@ -125,7 +125,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], @@ -213,7 +213,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 +810,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); diff --git a/SW_VegEstab.c b/SW_VegEstab.c index 3347a774c..4d85f53c2 100644 --- a/SW_VegEstab.c +++ b/SW_VegEstab.c @@ -505,42 +505,55 @@ 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\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, 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\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, 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/SW_VegProd.c b/SW_VegProd.c index 1f4265da5..9096b7e20 100644 --- a/SW_VegProd.c +++ b/SW_VegProd.c @@ -99,7 +99,7 @@ void SW_VPD_read(void) { case 1: x = sscanf(inbuf, "%d", &veg_method); if(x != 1) { - sprintf(errstr, "ERROR: invalid record in vegetation type components in %s\n", MyFileName); + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in vegetation type components in %s\n", MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -110,7 +110,7 @@ void SW_VPD_read(void) { 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); + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in vegetation type components in %s\n", MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -125,7 +125,7 @@ void SW_VPD_read(void) { 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); + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in albedo values in %s\n", MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -140,7 +140,7 @@ void SW_VPD_read(void) { 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); + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in canopy xinflec in %s\n", MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -152,7 +152,7 @@ void SW_VPD_read(void) { 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); + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in canopy yinflec in %s\n", MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -164,7 +164,7 @@ void SW_VPD_read(void) { 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); + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in canopy range in %s\n", MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -176,7 +176,7 @@ void SW_VPD_read(void) { 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); + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in canopy slope in %s\n", MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -188,7 +188,7 @@ void SW_VPD_read(void) { 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); + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in canopy height constant option in %s\n", MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -202,7 +202,7 @@ void SW_VPD_read(void) { 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); + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in interception parameter kSmax(veg) in %s\n", MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -214,7 +214,7 @@ void SW_VPD_read(void) { 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); + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in interception parameter kdead(veg) in %s\n", MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -228,7 +228,7 @@ void SW_VPD_read(void) { 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); + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in litter interception parameter kSmax(litter) in %s\n", MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -242,7 +242,7 @@ void SW_VPD_read(void) { 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); + 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); } @@ -256,7 +256,7 @@ void SW_VPD_read(void) { 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); + 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); } @@ -270,7 +270,7 @@ void SW_VPD_read(void) { 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); + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in shade scale in %s\n", MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -282,7 +282,7 @@ void SW_VPD_read(void) { 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); + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in shade max dead biomass in %s\n", MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -294,7 +294,7 @@ void SW_VPD_read(void) { 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); + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in shade xinflec in %s\n", MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -306,7 +306,7 @@ void SW_VPD_read(void) { 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); + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in shade yinflec in %s\n", MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -318,7 +318,7 @@ void SW_VPD_read(void) { 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); + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in shade range in %s\n", MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -330,7 +330,7 @@ void SW_VPD_read(void) { 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); + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in shade slope in %s\n", MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -344,7 +344,7 @@ void SW_VPD_read(void) { 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); + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in hydraulic redistribution: flag in %s\n", MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -356,7 +356,7 @@ void SW_VPD_read(void) { 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); + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in hydraulic redistribution: maxCondroot in %s\n", MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -368,7 +368,7 @@ void SW_VPD_read(void) { 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); + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in hydraulic redistribution: swpMatric50 in %s\n", MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -380,7 +380,7 @@ void SW_VPD_read(void) { 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); + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in hydraulic redistribution: shapeCond in %s\n", MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -394,7 +394,7 @@ void SW_VPD_read(void) { 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); + snprintf(errstr, MAX_ERROR, "ERROR: invalid record in critical soil water potentials: flag in %s\n", MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -411,7 +411,7 @@ void SW_VPD_read(void) { 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); + snprintf(errstr, MAX_ERROR, "ERROR: Not enough arguments for CO2 Biomass Coefficient 1 in %s\n", MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -424,7 +424,7 @@ void SW_VPD_read(void) { 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); + snprintf(errstr, MAX_ERROR, "ERROR: Not enough arguments for CO2 Biomass Coefficient 2 in %s\n", MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -439,7 +439,7 @@ void SW_VPD_read(void) { 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); + snprintf(errstr, MAX_ERROR, "ERROR: Not enough arguments for CO2 WUE Coefficient 1 in %s\n", MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -452,7 +452,7 @@ void SW_VPD_read(void) { 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); + snprintf(errstr, MAX_ERROR, "ERROR: Not enough arguments for CO2 WUE Coefficient 2 in %s\n", MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -471,7 +471,7 @@ void SW_VPD_read(void) { 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); + snprintf(errstr, MAX_ERROR, "ERROR: invalid record %d in %s\n", mon + 1, MyFileName); CloseFile(&f); LogError(logfp, LOGFATAL, errstr); } @@ -506,7 +506,7 @@ void SW_VPD_read(void) { } if (mon < Dec) { - sprintf(errstr, "No Veg Production values after month %d", mon + 1); + snprintf(errstr, MAX_ERROR, "No Veg Production values after month %d", mon + 1); LogError(logfp, LOGWARN, errstr); } @@ -801,25 +801,38 @@ void _echo_VegProd(void) { char outstr[1500]; int k; - sprintf(errstr, "\n==============================================\n" - "Vegetation Production Parameters\n\n"); + snprintf( + errstr, + MAX_ERROR, + "\n==============================================\n" + "Vegetation Production Parameters\n\n" + ); strcpy(outstr, errstr); LogError(logfp, LOGNOTE, outstr); ForEachVegType(k) { - sprintf(errstr, + 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); + 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); + 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); } diff --git a/SW_Weather.c b/SW_Weather.c index 511e97112..7d8ca45ed 100644 --- a/SW_Weather.c +++ b/SW_Weather.c @@ -74,7 +74,7 @@ static char *MyFileName; /** @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 @@ -84,7 +84,7 @@ static char *MyFileName; 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 @@ -106,12 +106,12 @@ void averageClimateAcrossYears(SW_CLIMATE_YEARLY *climateOutput, int 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); @@ -140,7 +140,7 @@ void averageClimateAcrossYears(SW_CLIMATE_YEARLY *climateOutput, int numYears, | 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` @@ -155,7 +155,7 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, int month, yearIndex, year, day, numDaysYear, currMonDay; int numDaysMonth, adjustedDoy, adjustedYear = 0, secondMonth, seventhMonth, adjustedStartYear, calendarYearDays; - + double currentTempMin, currentTempMean, totalAbove65, current7thMonMin, PPT7thMon, consecNonFrost, currentNonFrost; @@ -285,13 +285,13 @@ void calcSiteClimate(SW_WEATHER_HIST **allHist, int numYears, int startYear, // 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; @@ -349,7 +349,7 @@ void calcSiteClimateLatInvariants(SW_WEATHER_HIST **allHist, int numYears, int s /** @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 @@ -361,7 +361,7 @@ void calcSiteClimateLatInvariants(SW_WEATHER_HIST **allHist, int numYears, int s */ 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; @@ -372,7 +372,7 @@ void findDriestQtr(int numYears, Bool inNorthHem, double *meanTempDriestQtr_C, int adjustedYearZero = 0, adjustedYearOne = 0, adjustedYearTwo = 0; double driestThreeMonPPT, driestMeanTemp, currentQtrPPT, currentQtrTemp; - + for(yearIndex = 0; yearIndex < endNumYears; yearIndex++) { driestThreeMonPPT = SW_MISSING; driestMeanTemp = SW_MISSING; @@ -407,11 +407,11 @@ void findDriestQtr(int numYears, Bool inNorthHem, double *meanTempDriestQtr_C, driestMeanTemp = currentQtrTemp; driestThreeMonPPT = currentQtrPPT; } - + } - + meanTempDriestQtr_C[yearIndex] = driestMeanTemp / numQuarterMonths; - + } if(!inNorthHem) meanTempDriestQtr_C[numYears - 1] = SW_MISSING; @@ -1154,7 +1154,7 @@ void _read_weather_hist( char fname[MAX_FILENAMESIZE]; // Create file name: `[weather-file prefix].[year]` - sprintf(fname, "%s.%4d", weather_prefix, year); + snprintf(fname, MAX_FILENAMESIZE, "%s.%4d", weather_prefix, year); if (NULL == (f = fopen(fname, "r"))) return; diff --git a/generic.h b/generic.h index 417afd31c..160d77861 100644 --- a/generic.h +++ b/generic.h @@ -249,7 +249,7 @@ 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 From 74aeda08f4223bad459de1bcca0097f10122b6ac Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Mon, 19 Dec 2022 12:14:58 -0500 Subject: [PATCH 223/326] NEWS: add entry for SWRCs/PDFs --- NEWS.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/NEWS.md b/NEWS.md index 9bcfcaa9d..edf704619 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,21 @@ # NEWS # SOILWAT2 v7.0.0-9000 +* 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 (`PDF`) based on new input `pdf_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_pdf"` for additional details and for guidance on how to + implement additional `SWRCs` and `PDFs` (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 @@ -56,6 +71,15 @@ ## Changes to inputs +* 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 `pdf_name` or + values obtained from new input file `"swrc_params.in"`. + Default values `"Campbell1974"`, `"Cosby1984AndOthers"`, and 0 + (i.e., use `PDF` 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. From d10155bcbc4ef7c006e27b75b61931c0f685edb7 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Mon, 19 Dec 2022 12:31:09 -0500 Subject: [PATCH 224/326] Abbreviate pedotransfer function with community standard "PTF" --- NEWS.md | 10 +-- SW_Site.c | 172 ++++++++++++++++++------------------- SW_Site.h | 68 +++++++-------- test/test_SW_Site.cc | 146 +++++++++++++++---------------- test/test_SW_SoilWater.cc | 28 +++--- test/test_WaterBalance.cc | 12 +-- testing/Input/siteparam.in | 10 +-- 7 files changed, 223 insertions(+), 223 deletions(-) diff --git a/NEWS.md b/NEWS.md index edf704619..06be52f77 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,11 +10,11 @@ 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 (`PDF`) based on new input `pdf_name` or + 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_pdf"` for additional details and for guidance on how to - implement additional `SWRCs` and `PDFs` (issue #315; @dschlaep). + 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). @@ -73,10 +73,10 @@ ## Changes to inputs * 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 `pdf_name` or + 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 `PDF` to estimate paramaters) reproduce previous behavior. + (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. diff --git a/SW_Site.c b/SW_Site.c index 42a11669b..821840da9 100644 --- a/SW_Site.c +++ b/SW_Site.c @@ -96,7 +96,7 @@ RealD @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_pdf + - See details in section \ref swrc_ptf */ char const *swrc2str[N_SWRCs] = { "Campbell1974", @@ -104,15 +104,15 @@ char const *swrc2str[N_SWRCs] = { "FXW" }; -/** Character representation of implemented Pedotransfer Functions (PDF) +/** 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 `pdf2str`" - - See details in section \ref swrc_pdf - - `rSOILWAT2` may implemented additional PDFs + - Order must exactly match "indices of `ptf2str`" + - See details in section \ref swrc_ptf + - `rSOILWAT2` may implemented additional PTFs */ -char const *pdf2str[N_PDFs] = { +char const *ptf2str[N_PTFs] = { "Cosby1984AndOthers", "Cosby1984" }; @@ -254,9 +254,9 @@ static double lower_limit_of_theta_min( * a fixed VWC value, * a fixed SWP value, or * a realistic lower limit from `lower_limit_of_theta_min()`, - unless legacy mode (`pdf` equal to "Cosby1984AndOthers") is used + 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 `PDF_RawlsBrakensiek1985()` + maximum of `lower_limit_of_theta_min()` and `PTF_RawlsBrakensiek1985()` with pedotransfer function by Rawls & Brakensiek 1985 \cite rawls1985WmitE (independently of the selected SWRC). @@ -294,15 +294,15 @@ static double ui_theta_min( vwc_min = lower_limit_of_theta_min(swrc_type, swrcp, gravel, width); if (legacy_mode) { - /* residual theta estimated with Rawls & Brakensiek (1985) PDF */ - PDF_RawlsBrakensiek1985( + /* residual theta estimated with Rawls & Brakensiek (1985) PTF */ + PTF_RawlsBrakensiek1985( &tmp_vwcmin, sand, clay, swcBulk_sat / ((1. - gravel) * width) ); - /* if `PDF_RawlsBrakensiek1985()` was successful, then take max */ + /* if `PTF_RawlsBrakensiek1985()` was successful, then take max */ if (!missing(tmp_vwcmin)) { vwc_min = fmax(vwc_min, tmp_vwcmin); } @@ -366,25 +366,25 @@ unsigned int encode_str2swrc(char *swrc_name) { } /** - @brief Translate a PDF name into a PDF type number + @brief Translate a PTF name into a PTF type number - See #pdf2str and `siteparam.in`. + See #ptf2str and `siteparam.in`. - @param[in] *pdf_name Name of a PDF + @param[in] *ptf_name Name of a PTF - @return Internal identification number of selected PDF; + @return Internal identification number of selected PTF; #SW_MISSING if not implemented. */ -unsigned int encode_str2pdf(char *pdf_name) { +unsigned int encode_str2ptf(char *ptf_name) { unsigned int k; for ( k = 0; - k < N_PDFs && Str_CompareI(pdf_name, (char *)pdf2str[k]); + k < N_PTFs && Str_CompareI(ptf_name, (char *)ptf2str[k]); k++ ); - if (k == N_PDFs) { + if (k == N_PTFs) { k = (unsigned int) SW_MISSING; } @@ -395,11 +395,11 @@ unsigned int encode_str2pdf(char *pdf_name) { /** @brief Estimate parameters of selected soil water retention curve (SWRC) - using selected pedotransfer function (PDF) + using selected pedotransfer function (PTF) - See #pdf2str for implemented PDFs. + See #ptf2str for implemented PTFs. - @param[in] pdf_type Identification number of selected PDF + @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] @@ -407,11 +407,11 @@ unsigned int encode_str2pdf(char *pdf_name) { 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 PDF + accepts #SW_MISSING if not used by selected PTF */ -void SWRC_PDF_estimate_parameters( - unsigned int pdf_type, +void SWRC_PTF_estimate_parameters( + unsigned int ptf_type, double *swrcp, double sand, double clay, @@ -422,27 +422,27 @@ void SWRC_PDF_estimate_parameters( /* Initialize swrcp[] to 0 */ memset(swrcp, 0., SWRC_PARAM_NMAX * sizeof(swrcp[0])); - if (pdf_type == sw_Cosby1984AndOthers) { - SWRC_PDF_Cosby1984_for_Campbell1974(swrcp, sand, clay); + if (ptf_type == sw_Cosby1984AndOthers) { + SWRC_PTF_Cosby1984_for_Campbell1974(swrcp, sand, clay); - } else if (pdf_type == sw_Cosby1984) { - SWRC_PDF_Cosby1984_for_Campbell1974(swrcp, sand, clay); + } else if (ptf_type == sw_Cosby1984) { + SWRC_PTF_Cosby1984_for_Campbell1974(swrcp, sand, clay); } else { LogError( logfp, LOGFATAL, - "PDF is not implemented in SOILWAT2.", - pdf_type + "PTF is not implemented in SOILWAT2.", + ptf_type ); } /**********************************/ - /* TODO: remove once PDFs are implemented that utilize gravel */ + /* TODO: remove once PTFs are implemented that utilize gravel */ /* avoiding `error: unused parameter 'gravel' [-Werror=unused-parameter]` */ if (gravel < 0.) {}; - /* TODO: remove once PDFs are implemented that utilize bdensity */ + /* TODO: remove once PTFs are implemented that utilize bdensity */ /* avoiding `error: unused parameter 'gravel' [-Werror=unused-parameter]` */ if (bdensity < 0.) {}; /**********************************/ @@ -451,15 +451,15 @@ void SWRC_PDF_estimate_parameters( /** @brief Estimate Campbell's 1974 SWRC parameters - using Cosby et al. 1984 multivariate PDF + 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 PDFs are from Cosby et al. 1984 (\cite Cosby1984) Table 4; - Cosby et al. 1984 provided also univariate PDFs in Table 5 + 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()` @@ -469,7 +469,7 @@ void SWRC_PDF_estimate_parameters( @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_PDF_Cosby1984_for_Campbell1974( +void SWRC_PTF_Cosby1984_for_Campbell1974( double *swrcp, double sand, double clay ) { @@ -494,28 +494,28 @@ void SWRC_PDF_Cosby1984_for_Campbell1974( /** @brief Saturated soil water content - See #pdf2str for implemented PDFs. + 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 - `pdf_name` of "Cosby1984AndOthers" will reproduce `SOILWAT2` legacy mode + `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) PDF instead; - `pdf_name` of "Cosby1984" will return saturated soil water content estimated - by Cosby et al. 1984 (\cite Cosby1984) PDF. + 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 `pdf_type`, `sand`, and `clay` are utilized only if - `pdf_name` is "Cosby1984AndOthers" (see #pdf2str). + 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] pdf_type Identification number of selected PDF + @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] @@ -526,7 +526,7 @@ double SW_swcBulk_saturated( double *swrcp, double gravel, double width, - unsigned int pdf_type, + unsigned int ptf_type, double sand, double clay ) { @@ -534,9 +534,9 @@ double SW_swcBulk_saturated( switch (swrc_type) { case sw_Campbell1974: - if (pdf_type == sw_Cosby1984AndOthers) { + if (ptf_type == sw_Cosby1984AndOthers) { // Cosby1984AndOthers (backwards compatible) - PDF_Saxton2006(&theta_sat, sand, clay); + PTF_Saxton2006(&theta_sat, sand, clay); } else { theta_sat = swrcp[1]; } @@ -568,7 +568,7 @@ double SW_swcBulk_saturated( /** @brief Lower limit of simulated soil water content - See #pdf2str for implemented PDFs. + See #ptf2str for implemented PTFs. See #swrc2str for implemented SWRCs. SWRCs provide a theoretical minimum/residual volumetric water content; @@ -579,14 +579,14 @@ double SW_swcBulk_saturated( and is strictly larger than the theoretical SWRC value. The arguments `sand`, `clay`, and `swcBulk_sat` - are utilized only in legacy mode, i.e., `pdf` equal to "Cosby1984AndOthers". + 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] pdf_type Identification number of selected PDF + @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] @@ -600,7 +600,7 @@ double SW_swcBulk_minimum( double *swrcp, double gravel, double width, - unsigned int pdf_type, + unsigned int ptf_type, double ui_sm_min, double sand, double clay, @@ -642,9 +642,9 @@ double SW_swcBulk_minimum( swcBulk_sat, swrc_type, swrcp, - // `(Bool) pdf_type == sw_Cosby1984AndOthers` doesn't work for unit test: + // `(Bool) ptf_type == sw_Cosby1984AndOthers` doesn't work for unit test: // error: "no known conversion from 'bool' to 'Bool'" - pdf_type == sw_Cosby1984AndOthers ? swTRUE : swFALSE + ptf_type == sw_Cosby1984AndOthers ? swTRUE : swFALSE ); /* `theta_min_sim` must be strictly larger than `theta_min_theoretical` */ @@ -657,25 +657,25 @@ double SW_swcBulk_minimum( /** - @brief Check whether selected PDF and SWRC are compatible + @brief Check whether selected PTF and SWRC are compatible - See #pdf2str for implemented PDFs. + See #ptf2str for implemented PTFs. See #swrc2str for implemented SWRCs. @param[in] *swrc_name Name of selected SWRC - @param[in] *pdf_name Name of selected PDF + @param[in] *ptf_name Name of selected PTF - @return A logical value indicating if SWRC and PDF are compatible. + @return A logical value indicating if SWRC and PTF are compatible. */ -Bool check_SWRC_vs_PDF(char *swrc_name, char *pdf_name) { +Bool check_SWRC_vs_PTF(char *swrc_name, char *ptf_name) { Bool res = swFALSE; - if (!missing((double) encode_str2pdf(pdf_name))) { + if (!missing((double) encode_str2ptf(ptf_name))) { if ( Str_CompareI(swrc_name, (char *) "Campbell1974") == 0 && ( - Str_CompareI(pdf_name, (char *) "Cosby1984AndOthers") == 0 || - Str_CompareI(pdf_name, (char *) "Cosby1984") == 0 + Str_CompareI(ptf_name, (char *) "Cosby1984AndOthers") == 0 || + Str_CompareI(ptf_name, (char *) "Cosby1984") == 0 ) ) { res = swTRUE; @@ -993,7 +993,7 @@ Bool SWRC_check_parameters_for_FXW(double *swrcp) { /** - @brief Saxton et al. 2006 PDFs \cite Saxton2006 + @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] @@ -1001,7 +1001,7 @@ Bool SWRC_check_parameters_for_FXW(double *swrcp) { @param[out] *theta_sat Estimated saturated volumetric water content of the matric soil [cm/cm] */ -void PDF_Saxton2006( +void PTF_Saxton2006( double *theta_sat, double sand, double clay @@ -1046,7 +1046,7 @@ void PDF_Saxton2006( LogError( logfp, LOGFATAL, - "PDF_Saxton2006(): invalid value of " + "PTF_Saxton2006(): invalid value of " "theta(saturated, [cm / cm]) = %f (must be within 0-1)\n", *theta_sat ); @@ -1113,7 +1113,7 @@ void PDF_Saxton2006( /** - @brief Rawls and Brakensiek 1985 PDFs \cite rawls1985WmitE + @brief Rawls and Brakensiek 1985 PTFs \cite rawls1985WmitE to estimate residual soil water content for the Brooks-Corey SWRC \cite brooks1964a @@ -1127,7 +1127,7 @@ void PDF_Saxton2006( @param[out] *theta_min Estimated residual volumetric water content of the matric soil [cm/cm] */ -void PDF_RawlsBrakensiek1985( +void PTF_RawlsBrakensiek1985( double *theta_min, double sand, double clay, @@ -1160,7 +1160,7 @@ void PDF_RawlsBrakensiek1985( LogError( logfp, LOGWARN, - "`PDF_RawlsBrakensiek1985()`: sand, clay, or porosity out of valid range." + "`PTF_RawlsBrakensiek1985()`: sand, clay, or porosity out of valid range." ); *theta_min = SW_MISSING; @@ -1517,8 +1517,8 @@ void SW_SIT_read(void) { v->site_swrc_type = encode_str2swrc(v->site_swrc_name); break; case 43: - strcpy(v->site_pdf_name, inbuf); - v->site_pdf_type = encode_str2pdf(v->site_pdf_name); + 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)); @@ -1865,7 +1865,7 @@ void derive_soilRegions(int nRegions, RealD *regionLowerBounds){ */ void SW_SWRC_read(void) { - /* Don't read values from disk if they will be estimated via a PDF */ + /* Don't read values from disk if they will be estimated via a PTF */ if (!SW_Site.site_has_swrcp) return; FILE *f; @@ -1974,14 +1974,14 @@ void SW_SIT_init_run(void) { add_deepdrain_layer(); - /* Check compatibility between selected SWRC and PDF */ + /* Check compatibility between selected SWRC and PTF */ if (!sp->site_has_swrcp) { - if (!check_SWRC_vs_PDF(sp->site_swrc_name, sp->site_pdf_name)) { + if (!check_SWRC_vs_PTF(sp->site_swrc_name, sp->site_ptf_name)) { LogError( logfp, LOGFATAL, - "Selected PDF '%s' is incompatible with selected SWRC '%s'\n", - sp->site_pdf_name, + "Selected PTF '%s' is incompatible with selected SWRC '%s'\n", + sp->site_ptf_name, sp->site_swrc_name ); } @@ -1993,12 +1993,12 @@ void SW_SIT_init_run(void) { { lyr = sp->lyr[s]; - /* Copy site-level SWRC/PDF information to each layer: - We currently allow specifying one SWRC/PDF for a site for all layers; - remove in the future if we allow SWRC/PDF to vary by soil layer + /* 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 */ lyr->swrc_type = sp->site_swrc_type; - lyr->pdf_type = sp->site_pdf_type; + lyr->ptf_type = sp->site_ptf_type; /* Check soil properties for valid values */ @@ -2048,13 +2048,13 @@ void SW_SIT_init_run(void) { if (!sp->site_has_swrcp) { - /* Use pedotransfer function PDF + /* 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_PDF_estimate_parameters( - lyr->pdf_type, + SWRC_PTF_estimate_parameters( + lyr->ptf_type, lyr->swrcp, lyr->fractionWeightMatric_sand, lyr->fractionWeightMatric_clay, @@ -2099,7 +2099,7 @@ void SW_SIT_init_run(void) { lyr->swrcp, lyr->fractionVolBulk_gravel, lyr->width, - lyr->pdf_type, + lyr->ptf_type, lyr->fractionWeightMatric_sand, lyr->fractionWeightMatric_clay ); @@ -2109,7 +2109,7 @@ void SW_SIT_init_run(void) { lyr->swrcp, lyr->fractionVolBulk_gravel, lyr->width, - lyr->pdf_type, + lyr->ptf_type, _SWCMinVal, lyr->fractionWeightMatric_sand, lyr->fractionWeightMatric_clay, @@ -2586,9 +2586,9 @@ void _echo_inputs(void) { LogError( logfp, LOGNOTE, - " PDF type: %d (%s)\n", - s->site_pdf_type, - s->site_pdf_name + " PTF type: %d (%s)\n", + s->site_ptf_type, + s->site_ptf_name ); LogError( diff --git a/SW_Site.h b/SW_Site.h index 1f4f7a143..8f1f5de39 100644 --- a/SW_Site.h +++ b/SW_Site.h @@ -58,58 +58,58 @@ extern "C" { /** - @defgroup swrc_pdf Soil Water Retention Curves + @defgroup swrc_ptf Soil Water Retention Curves - __Soil Water Retention Curves (SWRCs) -- Pedotransfer functions (PDFs)__ + __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 PDFs by Cosby et al. 1984 (\cite Cosby1984). + 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 PDF (see input file `siteparam.in`) or, + 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 PDF functionality. + Please note that `rSOILWAT2` may provide additional PTF functionality. __Approach__ - -# User selections of SWRC and PDF are read in from input file `siteparam.in` + -# 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_PDF()` to check that selected SWRC and PDF are + - calls `check_SWRC_vs_PTF()` to check that selected SWRC and PTF are compatible - - calls `SWRC_PDF_estimate_parameters()` to estimate - SWRC parameter values from soil properties based on selected PDF + - 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/PDF + -# 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/PDF + 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 PDF specific functions - which implement SWRC and/or PDF specific details. + 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 PDF "YYY"__ + __Steps to implement a new SWRC "XXX" and corresponding PTF "YYY"__ - -# Update #N_SWRCs and #N_PDFs + -# Update #N_SWRCs and #N_PTFs - -# Add new names to #swrc2str and #pdf2str and + -# Add new names to #swrc2str and #ptf2str and add corresponding macros of indices -# Update input files `siteparam.in` and `swrc_params.in` @@ -117,8 +117,8 @@ extern "C" { -# Implement new XXX/YYY-specific functions - `SWRC_check_parameters_for_XXX()` to validate parameter values, e.g., `SWRC_check_parameters_for_Campbell1974()` - - `SWRC_PDF_YYY_for_XXX()` to estimate parameter values (if implemented), - e.g., `SWRC_PDF_Cosby1984_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, @@ -126,8 +126,8 @@ extern "C" { -# Update "wrapper" functions that select and call XXX/YYY-specific functions and/or parameters - - `check_SWRC_vs_PDF()` - - `SWRC_PDF_estimate_parameters()` (if PDF is implemented) + - `check_SWRC_vs_PTF()` + - `SWRC_PTF_estimate_parameters()` (if PTF is implemented) - `SWRC_check_parameters()` - `SWRC_SWCtoSWP()` - `SWRC_SWPtoSWC()` @@ -140,7 +140,7 @@ extern "C" { #define SWRC_PARAM_NMAX 6 /**< Maximal number of SWRC parameters implemented */ #define N_SWRCs 3 /**< Number of SWRCs implemented by SOILWAT2 */ -#define N_PDFs 2 /**< Number of PDFs implemented by SOILWAT2 */ +#define N_PTFs 2 /**< Number of PTFs implemented by SOILWAT2 */ // Indices of #swrc2str (for code readability) #define sw_Campbell1974 0 @@ -203,7 +203,7 @@ typedef struct { /* Soil water retention curve (SWRC) */ unsigned int swrc_type, /**< Type of SWRC (see #swrc2str) */ - pdf_type; /**< Type of PDF (see #pdf2str) */ + 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? */ @@ -264,11 +264,11 @@ typedef struct { /* Soil water retention curve (SWRC), see `SW_LAYER_INFO` */ unsigned int site_swrc_type, - site_pdf_type; + site_ptf_type; char site_swrc_name[64], - site_pdf_name[64]; + site_ptf_name[64]; Bool site_has_swrcp; /**< Are `swrcp` already (TRUE) or not yet estimated (FALSE)? */ @@ -284,7 +284,7 @@ extern LyrIndex _TranspRgnBounds[MAX_TRANSP_REGIONS]; extern RealD _SWCInitVal, _SWCWetVal, _SWCMinVal; extern char const *swrc2str[]; -extern char const *pdf2str[]; +extern char const *ptf2str[]; /* =================================================== */ @@ -292,24 +292,24 @@ extern char const *pdf2str[]; /* --------------------------------------------------- */ unsigned int encode_str2swrc(char *swrc_name); -unsigned int encode_str2pdf(char *pdf_name); +unsigned int encode_str2ptf(char *ptf_name); -void SWRC_PDF_estimate_parameters( - unsigned int pdf_type, +void SWRC_PTF_estimate_parameters( + unsigned int ptf_type, double *swrcp, double sand, double clay, double gravel, double bdensity ); -void SWRC_PDF_Cosby1984_for_Campbell1974( +void SWRC_PTF_Cosby1984_for_Campbell1974( double *swrcp, double sand, double clay ); -Bool check_SWRC_vs_PDF(char *swrc_name, char *pdf_name); +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); @@ -320,7 +320,7 @@ double SW_swcBulk_saturated( double *swrcp, double gravel, double width, - unsigned int pdf_type, + unsigned int ptf_type, double sand, double clay ); @@ -329,18 +329,18 @@ double SW_swcBulk_minimum( double *swrcp, double gravel, double width, - unsigned int pdf_type, + unsigned int ptf_type, double ui_sm_min, double sand, double clay, double swcBulk_sat ); -void PDF_Saxton2006( +void PTF_Saxton2006( double *theta_sat, double sand, double clay ); -void PDF_RawlsBrakensiek1985( +void PTF_RawlsBrakensiek1985( double *theta_min, double sand, double clay, diff --git a/test/test_SW_Site.cc b/test/test_SW_Site.cc index 1a84b8098..cfbbca391 100644 --- a/test/test_SW_Site.cc +++ b/test/test_SW_Site.cc @@ -40,32 +40,32 @@ namespace { - // List SWRC: PDFs - const char *ns_pdfca2C1974[] = { + // List SWRC: PTFs + const char *ns_ptfca2C1974[] = { "Campbell1974", "Cosby1984AndOthers", "Cosby1984" }; - const char *ns_pdfa2vG1980[] = { + const char *ns_ptfa2vG1980[] = { "vanGenuchten1980", - // all PDFs + // all PTFs "Rosetta3" }; - const char *ns_pdfc2vG1980[] = { + const char *ns_ptfc2vG1980[] = { "vanGenuchten1980" - // PDFs implemented in SOILWAT2 + // PTFs implemented in SOILWAT2 }; - const char *ns_pdfa2FXW[] = { + const char *ns_ptfa2FXW[] = { "FXW" - // all PDFs + // all PTFs "neuroFX2021" }; - const char *ns_pdfc2FXW[] = { + const char *ns_ptfc2FXW[] = { "FXW" - // PDFs implemented in SOILWAT2 + // PTFs implemented in SOILWAT2 }; // Test pedotransfer functions - TEST(SiteTest, PDFs) { + TEST(SiteTest, PTFs) { // inputs RealD swrcp[SWRC_PARAM_NMAX], @@ -76,13 +76,13 @@ namespace { unsigned int swrc_type, k; - //--- Matching PDF-SWRC pairs + //--- Matching PTF-SWRC pairs // (k starts at 1 because 0 holds the SWRC) - swrc_type = encode_str2swrc((char *) ns_pdfca2C1974[0]); - for (k = 1; k < length(ns_pdfca2C1974); k++) { - SWRC_PDF_estimate_parameters( - encode_str2pdf((char *) ns_pdfca2C1974[k]), + 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, @@ -92,10 +92,10 @@ namespace { EXPECT_TRUE((bool) SWRC_check_parameters(swrc_type, swrcp)); } - swrc_type = encode_str2swrc((char *) ns_pdfc2vG1980[0]); - for (k = 1; k < length(ns_pdfc2vG1980); k++) { - SWRC_PDF_estimate_parameters( - encode_str2pdf((char *) ns_pdfc2vG1980[k]), + 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, @@ -105,10 +105,10 @@ namespace { EXPECT_TRUE((bool) SWRC_check_parameters(swrc_type, swrcp)); } - swrc_type = encode_str2swrc((char *) ns_pdfc2FXW[0]); - for (k = 1; k < length(ns_pdfc2FXW); k++) { - SWRC_PDF_estimate_parameters( - encode_str2pdf((char *) ns_pdfc2FXW[k]), + 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, @@ -120,8 +120,8 @@ namespace { } - // Test fatal failures of PDF estimation - TEST(SiteDeathTest, PDFs) { + // Test fatal failures of PTF estimation + TEST(SiteDeathTest, PTFs) { RealD swrcp[SWRC_PARAM_NMAX], @@ -129,15 +129,15 @@ namespace { clay = 0.33, gravel = 0.1, bdensity = 1.4; - unsigned int pdf_type; + unsigned int ptf_type; - //--- Test unimplemented PDF - pdf_type = N_PDFs + 1; + //--- Test unimplemented PTF + ptf_type = N_PTFs + 1; EXPECT_DEATH_IF_SUPPORTED( - SWRC_PDF_estimate_parameters( - pdf_type, + SWRC_PTF_estimate_parameters( + ptf_type, swrcp, sand, clay, @@ -149,76 +149,76 @@ namespace { } - // Test PDF-SWRC pairings - TEST(SiteTest, PDF2SWRC) { + // Test PTF-SWRC pairings + TEST(SiteTest, PTF2SWRC) { unsigned int k; // `length()` returns "unsigned long" - for (k = 1; k < length(ns_pdfca2C1974); k++) { + for (k = 1; k < length(ns_ptfca2C1974); k++) { EXPECT_TRUE( - (bool) check_SWRC_vs_PDF( - (char *) ns_pdfca2C1974[0], - (char *) ns_pdfca2C1974[k] + (bool) check_SWRC_vs_PTF( + (char *) ns_ptfca2C1974[0], + (char *) ns_ptfca2C1974[k] ) ); EXPECT_FALSE( - (bool) check_SWRC_vs_PDF( - (char *) ns_pdfa2vG1980[0], - (char *) ns_pdfca2C1974[k] + (bool) check_SWRC_vs_PTF( + (char *) ns_ptfa2vG1980[0], + (char *) ns_ptfca2C1974[k] ) ); EXPECT_FALSE( - (bool) check_SWRC_vs_PDF( - (char *) ns_pdfa2FXW[0], - (char *) ns_pdfca2C1974[k] + (bool) check_SWRC_vs_PTF( + (char *) ns_ptfa2FXW[0], + (char *) ns_ptfca2C1974[k] ) ); } - for (k = 1; k < length(ns_pdfa2vG1980); k++) { + for (k = 1; k < length(ns_ptfa2vG1980); k++) { EXPECT_FALSE( - (bool) check_SWRC_vs_PDF( - (char *) ns_pdfa2vG1980[0], - (char *) ns_pdfa2vG1980[k] + (bool) check_SWRC_vs_PTF( + (char *) ns_ptfa2vG1980[0], + (char *) ns_ptfa2vG1980[k] ) ); EXPECT_FALSE( - (bool) check_SWRC_vs_PDF( - (char *) ns_pdfca2C1974[0], - (char *) ns_pdfa2vG1980[k] + (bool) check_SWRC_vs_PTF( + (char *) ns_ptfca2C1974[0], + (char *) ns_ptfa2vG1980[k] ) ); EXPECT_FALSE( - (bool) check_SWRC_vs_PDF( - (char *) ns_pdfa2FXW[0], - (char *) ns_pdfa2vG1980[k] + (bool) check_SWRC_vs_PTF( + (char *) ns_ptfa2FXW[0], + (char *) ns_ptfa2vG1980[k] ) ); } - for (k = 1; k < length(ns_pdfa2FXW); k++) { + for (k = 1; k < length(ns_ptfa2FXW); k++) { EXPECT_FALSE( - (bool) check_SWRC_vs_PDF( - (char *) ns_pdfa2FXW[0], - (char *) ns_pdfa2FXW[k] + (bool) check_SWRC_vs_PTF( + (char *) ns_ptfa2FXW[0], + (char *) ns_ptfa2FXW[k] ) ); EXPECT_FALSE( - (bool) check_SWRC_vs_PDF( - (char *) ns_pdfca2C1974[0], - (char *) ns_pdfa2FXW[k] + (bool) check_SWRC_vs_PTF( + (char *) ns_ptfca2C1974[0], + (char *) ns_ptfa2FXW[k] ) ); EXPECT_FALSE( - (bool) check_SWRC_vs_PDF( - (char *) ns_pdfa2vG1980[0], - (char *) ns_pdfa2FXW[k] + (bool) check_SWRC_vs_PTF( + (char *) ns_ptfa2vG1980[0], + (char *) ns_ptfa2FXW[k] ) ); } @@ -382,8 +382,8 @@ namespace { } - // Test 'PDF_RawlsBrakensiek1985' - TEST(SiteTest, PDFRawlsBrakensiek1985) { + // Test 'PTF_RawlsBrakensiek1985' + TEST(SiteTest, PTFRawlsBrakensiek1985) { //declare mock INPUTS double theta_min, @@ -394,22 +394,22 @@ namespace { //--- 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[ - PDF_RawlsBrakensiek1985(&theta_min, 0., clay, porosity); + PTF_RawlsBrakensiek1985(&theta_min, 0., clay, porosity); EXPECT_DOUBLE_EQ(theta_min, SW_MISSING); - PDF_RawlsBrakensiek1985(&theta_min, 0.75, clay, porosity); + PTF_RawlsBrakensiek1985(&theta_min, 0.75, clay, porosity); EXPECT_DOUBLE_EQ(theta_min, SW_MISSING); - PDF_RawlsBrakensiek1985(&theta_min, sand, 0., porosity); + PTF_RawlsBrakensiek1985(&theta_min, sand, 0., porosity); EXPECT_DOUBLE_EQ(theta_min, SW_MISSING); - PDF_RawlsBrakensiek1985(&theta_min, sand, 0.65, porosity); + PTF_RawlsBrakensiek1985(&theta_min, sand, 0.65, porosity); EXPECT_DOUBLE_EQ(theta_min, SW_MISSING); - PDF_RawlsBrakensiek1985(&theta_min, sand, clay, 0.); + PTF_RawlsBrakensiek1985(&theta_min, sand, clay, 0.); EXPECT_DOUBLE_EQ(theta_min, SW_MISSING); - PDF_RawlsBrakensiek1985(&theta_min, sand, clay, 1.); + PTF_RawlsBrakensiek1985(&theta_min, sand, clay, 1.); EXPECT_DOUBLE_EQ(theta_min, SW_MISSING); @@ -423,7 +423,7 @@ namespace { for (k3 = 0; k3 <= 5; k3++) { porosity = 0.1 + (double) k3 / 5. * (0.99 - 0.1); - PDF_RawlsBrakensiek1985(&theta_min, sand, clay, porosity); + PTF_RawlsBrakensiek1985(&theta_min, sand, clay, porosity); EXPECT_GE(theta_min, 0.); EXPECT_LT(theta_min, porosity); } @@ -431,7 +431,7 @@ namespace { } // Expect theta_min = 0 if sand = 0.4, clay = 0.5, and porosity = 0.1 - PDF_RawlsBrakensiek1985(&theta_min, 0.4, 0.5, 0.1); + PTF_RawlsBrakensiek1985(&theta_min, 0.4, 0.5, 0.1); EXPECT_DOUBLE_EQ(theta_min, 0); } diff --git a/test/test_SW_SoilWater.cc b/test/test_SW_SoilWater.cc index 3a6df814d..17f0629ec 100644 --- a/test/test_SW_SoilWater.cc +++ b/test/test_SW_SoilWater.cc @@ -73,7 +73,7 @@ namespace{ // Test the 'SW_SoilWater' functions 'SWRC_SWCtoSWP' and `SWRC_SWPtoSWC` TEST(SWSoilWaterTest, TranslateBetweenSWCandSWP) { // set up mock variables - unsigned int swrc_type, pdf_type, k; + unsigned int swrc_type, ptf_type, k; const int em = LOGFATAL; RealD phi, @@ -100,22 +100,22 @@ namespace{ for (swrc_type = 0; swrc_type < N_SWRCs; swrc_type++) { memset(swrcp, 0., SWRC_PARAM_NMAX * sizeof(swrcp[0])); - // Find a suitable PDF to generate `SWRCp` + // Find a suitable PTF to generate `SWRCp` for ( - pdf_type = 0; - pdf_type < N_PDFs && !check_SWRC_vs_PDF( + ptf_type = 0; + ptf_type < N_PTFs && !check_SWRC_vs_PTF( (char *) swrc2str[swrc_type], - (char *) pdf2str[pdf_type] + (char *) ptf2str[ptf_type] ); - pdf_type++ + ptf_type++ ) {} // Obtain SWRCp - if (pdf_type < N_PDFs) { - // PDF implemented in C: estimate parameters - SWRC_PDF_estimate_parameters( - pdf_type, + if (ptf_type < N_PTFs) { + // PTF implemented in C: estimate parameters + SWRC_PTF_estimate_parameters( + ptf_type, swrcp, sand, clay, @@ -124,7 +124,7 @@ namespace{ ); } else { - // PDF not implemented in C: provide hard coded values + // PTF not implemented in C: provide hard coded values if ( Str_CompareI( (char *) swrc2str[swrc_type], @@ -158,12 +158,12 @@ namespace{ //------ Tests SWC -> SWP msg.str(""); - msg << "SWRC/PDF = " << swrc_type << "/" << pdf_type; + 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 'pdf2str' - //msg << "SWRC/PDF = " << swrc2str[swrc_type] << "/" << pdf2str[pdf_type]; + // 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); diff --git a/test/test_WaterBalance.cc b/test/test_WaterBalance.cc index 5fa1202a1..4472c6917 100644 --- a/test/test_WaterBalance.cc +++ b/test/test_WaterBalance.cc @@ -225,11 +225,11 @@ namespace { TEST(WaterBalanceTest, WithSWRCvanGenuchten1980) { int i; - // Set SWRC and PDF (and SWRC parameter input filename) + // 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_pdf_name, (char *) "Rosetta3"); - SW_Site.site_pdf_type = encode_str2pdf(SW_Site.site_pdf_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]); @@ -260,11 +260,11 @@ namespace { TEST(WaterBalanceTest, WithSWRCFXW) { int i; - // Set SWRC and PDF (and SWRC parameter input filename) + // 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_pdf_name, (char *) "neuroFX2021"); - SW_Site.site_pdf_type = encode_str2pdf(SW_Site.site_pdf_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]); diff --git a/testing/Input/siteparam.in b/testing/Input/siteparam.in index 79764fb5d..bce9a0557 100644 --- a/testing/Input/siteparam.in +++ b/testing/Input/siteparam.in @@ -93,16 +93,16 @@ RCP85 #--- Soil water retention curve (SWRC) ------ # -# Implemented options (`swrc_name`/`pdf_name`, see `swrc2str[]`/`pdf2str[]`): -# - pdf_name = : SWRC parameters must be provided via "swrc_params.in" +# 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) -# * pdf_name = "Cosby1984AndOthers" (Cosby et al. 1984 but `swc_sat` by Saxton et al. 2006) -# * pdf_name = "Cosby1984" (Cosby et al. 1984) +# * 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 PDFs +# Note: `rSOILWAT2` may implement additional PTFs Campbell1974 # Specify soil water retention curve Cosby1984AndOthers # Specify pedotransfer function From a421fd44808bec1ee9c6c8b1e3f591449bdfd98f Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Tue, 20 Dec 2022 11:11:45 -0500 Subject: [PATCH 225/326] Add leak suppression for `realizeClassWithoutSwift` on Apple arm64 --- .LSAN_suppr.txt | 3 +++ 1 file changed, 3 insertions(+) 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 From 90b17413fee14aa61193e66ebb5757aae5cded63 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Tue, 20 Dec 2022 17:28:21 -0500 Subject: [PATCH 226/326] Re-organize repository (part 1) - addressing #89 - `git mv previous_path target_path` to "rename" folders and files without losing git history - external: googletest and pcg submodules - src: SOILWAT2 source code - include: SOILWAT2 header files - tests/example: example inputs for a SOILWAT2 run, previously "testing" - tests/gtests: unit tests based on googletest, previously "test" - note: additional commits to complete #89 will need to update file path names, makefile, etc. --- .gitignore | 7 ++++--- .gitmodules | 4 ++-- googletest => external/googletest | 0 pcg => external/pcg | 0 SW_Carbon.h => include/SW_Carbon.h | 0 SW_Control.h => include/SW_Control.h | 0 SW_Defines.h => include/SW_Defines.h | 0 SW_Files.h => include/SW_Files.h | 0 SW_Flow.h => include/SW_Flow.h | 0 SW_Flow_lib.h => include/SW_Flow_lib.h | 0 SW_Flow_lib_PET.h => include/SW_Flow_lib_PET.h | 0 SW_Main_lib.h => include/SW_Main_lib.h | 0 SW_Markov.h => include/SW_Markov.h | 0 SW_Model.h => include/SW_Model.h | 0 SW_Output.h => include/SW_Output.h | 0 SW_Output_outarray.h => include/SW_Output_outarray.h | 0 SW_Output_outtext.h => include/SW_Output_outtext.h | 0 SW_Site.h => include/SW_Site.h | 0 SW_Sky.h => include/SW_Sky.h | 0 SW_SoilWater.h => include/SW_SoilWater.h | 0 SW_Times.h => include/SW_Times.h | 0 SW_VegEstab.h => include/SW_VegEstab.h | 0 SW_VegProd.h => include/SW_VegProd.h | 0 SW_Weather.h => include/SW_Weather.h | 0 Times.h => include/Times.h | 0 filefuncs.h => include/filefuncs.h | 0 generic.h => include/generic.h | 0 memblock.h => include/memblock.h | 0 myMemory.h => include/myMemory.h | 0 rands.h => include/rands.h | 0 SW_Carbon.c => src/SW_Carbon.c | 0 SW_Control.c => src/SW_Control.c | 0 SW_Files.c => src/SW_Files.c | 0 SW_Flow.c => src/SW_Flow.c | 0 SW_Flow_lib.c => src/SW_Flow_lib.c | 0 SW_Flow_lib_PET.c => src/SW_Flow_lib_PET.c | 0 SW_Main.c => src/SW_Main.c | 0 SW_Main_lib.c => src/SW_Main_lib.c | 0 SW_Markov.c => src/SW_Markov.c | 0 SW_Model.c => src/SW_Model.c | 0 SW_Output.c => src/SW_Output.c | 0 SW_Output_get_functions.c => src/SW_Output_get_functions.c | 0 SW_Output_mock.c => src/SW_Output_mock.c | 0 SW_Output_outarray.c => src/SW_Output_outarray.c | 0 SW_Output_outtext.c => src/SW_Output_outtext.c | 0 SW_Site.c => src/SW_Site.c | 0 SW_Sky.c => src/SW_Sky.c | 0 SW_SoilWater.c => src/SW_SoilWater.c | 0 SW_VegEstab.c => src/SW_VegEstab.c | 0 SW_VegProd.c => src/SW_VegProd.c | 0 SW_Weather.c => src/SW_Weather.c | 0 Times.c => src/Times.c | 0 filefuncs.c => src/filefuncs.c | 0 generic.c => src/generic.c | 0 mymemory.c => src/mymemory.c | 0 rands.c => src/rands.c | 0 {testing => tests/example}/Input/bouteloua.estab | 0 {testing => tests/example}/Input/bromus.estab | 0 {testing => tests/example}/Input/carbon.in | 0 {testing => tests/example}/Input/climate.in | 0 {testing => tests/example}/Input/data_weather/weath.1980 | 0 {testing => tests/example}/Input/data_weather/weath.1981 | 0 {testing => tests/example}/Input/data_weather/weath.1982 | 0 {testing => tests/example}/Input/data_weather/weath.1983 | 0 {testing => tests/example}/Input/data_weather/weath.1984 | 0 {testing => tests/example}/Input/data_weather/weath.1985 | 0 {testing => tests/example}/Input/data_weather/weath.1986 | 0 {testing => tests/example}/Input/data_weather/weath.1987 | 0 {testing => tests/example}/Input/data_weather/weath.1988 | 0 {testing => tests/example}/Input/data_weather/weath.1989 | 0 {testing => tests/example}/Input/data_weather/weath.1990 | 0 {testing => tests/example}/Input/data_weather/weath.1991 | 0 {testing => tests/example}/Input/data_weather/weath.1992 | 0 {testing => tests/example}/Input/data_weather/weath.1993 | 0 {testing => tests/example}/Input/data_weather/weath.1994 | 0 {testing => tests/example}/Input/data_weather/weath.1995 | 0 {testing => tests/example}/Input/data_weather/weath.1996 | 0 {testing => tests/example}/Input/data_weather/weath.1997 | 0 {testing => tests/example}/Input/data_weather/weath.1998 | 0 {testing => tests/example}/Input/data_weather/weath.1999 | 0 {testing => tests/example}/Input/data_weather/weath.2000 | 0 {testing => tests/example}/Input/data_weather/weath.2001 | 0 {testing => tests/example}/Input/data_weather/weath.2002 | 0 {testing => tests/example}/Input/data_weather/weath.2003 | 0 {testing => tests/example}/Input/data_weather/weath.2004 | 0 {testing => tests/example}/Input/data_weather/weath.2005 | 0 {testing => tests/example}/Input/data_weather/weath.2006 | 0 {testing => tests/example}/Input/data_weather/weath.2007 | 0 {testing => tests/example}/Input/data_weather/weath.2008 | 0 {testing => tests/example}/Input/data_weather/weath.2009 | 0 {testing => tests/example}/Input/data_weather/weath.2010 | 0 .../example}/Input/data_weather_missing/weath.1980 | 0 .../example}/Input/data_weather_missing/weath.2010 | 0 {testing => tests/example}/Input/estab.in | 0 {testing => tests/example}/Input/mkv_covar.in | 0 {testing => tests/example}/Input/mkv_prob.in | 0 {testing => tests/example}/Input/outsetup.in | 0 {testing => tests/example}/Input/siteparam.in | 0 {testing => tests/example}/Input/soils.in | 0 {testing => tests/example}/Input/swcsetup.in | 0 {testing => tests/example}/Input/swrc_params.in | 0 {testing => tests/example}/Input/swrc_params_FXW.in | 0 .../example}/Input/swrc_params_vanGenuchten1980.in | 0 {testing => tests/example}/Input/veg.in | 0 {testing => tests/example}/Input/weathsetup.in | 0 {testing => tests/example}/Input/years.in | 0 {testing => tests/example}/Output/.gitignore | 0 {testing => tests/example}/README.md | 0 {testing => tests/example}/files.in | 0 {test => tests/gtests}/sw_maintest.cc | 0 {test => tests/gtests}/sw_testhelpers.cc | 0 {test => tests/gtests}/sw_testhelpers.h | 0 {test => tests/gtests}/test_SW_Carbon.cc | 0 {test => tests/gtests}/test_SW_Defines.cc | 0 {test => tests/gtests}/test_SW_Flow_Lib.cc | 0 {test => tests/gtests}/test_SW_Flow_Lib_PET.cc | 0 {test => tests/gtests}/test_SW_Flow_lib_temp.cc | 0 {test => tests/gtests}/test_SW_Markov.cc | 0 {test => tests/gtests}/test_SW_Site.cc | 0 {test => tests/gtests}/test_SW_SoilWater.cc | 0 {test => tests/gtests}/test_SW_VegProd.cc | 0 {test => tests/gtests}/test_SW_Weather.cc | 0 {test => tests/gtests}/test_Times.cc | 0 {test => tests/gtests}/test_WaterBalance.cc | 0 {test => tests/gtests}/test_generic.cc | 0 {test => tests/gtests}/test_rands.cc | 0 126 files changed, 6 insertions(+), 5 deletions(-) rename googletest => external/googletest (100%) rename pcg => external/pcg (100%) rename SW_Carbon.h => include/SW_Carbon.h (100%) rename SW_Control.h => include/SW_Control.h (100%) rename SW_Defines.h => include/SW_Defines.h (100%) rename SW_Files.h => include/SW_Files.h (100%) rename SW_Flow.h => include/SW_Flow.h (100%) rename SW_Flow_lib.h => include/SW_Flow_lib.h (100%) rename SW_Flow_lib_PET.h => include/SW_Flow_lib_PET.h (100%) rename SW_Main_lib.h => include/SW_Main_lib.h (100%) rename SW_Markov.h => include/SW_Markov.h (100%) rename SW_Model.h => include/SW_Model.h (100%) rename SW_Output.h => include/SW_Output.h (100%) rename SW_Output_outarray.h => include/SW_Output_outarray.h (100%) rename SW_Output_outtext.h => include/SW_Output_outtext.h (100%) rename SW_Site.h => include/SW_Site.h (100%) rename SW_Sky.h => include/SW_Sky.h (100%) rename SW_SoilWater.h => include/SW_SoilWater.h (100%) rename SW_Times.h => include/SW_Times.h (100%) rename SW_VegEstab.h => include/SW_VegEstab.h (100%) rename SW_VegProd.h => include/SW_VegProd.h (100%) rename SW_Weather.h => include/SW_Weather.h (100%) rename Times.h => include/Times.h (100%) rename filefuncs.h => include/filefuncs.h (100%) rename generic.h => include/generic.h (100%) rename memblock.h => include/memblock.h (100%) rename myMemory.h => include/myMemory.h (100%) rename rands.h => include/rands.h (100%) rename SW_Carbon.c => src/SW_Carbon.c (100%) rename SW_Control.c => src/SW_Control.c (100%) rename SW_Files.c => src/SW_Files.c (100%) rename SW_Flow.c => src/SW_Flow.c (100%) rename SW_Flow_lib.c => src/SW_Flow_lib.c (100%) rename SW_Flow_lib_PET.c => src/SW_Flow_lib_PET.c (100%) rename SW_Main.c => src/SW_Main.c (100%) rename SW_Main_lib.c => src/SW_Main_lib.c (100%) rename SW_Markov.c => src/SW_Markov.c (100%) rename SW_Model.c => src/SW_Model.c (100%) rename SW_Output.c => src/SW_Output.c (100%) rename SW_Output_get_functions.c => src/SW_Output_get_functions.c (100%) rename SW_Output_mock.c => src/SW_Output_mock.c (100%) rename SW_Output_outarray.c => src/SW_Output_outarray.c (100%) rename SW_Output_outtext.c => src/SW_Output_outtext.c (100%) rename SW_Site.c => src/SW_Site.c (100%) rename SW_Sky.c => src/SW_Sky.c (100%) rename SW_SoilWater.c => src/SW_SoilWater.c (100%) rename SW_VegEstab.c => src/SW_VegEstab.c (100%) rename SW_VegProd.c => src/SW_VegProd.c (100%) rename SW_Weather.c => src/SW_Weather.c (100%) rename Times.c => src/Times.c (100%) rename filefuncs.c => src/filefuncs.c (100%) rename generic.c => src/generic.c (100%) rename mymemory.c => src/mymemory.c (100%) rename rands.c => src/rands.c (100%) rename {testing => tests/example}/Input/bouteloua.estab (100%) rename {testing => tests/example}/Input/bromus.estab (100%) rename {testing => tests/example}/Input/carbon.in (100%) rename {testing => tests/example}/Input/climate.in (100%) rename {testing => tests/example}/Input/data_weather/weath.1980 (100%) rename {testing => tests/example}/Input/data_weather/weath.1981 (100%) rename {testing => tests/example}/Input/data_weather/weath.1982 (100%) rename {testing => tests/example}/Input/data_weather/weath.1983 (100%) rename {testing => tests/example}/Input/data_weather/weath.1984 (100%) rename {testing => tests/example}/Input/data_weather/weath.1985 (100%) rename {testing => tests/example}/Input/data_weather/weath.1986 (100%) rename {testing => tests/example}/Input/data_weather/weath.1987 (100%) rename {testing => tests/example}/Input/data_weather/weath.1988 (100%) rename {testing => tests/example}/Input/data_weather/weath.1989 (100%) rename {testing => tests/example}/Input/data_weather/weath.1990 (100%) rename {testing => tests/example}/Input/data_weather/weath.1991 (100%) rename {testing => tests/example}/Input/data_weather/weath.1992 (100%) rename {testing => tests/example}/Input/data_weather/weath.1993 (100%) rename {testing => tests/example}/Input/data_weather/weath.1994 (100%) rename {testing => tests/example}/Input/data_weather/weath.1995 (100%) rename {testing => tests/example}/Input/data_weather/weath.1996 (100%) rename {testing => tests/example}/Input/data_weather/weath.1997 (100%) rename {testing => tests/example}/Input/data_weather/weath.1998 (100%) rename {testing => tests/example}/Input/data_weather/weath.1999 (100%) rename {testing => tests/example}/Input/data_weather/weath.2000 (100%) rename {testing => tests/example}/Input/data_weather/weath.2001 (100%) rename {testing => tests/example}/Input/data_weather/weath.2002 (100%) rename {testing => tests/example}/Input/data_weather/weath.2003 (100%) rename {testing => tests/example}/Input/data_weather/weath.2004 (100%) rename {testing => tests/example}/Input/data_weather/weath.2005 (100%) rename {testing => tests/example}/Input/data_weather/weath.2006 (100%) rename {testing => tests/example}/Input/data_weather/weath.2007 (100%) rename {testing => tests/example}/Input/data_weather/weath.2008 (100%) rename {testing => tests/example}/Input/data_weather/weath.2009 (100%) rename {testing => tests/example}/Input/data_weather/weath.2010 (100%) rename {testing => tests/example}/Input/data_weather_missing/weath.1980 (100%) rename {testing => tests/example}/Input/data_weather_missing/weath.2010 (100%) rename {testing => tests/example}/Input/estab.in (100%) rename {testing => tests/example}/Input/mkv_covar.in (100%) rename {testing => tests/example}/Input/mkv_prob.in (100%) rename {testing => tests/example}/Input/outsetup.in (100%) rename {testing => tests/example}/Input/siteparam.in (100%) rename {testing => tests/example}/Input/soils.in (100%) rename {testing => tests/example}/Input/swcsetup.in (100%) rename {testing => tests/example}/Input/swrc_params.in (100%) rename {testing => tests/example}/Input/swrc_params_FXW.in (100%) rename {testing => tests/example}/Input/swrc_params_vanGenuchten1980.in (100%) rename {testing => tests/example}/Input/veg.in (100%) rename {testing => tests/example}/Input/weathsetup.in (100%) rename {testing => tests/example}/Input/years.in (100%) rename {testing => tests/example}/Output/.gitignore (100%) rename {testing => tests/example}/README.md (100%) rename {testing => tests/example}/files.in (100%) rename {test => tests/gtests}/sw_maintest.cc (100%) rename {test => tests/gtests}/sw_testhelpers.cc (100%) rename {test => tests/gtests}/sw_testhelpers.h (100%) rename {test => tests/gtests}/test_SW_Carbon.cc (100%) rename {test => tests/gtests}/test_SW_Defines.cc (100%) rename {test => tests/gtests}/test_SW_Flow_Lib.cc (100%) rename {test => tests/gtests}/test_SW_Flow_Lib_PET.cc (100%) rename {test => tests/gtests}/test_SW_Flow_lib_temp.cc (100%) rename {test => tests/gtests}/test_SW_Markov.cc (100%) rename {test => tests/gtests}/test_SW_Site.cc (100%) rename {test => tests/gtests}/test_SW_SoilWater.cc (100%) rename {test => tests/gtests}/test_SW_VegProd.cc (100%) rename {test => tests/gtests}/test_SW_Weather.cc (100%) rename {test => tests/gtests}/test_Times.cc (100%) rename {test => tests/gtests}/test_WaterBalance.cc (100%) rename {test => tests/gtests}/test_generic.cc (100%) rename {test => tests/gtests}/test_rands.cc (100%) diff --git a/.gitignore b/.gitignore index 2ce2a2243..30b33c976 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* @@ -39,7 +40,7 @@ doc/Doxyfile.bak # Binary files 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/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 100% rename from SW_Carbon.h rename to include/SW_Carbon.h diff --git a/SW_Control.h b/include/SW_Control.h similarity index 100% rename from SW_Control.h rename to include/SW_Control.h diff --git a/SW_Defines.h b/include/SW_Defines.h similarity index 100% rename from SW_Defines.h rename to include/SW_Defines.h diff --git a/SW_Files.h b/include/SW_Files.h similarity index 100% rename from SW_Files.h rename to include/SW_Files.h 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 100% rename from SW_Flow_lib_PET.h rename to include/SW_Flow_lib_PET.h diff --git a/SW_Main_lib.h b/include/SW_Main_lib.h similarity index 100% rename from SW_Main_lib.h rename to include/SW_Main_lib.h diff --git a/SW_Markov.h b/include/SW_Markov.h similarity index 100% rename from SW_Markov.h rename to include/SW_Markov.h diff --git a/SW_Model.h b/include/SW_Model.h similarity index 100% rename from SW_Model.h rename to include/SW_Model.h diff --git a/SW_Output.h b/include/SW_Output.h similarity index 100% rename from SW_Output.h rename to include/SW_Output.h 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 100% rename from SW_Output_outtext.h rename to include/SW_Output_outtext.h diff --git a/SW_Site.h b/include/SW_Site.h similarity index 100% rename from SW_Site.h rename to include/SW_Site.h diff --git a/SW_Sky.h b/include/SW_Sky.h similarity index 100% rename from SW_Sky.h rename to include/SW_Sky.h diff --git a/SW_SoilWater.h b/include/SW_SoilWater.h similarity index 100% rename from SW_SoilWater.h rename to include/SW_SoilWater.h diff --git a/SW_Times.h b/include/SW_Times.h similarity index 100% rename from SW_Times.h rename to include/SW_Times.h diff --git a/SW_VegEstab.h b/include/SW_VegEstab.h similarity index 100% rename from SW_VegEstab.h rename to include/SW_VegEstab.h diff --git a/SW_VegProd.h b/include/SW_VegProd.h similarity index 100% rename from SW_VegProd.h rename to include/SW_VegProd.h diff --git a/SW_Weather.h b/include/SW_Weather.h similarity index 100% rename from SW_Weather.h rename to include/SW_Weather.h diff --git a/Times.h b/include/Times.h similarity index 100% rename from Times.h rename to include/Times.h diff --git a/filefuncs.h b/include/filefuncs.h similarity index 100% rename from filefuncs.h rename to include/filefuncs.h diff --git a/generic.h b/include/generic.h similarity index 100% rename from generic.h rename to include/generic.h 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 100% rename from myMemory.h rename to include/myMemory.h diff --git a/rands.h b/include/rands.h similarity index 100% rename from rands.h rename to include/rands.h diff --git a/SW_Carbon.c b/src/SW_Carbon.c similarity index 100% rename from SW_Carbon.c rename to src/SW_Carbon.c diff --git a/SW_Control.c b/src/SW_Control.c similarity index 100% rename from SW_Control.c rename to src/SW_Control.c diff --git a/SW_Files.c b/src/SW_Files.c similarity index 100% rename from SW_Files.c rename to src/SW_Files.c diff --git a/SW_Flow.c b/src/SW_Flow.c similarity index 100% rename from SW_Flow.c rename to src/SW_Flow.c diff --git a/SW_Flow_lib.c b/src/SW_Flow_lib.c similarity index 100% rename from SW_Flow_lib.c rename to src/SW_Flow_lib.c diff --git a/SW_Flow_lib_PET.c b/src/SW_Flow_lib_PET.c similarity index 100% rename from SW_Flow_lib_PET.c rename to src/SW_Flow_lib_PET.c diff --git a/SW_Main.c b/src/SW_Main.c similarity index 100% rename from SW_Main.c rename to src/SW_Main.c diff --git a/SW_Main_lib.c b/src/SW_Main_lib.c similarity index 100% rename from SW_Main_lib.c rename to src/SW_Main_lib.c diff --git a/SW_Markov.c b/src/SW_Markov.c similarity index 100% rename from SW_Markov.c rename to src/SW_Markov.c diff --git a/SW_Model.c b/src/SW_Model.c similarity index 100% rename from SW_Model.c rename to src/SW_Model.c diff --git a/SW_Output.c b/src/SW_Output.c similarity index 100% rename from SW_Output.c rename to src/SW_Output.c diff --git a/SW_Output_get_functions.c b/src/SW_Output_get_functions.c similarity index 100% rename from SW_Output_get_functions.c rename to src/SW_Output_get_functions.c diff --git a/SW_Output_mock.c b/src/SW_Output_mock.c similarity index 100% rename from SW_Output_mock.c rename to src/SW_Output_mock.c diff --git a/SW_Output_outarray.c b/src/SW_Output_outarray.c similarity index 100% rename from SW_Output_outarray.c rename to src/SW_Output_outarray.c diff --git a/SW_Output_outtext.c b/src/SW_Output_outtext.c similarity index 100% rename from SW_Output_outtext.c rename to src/SW_Output_outtext.c diff --git a/SW_Site.c b/src/SW_Site.c similarity index 100% rename from SW_Site.c rename to src/SW_Site.c diff --git a/SW_Sky.c b/src/SW_Sky.c similarity index 100% rename from SW_Sky.c rename to src/SW_Sky.c diff --git a/SW_SoilWater.c b/src/SW_SoilWater.c similarity index 100% rename from SW_SoilWater.c rename to src/SW_SoilWater.c diff --git a/SW_VegEstab.c b/src/SW_VegEstab.c similarity index 100% rename from SW_VegEstab.c rename to src/SW_VegEstab.c diff --git a/SW_VegProd.c b/src/SW_VegProd.c similarity index 100% rename from SW_VegProd.c rename to src/SW_VegProd.c diff --git a/SW_Weather.c b/src/SW_Weather.c similarity index 100% rename from SW_Weather.c rename to src/SW_Weather.c diff --git a/Times.c b/src/Times.c similarity index 100% rename from Times.c rename to src/Times.c diff --git a/filefuncs.c b/src/filefuncs.c similarity index 100% rename from filefuncs.c rename to src/filefuncs.c diff --git a/generic.c b/src/generic.c similarity index 100% rename from generic.c rename to src/generic.c diff --git a/mymemory.c b/src/mymemory.c similarity index 100% rename from mymemory.c rename to src/mymemory.c diff --git a/rands.c b/src/rands.c similarity index 100% rename from rands.c rename to src/rands.c diff --git a/testing/Input/bouteloua.estab b/tests/example/Input/bouteloua.estab similarity index 100% rename from testing/Input/bouteloua.estab rename to tests/example/Input/bouteloua.estab diff --git a/testing/Input/bromus.estab b/tests/example/Input/bromus.estab similarity index 100% rename from testing/Input/bromus.estab rename to tests/example/Input/bromus.estab 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/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/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 100% rename from testing/Input/siteparam.in rename to tests/example/Input/siteparam.in 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/testing/Input/swrc_params.in b/tests/example/Input/swrc_params.in similarity index 100% rename from testing/Input/swrc_params.in rename to tests/example/Input/swrc_params.in diff --git a/testing/Input/swrc_params_FXW.in b/tests/example/Input/swrc_params_FXW.in similarity index 100% rename from testing/Input/swrc_params_FXW.in rename to tests/example/Input/swrc_params_FXW.in diff --git a/testing/Input/swrc_params_vanGenuchten1980.in b/tests/example/Input/swrc_params_vanGenuchten1980.in similarity index 100% rename from testing/Input/swrc_params_vanGenuchten1980.in rename to tests/example/Input/swrc_params_vanGenuchten1980.in diff --git a/testing/Input/veg.in b/tests/example/Input/veg.in similarity index 100% rename from testing/Input/veg.in rename to tests/example/Input/veg.in diff --git a/testing/Input/weathsetup.in b/tests/example/Input/weathsetup.in similarity index 100% rename from testing/Input/weathsetup.in rename to tests/example/Input/weathsetup.in 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 100% rename from testing/files.in rename to tests/example/files.in diff --git a/test/sw_maintest.cc b/tests/gtests/sw_maintest.cc similarity index 100% rename from test/sw_maintest.cc rename to tests/gtests/sw_maintest.cc diff --git a/test/sw_testhelpers.cc b/tests/gtests/sw_testhelpers.cc similarity index 100% rename from test/sw_testhelpers.cc rename to tests/gtests/sw_testhelpers.cc 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 100% rename from test/test_SW_Carbon.cc rename to tests/gtests/test_SW_Carbon.cc diff --git a/test/test_SW_Defines.cc b/tests/gtests/test_SW_Defines.cc similarity index 100% rename from test/test_SW_Defines.cc rename to tests/gtests/test_SW_Defines.cc diff --git a/test/test_SW_Flow_Lib.cc b/tests/gtests/test_SW_Flow_Lib.cc similarity index 100% rename from test/test_SW_Flow_Lib.cc rename to tests/gtests/test_SW_Flow_Lib.cc diff --git a/test/test_SW_Flow_Lib_PET.cc b/tests/gtests/test_SW_Flow_Lib_PET.cc similarity index 100% rename from test/test_SW_Flow_Lib_PET.cc rename to tests/gtests/test_SW_Flow_Lib_PET.cc diff --git a/test/test_SW_Flow_lib_temp.cc b/tests/gtests/test_SW_Flow_lib_temp.cc similarity index 100% rename from test/test_SW_Flow_lib_temp.cc rename to tests/gtests/test_SW_Flow_lib_temp.cc diff --git a/test/test_SW_Markov.cc b/tests/gtests/test_SW_Markov.cc similarity index 100% rename from test/test_SW_Markov.cc rename to tests/gtests/test_SW_Markov.cc diff --git a/test/test_SW_Site.cc b/tests/gtests/test_SW_Site.cc similarity index 100% rename from test/test_SW_Site.cc rename to tests/gtests/test_SW_Site.cc diff --git a/test/test_SW_SoilWater.cc b/tests/gtests/test_SW_SoilWater.cc similarity index 100% rename from test/test_SW_SoilWater.cc rename to tests/gtests/test_SW_SoilWater.cc diff --git a/test/test_SW_VegProd.cc b/tests/gtests/test_SW_VegProd.cc similarity index 100% rename from test/test_SW_VegProd.cc rename to tests/gtests/test_SW_VegProd.cc diff --git a/test/test_SW_Weather.cc b/tests/gtests/test_SW_Weather.cc similarity index 100% rename from test/test_SW_Weather.cc rename to tests/gtests/test_SW_Weather.cc diff --git a/test/test_Times.cc b/tests/gtests/test_Times.cc similarity index 100% rename from test/test_Times.cc rename to tests/gtests/test_Times.cc diff --git a/test/test_WaterBalance.cc b/tests/gtests/test_WaterBalance.cc similarity index 100% rename from test/test_WaterBalance.cc rename to tests/gtests/test_WaterBalance.cc diff --git a/test/test_generic.cc b/tests/gtests/test_generic.cc similarity index 100% rename from test/test_generic.cc rename to tests/gtests/test_generic.cc diff --git a/test/test_rands.cc b/tests/gtests/test_rands.cc similarity index 100% rename from test/test_rands.cc rename to tests/gtests/test_rands.cc From dc521a4a5036c964503a99891337e15b1fe21af6 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 21 Dec 2022 16:10:43 -0500 Subject: [PATCH 227/326] Re-organize repository (part 2) - addressing #89 - update file paths (see previous commit 90b17413fee14aa61193e66ebb5757aae5cded63 "Re-organize repository (part 1)" - note: additional commits to complete #89 will need to update makefile and scripts --- .gitignore | 4 +- README.md | 8 +-- doc/Doxyfile | 10 ++-- doc/additional_pages/A_SOILWAT2_user_guide.md | 14 +++-- doc/additional_pages/SOILWAT2_Inputs.md | 39 +++++++------- doc/additional_pages/SOILWAT2_Outputs.md | 12 ++--- include/SW_Carbon.h | 2 +- include/SW_Defines.h | 2 +- include/SW_Markov.h | 2 +- include/SW_Model.h | 4 +- include/SW_Output.h | 8 +-- include/SW_Site.h | 2 +- include/SW_Sky.h | 2 +- include/SW_SoilWater.h | 8 +-- include/SW_Times.h | 2 +- include/SW_VegEstab.h | 4 +- include/SW_VegProd.h | 4 +- include/SW_Weather.h | 10 ++-- include/Times.h | 2 +- include/filefuncs.h | 4 +- include/myMemory.h | 4 +- include/rands.h | 2 +- src/SW_Carbon.c | 20 +++---- src/SW_Control.c | 42 +++++++-------- src/SW_Files.c | 12 ++--- src/SW_Flow.c | 28 +++++----- src/SW_Flow_lib.c | 20 +++---- src/SW_Flow_lib_PET.c | 6 +-- src/SW_Main.c | 18 +++---- src/SW_Main_lib.c | 14 ++--- src/SW_Markov.c | 24 ++++----- src/SW_Model.c | 24 ++++----- src/SW_Output.c | 42 +++++++-------- src/SW_Output_get_functions.c | 42 +++++++-------- src/SW_Output_mock.c | 32 +++++------ src/SW_Output_outarray.c | 18 +++---- src/SW_Output_outtext.c | 20 +++---- src/SW_Site.c | 20 +++---- src/SW_Sky.c | 16 +++--- src/SW_SoilWater.c | 28 +++++----- src/SW_VegEstab.c | 22 ++++---- src/SW_VegProd.c | 20 +++---- src/SW_Weather.c | 22 ++++---- src/Times.c | 4 +- src/filefuncs.c | 8 +-- src/generic.c | 14 ++--- src/mymemory.c | 6 +-- src/rands.c | 10 ++-- tests/gtests/sw_maintest.cc | 54 +++++++++---------- tests/gtests/sw_testhelpers.cc | 40 +++++++------- tests/gtests/test_SW_Carbon.cc | 38 ++++++------- tests/gtests/test_SW_Defines.cc | 6 +-- tests/gtests/test_SW_Flow_Lib.cc | 44 +++++++-------- tests/gtests/test_SW_Flow_Lib_PET.cc | 44 +++++++-------- tests/gtests/test_SW_Flow_lib_temp.cc | 46 ++++++++-------- tests/gtests/test_SW_Markov.cc | 38 ++++++------- tests/gtests/test_SW_Site.cc | 38 ++++++------- tests/gtests/test_SW_SoilWater.cc | 24 ++++----- tests/gtests/test_SW_VegProd.cc | 38 ++++++------- tests/gtests/test_SW_Weather.cc | 12 ++--- tests/gtests/test_Times.cc | 28 +++++----- tests/gtests/test_WaterBalance.cc | 40 +++++++------- tests/gtests/test_generic.cc | 30 +++++------ tests/gtests/test_rands.cc | 10 ++-- tools/check_SOILWAT2.sh | 6 +-- 65 files changed, 606 insertions(+), 611 deletions(-) diff --git a/.gitignore b/.gitignore index 30b33c976..8ee1176be 100644 --- a/.gitignore +++ b/.gitignore @@ -38,7 +38,9 @@ doc/html/* doc/log_doxygen.log doc/Doxyfile.bak -# Binary files +# Build and binary directories and files +bin/* +build/* SOILWAT2 tests/example/SOILWAT2 libSOILWAT2* diff --git a/README.md b/README.md index 946c38cd8..7ad1e62e1 100644 --- a/README.md +++ b/README.md @@ -239,7 +239,7 @@ __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). @@ -247,7 +247,7 @@ for one generic location make bin bint_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 @@ -263,7 +263,7 @@ The following steps provide a starting point for such comparisons: # Simulate on refernce branch and copy output to "Output_ref" git checkout master make bin bint_run - cp -r testing/Output testing/Output_ref + cp -r tests/example/Output tests/example/Output_ref # Switch to development branch and run the same simulation git checkout @@ -271,7 +271,7 @@ The following steps provide a starting point for such comparisons: # 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 ``` diff --git a/doc/Doxyfile b/doc/Doxyfile index 6b4faa526..ceb06ca65 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -876,8 +876,8 @@ WARN_LOGFILE = INPUT = . \ 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 +925,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 +958,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 diff --git a/doc/additional_pages/A_SOILWAT2_user_guide.md b/doc/additional_pages/A_SOILWAT2_user_guide.md index 8ca3ce03d..3636b9d1f 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/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 926d1b3c2..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". @@ -64,78 +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 testing/Input/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/include/SW_Carbon.h b/include/SW_Carbon.h index 9a63525df..ec46c4494 100644 --- a/include/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/include/SW_Defines.h b/include/SW_Defines.h index 2380eba6d..6c13cce8c 100644 --- a/include/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" { diff --git a/include/SW_Markov.h b/include/SW_Markov.h index ce0e8c36f..e7e99854a 100644 --- a/include/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/include/SW_Model.h b/include/SW_Model.h index 2cb5d4862..4087c32df 100644 --- a/include/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/include/SW_Output.h b/include/SW_Output.h index 8a7e7bae6..ff37efaa1 100644 --- a/include/SW_Output.h +++ b/include/SW_Output.h @@ -56,10 +56,10 @@ #ifndef SW_OUTPUT_H #define SW_OUTPUT_H -#include "Times.h" -#include "SW_Defines.h" -#include "SW_SoilWater.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" { diff --git a/include/SW_Site.h b/include/SW_Site.h index 8f1f5de39..0a48b8f69 100644 --- a/include/SW_Site.h +++ b/include/SW_Site.h @@ -47,7 +47,7 @@ #ifndef SW_SITE_H #define SW_SITE_H -#include "SW_Defines.h" +#include "include/SW_Defines.h" #ifdef __cplusplus extern "C" { diff --git a/include/SW_Sky.h b/include/SW_Sky.h index c6a8beb74..fe56ef784 100644 --- a/include/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" { diff --git a/include/SW_SoilWater.h b/include/SW_SoilWater.h index dc40a94e3..d9424fef9 100644 --- a/include/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 diff --git a/include/SW_Times.h b/include/SW_Times.h index 401c6d199..439e1bf5f 100644 --- a/include/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/include/SW_VegEstab.h b/include/SW_VegEstab.h index bc6e69fcf..ce5b9478d 100644 --- a/include/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/include/SW_VegProd.h b/include/SW_VegProd.h index 530dd267c..bb8659613 100644 --- a/include/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" { diff --git a/include/SW_Weather.h b/include/SW_Weather.h index 0aa7f8853..ada62040b 100644 --- a/include/SW_Weather.h +++ b/include/SW_Weather.h @@ -24,8 +24,8 @@ #ifndef SW_WEATHER_H #define SW_WEATHER_H -#include "SW_Times.h" -#include "SW_Defines.h" +#include "include/SW_Times.h" +#include "include/SW_Defines.h" #ifdef __cplusplus extern "C" { @@ -59,9 +59,9 @@ typedef struct { /** @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. */ @@ -87,7 +87,7 @@ typedef struct { /** @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()`. diff --git a/include/Times.h b/include/Times.h index bceef1e06..86cc9c876 100644 --- a/include/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" { diff --git a/include/filefuncs.h b/include/filefuncs.h index ab28edbbd..f168ad5e3 100644 --- a/include/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/include/myMemory.h b/include/myMemory.h index 54e224a83..a150e4be0 100644 --- a/include/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/include/rands.h b/include/rands.h index 68b708f2e..33a58e6af 100644 --- a/include/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/src/SW_Carbon.c b/src/SW_Carbon.c index 63cfff88b..d2fdbdaa3 100644 --- a/src/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 diff --git a/src/SW_Control.c b/src/SW_Control.c index 710afa6f0..f47d4116d 100644 --- a/src/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" @@ -315,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/src/SW_Files.c b/src/SW_Files.c index 69a615c98..ef93b8094 100644 --- a/src/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" /* =================================================== */ @@ -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/src/SW_Flow.c b/src/SW_Flow.c index 293009bd8..a79bf317a 100644 --- a/src/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" /* =================================================== */ diff --git a/src/SW_Flow_lib.c b/src/SW_Flow_lib.c index 480594907..c818162bd 100644 --- a/src/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 "SW_Model.h" // externs SW_Model +#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 "include/SW_Model.h" // externs SW_Model diff --git a/src/SW_Flow_lib_PET.c b/src/SW_Flow_lib_PET.c index e35c8ae49..aaa8d900c 100644 --- a/src/SW_Flow_lib_PET.c +++ b/src/SW_Flow_lib_PET.c @@ -14,10 +14,10 @@ /* --------------------------------------------------- */ #include -#include "generic.h" -#include "SW_Defines.h" +#include "include/generic.h" +#include "include/SW_Defines.h" -#include "SW_Flow_lib_PET.h" +#include "include/SW_Flow_lib_PET.h" diff --git a/src/SW_Main.c b/src/SW_Main.c index 358d6405e..538747bf4 100644 --- a/src/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" diff --git a/src/SW_Main_lib.c b/src/SW_Main_lib.c index 307ce56e4..e591d1e29 100644 --- a/src/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" /* =================================================== */ diff --git a/src/SW_Markov.c b/src/SW_Markov.c index ee083faa9..e27e6e49d 100644 --- a/src/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" /* =================================================== */ @@ -618,7 +618,7 @@ void SW_MKV_setup(void) { #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/src/SW_Model.c b/src/SW_Model.c index c111c8e83..01b034bf3 100644 --- a/src/SW_Model.c +++ b/src/SW_Model.c @@ -35,18 +35,18 @@ #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_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/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_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 /* =================================================== */ diff --git a/src/SW_Output.c b/src/SW_Output.c index b5ae5605b..7cd5ddd02 100644 --- a/src/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` @@ -2589,7 +2589,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/src/SW_Output_get_functions.c b/src/SW_Output_get_functions.c index 4fe1baccd..ca4dbd09e 100644 --- a/src/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 diff --git a/src/SW_Output_mock.c b/src/SW_Output_mock.c index b6264c57f..d5c3e623f 100644 --- a/src/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/src/SW_Output_outarray.c b/src/SW_Output_outarray.c index 14d76d23d..049e59e17 100644 --- a/src/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 diff --git a/src/SW_Output_outtext.c b/src/SW_Output_outtext.c index 581ba1b30..a7f729ade 100644 --- a/src/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" diff --git a/src/SW_Site.c b/src/SW_Site.c index 821840da9..a5763bcbc 100644 --- a/src/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 @@ -2623,7 +2623,7 @@ void _echo_inputs(void) { } #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/src/SW_Sky.c b/src/SW_Sky.c index d2d1d4ca3..f5f6b2cef 100644 --- a/src/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 diff --git a/src/SW_SoilWater.c b/src/SW_SoilWater.c index 125d093b5..d40309c66 100644 --- a/src/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 @@ -1824,7 +1824,7 @@ double SWRC_SWPtoSWC_FXW( */ #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/src/SW_VegEstab.c b/src/SW_VegEstab.c index ed04dc31b..bd12178ed 100644 --- a/src/SW_VegEstab.c +++ b/src/SW_VegEstab.c @@ -35,17 +35,17 @@ #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_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_VegEstab.h" diff --git a/src/SW_VegProd.c b/src/SW_VegProd.c index 9096b7e20..a62251281 100644 --- a/src/SW_VegProd.c +++ b/src/SW_VegProd.c @@ -45,16 +45,16 @@ changed _echo_inits() to now display the bare ground components in logfile.log #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 -#include "SW_Weather.h" -#include "SW_Site.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_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" diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 7d8ca45ed..a7ec4c3e5 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -41,17 +41,17 @@ #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 "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 "SW_Weather.h" +#include "include/SW_Weather.h" @@ -1289,7 +1289,7 @@ void deallocateClimateStructs(SW_CLIMATE_YEARLY *climateOutput, } #ifdef DEBUG_MEM -#include "myMemory.h" +#include "include/myMemory.h" /*======================================================*/ void SW_WTH_SetMemoryRefs( void) { /* when debugging memory problems, use the bookkeeping diff --git a/src/Times.c b/src/Times.c index ac473ae5b..6b0d91dbf 100644 --- a/src/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" /* =================================================== */ diff --git a/src/filefuncs.c b/src/filefuncs.c index e754b51b3..5d4e689f4 100644 --- a/src/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 diff --git a/src/generic.c b/src/generic.c index 49dce535b..97d48967d 100644 --- a/src/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" @@ -444,7 +444,7 @@ double mean(double values[], int length) { finalLength++; } } - + return total / finalLength; } @@ -461,10 +461,10 @@ double mean(double values[], int length) { 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]; @@ -473,7 +473,7 @@ double standardDeviation(double inputArray[], int length) { finalLength++; } } - + return sqrt(total / (finalLength - 1)); - + } diff --git a/src/mymemory.c b/src/mymemory.c index 92d2f84c4..b6af21380 100644 --- a/src/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/src/rands.c b/src/rands.c index 2f171f619..da8bd9013 100644 --- a/src/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 diff --git a/tests/gtests/sw_maintest.cc b/tests/gtests/sw_maintest.cc index 93fcf6640..8e0e0a603 100644 --- a/tests/gtests/sw_maintest.cc +++ b/tests/gtests/sw_maintest.cc @@ -15,36 +15,36 @@ #include #include -#include "../myMemory.h" +#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/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" + + +/* 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' diff --git a/tests/gtests/sw_testhelpers.cc b/tests/gtests/sw_testhelpers.cc index 56b5b2b2f..f9936a00e 100644 --- a/tests/gtests/sw_testhelpers.cc +++ b/tests/gtests/sw_testhelpers.cc @@ -15,27 +15,27 @@ #include #include -#include "../myMemory.h" +#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/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" diff --git a/tests/gtests/test_SW_Carbon.cc b/tests/gtests/test_SW_Carbon.cc index 44031e8ed..2cc80af3e 100644 --- a/tests/gtests/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/tests/gtests/test_SW_Defines.cc b/tests/gtests/test_SW_Defines.cc index d72aaa2bd..0f3d62314 100644 --- a/tests/gtests/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/tests/gtests/test_SW_Flow_Lib.cc b/tests/gtests/test_SW_Flow_Lib.cc index 284576820..9ba14139d 100644 --- a/tests/gtests/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" diff --git a/tests/gtests/test_SW_Flow_Lib_PET.cc b/tests/gtests/test_SW_Flow_Lib_PET.cc index 2a7b02215..fddbfbf56 100644 --- a/tests/gtests/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" diff --git a/tests/gtests/test_SW_Flow_lib_temp.cc b/tests/gtests/test_SW_Flow_lib_temp.cc index 4f60f2a21..c6ca28e2d 100644 --- a/tests/gtests/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; diff --git a/tests/gtests/test_SW_Markov.cc b/tests/gtests/test_SW_Markov.cc index 992a6918e..084fd82a4 100644 --- a/tests/gtests/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" diff --git a/tests/gtests/test_SW_Site.cc b/tests/gtests/test_SW_Site.cc index cfbbca391..6363ca59f 100644 --- a/tests/gtests/test_SW_Site.cc +++ b/tests/gtests/test_SW_Site.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" // 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" +#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" diff --git a/tests/gtests/test_SW_SoilWater.cc b/tests/gtests/test_SW_SoilWater.cc index 17f0629ec..94225368d 100644 --- a/tests/gtests/test_SW_SoilWater.cc +++ b/tests/gtests/test_SW_SoilWater.cc @@ -7,18 +7,18 @@ #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" +#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" diff --git a/tests/gtests/test_SW_VegProd.cc b/tests/gtests/test_SW_VegProd.cc index 24239cf0e..983d5e93a 100644 --- a/tests/gtests/test_SW_VegProd.cc +++ b/tests/gtests/test_SW_VegProd.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 "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 "sw_testhelpers.h" +#include "tests/gtests/sw_testhelpers.h" @@ -710,7 +710,7 @@ namespace { Bool inNorthHem = swTRUE; Bool warnExtrapolation = swTRUE; Bool fixBareGround = swTRUE; - + // Reset "SW_Weather.allHist" SW_WTH_read(); diff --git a/tests/gtests/test_SW_Weather.cc b/tests/gtests/test_SW_Weather.cc index 5a3890e4d..8606b6b87 100644 --- a/tests/gtests/test_SW_Weather.cc +++ b/tests/gtests/test_SW_Weather.cc @@ -15,10 +15,10 @@ #include #include -#include "../SW_Weather.h" -#include "sw_testhelpers.h" -#include "../SW_Markov.h" -#include "../SW_Model.h" +#include "include/SW_Weather.h" +#include "tests/gtests/sw_testhelpers.h" +#include "include/SW_Markov.h" +#include "include/SW_Model.h" namespace { @@ -510,9 +510,9 @@ namespace { SW_CLIMATE_YEARLY climateOutput; SW_CLIMATE_CLIM climateAverages; SW_WEATHER_HIST **allHist; - + Bool inNorthHem = swTRUE; - + // Allocate memory allocateClimateStructs(2, &climateOutput, &climateAverages); diff --git a/tests/gtests/test_Times.cc b/tests/gtests/test_Times.cc index 4c59bfc37..d4a73ecaf 100644 --- a/tests/gtests/test_Times.cc +++ b/tests/gtests/test_Times.cc @@ -4,20 +4,20 @@ #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 "tests/gtests/sw_testhelpers.h" namespace{ TEST(TimesTest, LeapYear) { diff --git a/tests/gtests/test_WaterBalance.cc b/tests/gtests/test_WaterBalance.cc index 4472c6917..a9bff44eb 100644 --- a/tests/gtests/test_WaterBalance.cc +++ b/tests/gtests/test_WaterBalance.cc @@ -15,26 +15,26 @@ #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" +#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" diff --git a/tests/gtests/test_generic.cc b/tests/gtests/test_generic.cc index 1b9a95102..6e39b598b 100644 --- a/tests/gtests/test_generic.cc +++ b/tests/gtests/test_generic.cc @@ -15,8 +15,8 @@ #include #include -#include "../generic.h" -#include "../SW_Defines.h" +#include "include/generic.h" +#include "include/SW_Defines.h" namespace { @@ -63,19 +63,19 @@ namespace { 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); @@ -85,20 +85,20 @@ namespace { // 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); @@ -106,7 +106,7 @@ namespace { // Testing the mean function on a normal set of data EXPECT_FLOAT_EQ(result, 14.1); - + } } // namespace diff --git a/tests/gtests/test_rands.cc b/tests/gtests/test_rands.cc index 6d5c69846..15a4f3bfd 100644 --- a/tests/gtests/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..74977d949 100755 --- a/tools/check_SOILWAT2.sh +++ b/tools/check_SOILWAT2.sh @@ -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 } @@ -97,7 +97,7 @@ for ((k = 0; k < ncomp; k++)); do 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 From 2b5c7d71f355337390a0d094d5463b2bea1ca9a7 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Thu, 22 Dec 2022 10:37:26 -0500 Subject: [PATCH 228/326] Re-organize repository (part 3) - address #89 - update makefiles, scripts, and continuous integration setup (see previous commit 90b17413fee14aa61193e66ebb5757aae5cded63 "Re-organize repository (part 1)" (see previous commit dc521a4a5036c964503a99891337e15b1fe21af6 "Re-organize repository (part 2)" --- .github/workflows/check_doc.yml | 2 +- .github/workflows/main_nix.yml | 12 +- .github/workflows/main_win.yml | 4 +- README.md | 14 +- makefile | 466 +++++++++++++++++--------------- tools/check_SOILWAT2.sh | 22 +- tools/many_test_runs.sh | 7 +- tools/run_debug.sh | 9 + tools/run_debug_severe.sh | 33 +++ tools/run_gcov.sh | 17 +- tools/run_test_leaks.sh | 37 +++ tools/run_test_severe.sh | 39 +++ 12 files changed, 414 insertions(+), 248 deletions(-) create mode 100755 tools/run_debug.sh create mode 100755 tools/run_debug_severe.sh create mode 100755 tools/run_test_leaks.sh create mode 100755 tools/run_test_severe.sh diff --git a/.github/workflows/check_doc.yml b/.github/workflows/check_doc.yml index ea7d132e3..111cc0701 100644 --- a/.github/workflows/check_doc.yml +++ b/.github/workflows/check_doc.yml @@ -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 f6f4122d1..0bf2e3867 100644 --- a/.github/workflows/main_nix.yml +++ b/.github/workflows/main_nix.yml @@ -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 + run: make clean test_run - 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: @@ -55,7 +55,7 @@ jobs: 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@v3 diff --git a/.github/workflows/main_win.yml b/.github/workflows/main_win.yml index e6af38842..a6f13f8ef 100644 --- a/.github/workflows/main_win.yml +++ b/.github/workflows/main_win.yml @@ -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 + run: make clean test_run diff --git a/README.md b/README.md index 7ad1e62e1..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 @@ -244,7 +244,7 @@ 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 `tests/example/Output/`. @@ -262,12 +262,12 @@ 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 + 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 @@ -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/makefile b/makefile index efdb71a49..33cd8e09e 100644 --- a/makefile +++ b/makefile @@ -3,70 +3,111 @@ #------------------------------------------------------------------------------- # 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 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 +120,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 +137,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 +151,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 +168,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 +sw_LDFLAGS_bin := $(LDFLAGS) -L$(dir_bin) +sw_LDFLAGS_test := $(LDFLAGS) -L$(dir_bin) -L$(dir_build_test) +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) - -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 +240,168 @@ 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 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 -# 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 #------ 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 : bin_debug +bin_debug : + ./tools/run_debug.sh +.PHONY : bin_debug_severe +bin_debug_severe : + ./tools/run_debug_severe.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 : 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 +411,7 @@ doc_clean : help : less makefile + #--- Target to print compiler information .PHONY : compiler_version compiler_version : @@ -385,3 +419,5 @@ compiler_version : @(echo `"$(CC)" --version`) @(echo CXX version:) @(echo `"$(CXX)" --version`) + @(echo make version:) + @(echo `"$(MAKE)" --version`) diff --git a/tools/check_SOILWAT2.sh b/tools/check_SOILWAT2.sh index 74977d949..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++" ) @@ -93,7 +93,7 @@ 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 @@ -101,7 +101,7 @@ for ((k = 0; k < ncomp; k++)); do 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/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_gcov.sh b/tools/run_gcov.sh index 57d76b825..dc1102153 100755 --- a/tools/run_gcov.sh +++ b/tools/run_gcov.sh @@ -1,5 +1,20 @@ #!/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 @@ -8,5 +23,5 @@ sources_tests='SW_Main_lib.c SW_VegEstab.c SW_Control.c generic.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 From 1ba46e3637263c330989e4434a4058e748aa7448 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Thu, 22 Dec 2022 16:49:17 -0500 Subject: [PATCH 229/326] Update paths of R scripts in tools/ --- tools/plot__SW2_PET_Test__petfunc_by_temps.R | 138 +++++++++++------- tools/plot__SW2_SoilTemperature.R | 18 ++- ...Position_Test__hourangles_by_lat_and_doy.R | 4 +- ...2_SolarPosition_Test__hourangles_by_lats.R | 4 +- 4 files changed, 102 insertions(+), 62 deletions(-) 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" From 8964ad12d112613151edaa085f06f3f99dc6c397 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 23 Dec 2022 07:20:09 -0500 Subject: [PATCH 230/326] Fix "array-bounds" warning in `sw_init_args()` - close #335 - gcc12 issued `warning: array subscript 6 is above array bounds of 'int[6]' [-Warray-bounds]` for `if (valopts[op]) {` while referencing 'valopts' --- src/SW_Main_lib.c | 118 ++++++++++++++++++++++++---------------------- 1 file changed, 61 insertions(+), 57 deletions(-) diff --git a/src/SW_Main_lib.c b/src/SW_Main_lib.c index e591d1e29..5b74d90cf 100644 --- a/src/SW_Main_lib.c +++ b/src/SW_Main_lib.c @@ -139,67 +139,71 @@ 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 */ } - - /* 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) */ } From 86de8172632b4bd679b46b50cbe8620bbfa9c89f Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sat, 24 Dec 2022 16:50:06 -0700 Subject: [PATCH 231/326] Updated weather structs and `scaleAllWeather()` interface - Moved cloud cover, wind speed, and relative humidity to SW_SKY - Added same variables listed above to SW_WEATHER_NOW - Updated `scaleAllWeather()` parameters to include scaling information for cloud cover, wind speed, and relative humidity and added parameter descriptions - Updated calls to `solar_radiation()` and `petfunc()` to take in the "now" value for the required value --- include/SW_Sky.h | 5 +---- include/SW_Weather.h | 10 +++++++--- src/SW_Flow.c | 10 +++++----- src/SW_Weather.c | 13 +++++++++++-- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/include/SW_Sky.h b/include/SW_Sky.h index fe56ef784..49fe1c48a 100644 --- a/include/SW_Sky.h +++ b/include/SW_Sky.h @@ -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; diff --git a/include/SW_Weather.h b/include/SW_Weather.h index ada62040b..33ca6b049 100644 --- a/include/SW_Weather.h +++ b/include/SW_Weather.h @@ -41,12 +41,13 @@ extern "C" { typedef struct { /* Weather values of the current simulation day */ - RealD temp_avg, temp_max, temp_min, ppt, rain; + RealD temp_avg, temp_max, temp_min, ppt, rain, cloudCover, windSpeed, relHumidity; } 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]; + RealD temp_max[MAX_DAYS], temp_min[MAX_DAYS], temp_avg[MAX_DAYS], ppt[MAX_DAYS], + cloudcov_daily[MAX_DAYS+1], windspeed_daily[MAX_DAYS+1], r_humidity_daily[MAX_DAYS+1]; // RealD temp_month_avg[MAX_MONTHS], temp_year_avg; // currently not used } SW_WEATHER_HIST; @@ -221,7 +222,10 @@ void scaleAllWeather( unsigned int n_years, double *scale_temp_max, double *scale_temp_min, - double *scale_precip + double *scale_precip, + double *scale_skyCover, + double *scale_wind, + double *scale_rH ); void generateMissingWeather( SW_WEATHER_HIST **allHist, diff --git a/src/SW_Flow.c b/src/SW_Flow.c index a79bf317a..62df35505 100644 --- a/src/SW_Flow.c +++ b/src/SW_Flow.c @@ -406,8 +406,8 @@ 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.cloudCover, + w->now.relHumidity, w->now.temp_avg, &sw->H_oh, &sw->H_ot, @@ -419,9 +419,9 @@ void SW_Water_Flow(void) { 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 ); diff --git a/src/SW_Weather.c b/src/SW_Weather.c index a7ec4c3e5..622fe4a51 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -557,7 +557,10 @@ void finalizeAllWeather(SW_WEATHER *w) { w->n_years, w->scale_temp_max, w->scale_temp_min, - w->scale_precip + w->scale_precip, + w->scale_skyCover, + w->scale_wind, + w->scale_rH ); } @@ -579,6 +582,9 @@ void SW_WTH_finalize_all_weather(void) { 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 [%] @note Daily average air temperature is re-calculated after scaling minimum and maximum air temperature. @@ -591,7 +597,10 @@ void scaleAllWeather( unsigned int n_years, double *scale_temp_max, double *scale_temp_min, - double *scale_precip + double *scale_precip, + double *scale_skyCover, + double *scale_wind, + double *scale_rH ) { int year, month; From 82cb6ce838c8b241126d8d14a4e95e1a2dbb955b Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Mon, 26 Dec 2022 11:22:06 -0700 Subject: [PATCH 232/326] Moved monthly interpolation and scaling of SW_SKY values - Moved contents of `SW_SKY_init_run()` to `_read_weather_hist()` and deleted `SW_SKY_init_run()` due to no longer doing anything - Removed call to `SW_SKY_init_run()` in `SW_CTL_init_run()` - Updated documentation of `scaleAllWeather()` to mention cloud cover, relative humidity, and wind speed - Added cloud cover, wind speed, and relative humidity to `_clear_hist_weather()` and `SW_WTH_new_day()` --- include/SW_Sky.h | 1 - include/SW_Weather.h | 1 + src/SW_Control.c | 2 +- src/SW_Sky.c | 30 ------------------------------ src/SW_Weather.c | 35 ++++++++++++++++++++++++++++++++++- 5 files changed, 36 insertions(+), 33 deletions(-) diff --git a/include/SW_Sky.h b/include/SW_Sky.h index 49fe1c48a..0868f1f64 100644 --- a/include/SW_Sky.h +++ b/include/SW_Sky.h @@ -48,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/include/SW_Weather.h b/include/SW_Weather.h index 33ca6b049..f0e335d5f 100644 --- a/include/SW_Weather.h +++ b/include/SW_Weather.h @@ -26,6 +26,7 @@ #include "include/SW_Times.h" #include "include/SW_Defines.h" +#include "include/SW_Sky.h" #ifdef __cplusplus extern "C" { diff --git a/src/SW_Control.c b/src/SW_Control.c index f47d4116d..72f3d59bf 100644 --- a/src/SW_Control.c +++ b/src/SW_Control.c @@ -156,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() not needed SW_VPD_init_run(); diff --git a/src/SW_Sky.c b/src/SW_Sky.c index f5f6b2cef..b48c653bc 100644 --- a/src/SW_Sky.c +++ b/src/SW_Sky.c @@ -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) @@ -152,9 +125,6 @@ void SW_SKY_new_year(void) { */ 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); } } diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 622fe4a51..69179f462 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -571,7 +571,8 @@ void SW_WTH_finalize_all_weather(void) { /** - @brief Apply temperature and precipitation scaling to daily weather values + @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`) @@ -639,6 +640,27 @@ void scaleAllWeather( 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]) + ); + } + /* re-calculate average air temperature */ if ( !missing(allHist[yearIndex]->temp_max[day]) && @@ -804,6 +826,9 @@ void _clear_hist_weather(SW_WEATHER_HIST *yearWeather) { 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; } } @@ -968,6 +993,9 @@ void SW_WTH_new_day(void) { 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->temp_avg = w->allHist[yearIndex]->temp_avg[day]; @@ -1159,6 +1187,7 @@ void _read_weather_hist( // TimeInt mon, j, k = 0; RealF tmpmax, tmpmin, ppt; // RealF acc = 0.0; + SW_SKY *sky = &SW_Sky; char fname[MAX_FILENAMESIZE]; @@ -1208,6 +1237,10 @@ void _read_weather_hist( */ } /* end of input lines */ + interpolate_monthlyValues(sky->cloudcov, yearWeather->cloudcov_daily); + interpolate_monthlyValues(sky->windspeed, yearWeather->windspeed_daily); + interpolate_monthlyValues(sky->r_humidity, yearWeather->r_humidity_daily); + // Calculate annual average temperature based on historical input // wh->temp_year_avg = acc / (k + 0.0); From c807e8361a0db541befb99e91ed317e5e3eb13f4 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Mon, 26 Dec 2022 11:49:12 -0700 Subject: [PATCH 233/326] Modified interpolation test to use `allHist` - Due to SW_WEATHER_HIST now containing cloud cover, relative humidity, and wind speed, "TimesTest.InterpolateMonthlyValues" now uses SW_WEATHER_HIST to test `interpolate_monthlyValues()` --- tests/gtests/test_Times.cc | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/tests/gtests/test_Times.cc b/tests/gtests/test_Times.cc index d4a73ecaf..d7fa9e84d 100644 --- a/tests/gtests/test_Times.cc +++ b/tests/gtests/test_Times.cc @@ -17,6 +17,7 @@ #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{ @@ -65,6 +66,7 @@ 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]; unsigned int i, k, doy, lpadd, years[] = {1980, 1981}; // leap year, non-leap year @@ -81,16 +83,16 @@ namespace{ for (i = 0; i < length(xintpl -> cloudcov); i++) { xintpl -> cloudcov[i] = 10; } - xintpl -> cloudcov_daily[0] = 0; + xintpl_weather[0] -> cloudcov_daily[0] = 0; - interpolate_monthlyValues(xintpl -> cloudcov, xintpl -> cloudcov_daily); + interpolate_monthlyValues(xintpl->cloudcov, 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); + EXPECT_NEAR(xintpl_weather[0] -> cloudcov_daily[0], 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); + EXPECT_NEAR(xintpl_weather[0] -> cloudcov_daily[doy], 10.0, tol9); } @@ -99,7 +101,7 @@ namespace{ xintpl -> cloudcov[Mar] = 20; xintpl -> cloudcov[Dec] = 20; - interpolate_monthlyValues(xintpl -> cloudcov, xintpl -> cloudcov_daily); + interpolate_monthlyValues(xintpl -> cloudcov, xintpl_weather[0] -> cloudcov_daily); /* for (doy = 1; doy <= Time_get_lastdoy_y(years[k]); doy++) { printf( @@ -111,7 +113,7 @@ namespace{ // value for daily index 0 is unchanged because we use here a base1 index - EXPECT_NEAR(xintpl -> cloudcov_daily[0], 0, tol9); + EXPECT_NEAR(xintpl_weather[0] -> cloudcov_daily[0], 0, tol9); // Expect mid-Nov to mid-Jan and mid-Feb to mid-Apr values to vary, // all other are the same @@ -119,7 +121,7 @@ namespace{ // Expect Jan 1 to Jan 15 to vary for (doy = 1; doy <= 15; doy++) { EXPECT_NEAR( - xintpl -> cloudcov_daily[doy], + xintpl_weather[0] -> cloudcov_daily[doy], valXd(10, 20, -1, doy2mday(doy), 31), tol9 ); @@ -127,14 +129,14 @@ namespace{ // 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); + 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); EXPECT_NEAR( - xintpl -> cloudcov_daily[doy], + xintpl_weather[0] -> cloudcov_daily[doy], valXd( isMon1 ? 10 : 20, isMon1 ? 20 : 10, @@ -148,14 +150,14 @@ namespace{ // 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); + 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++) { 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 From 0ca9650bdb515cfdbc60c44542b3c45b0c496c39 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Mon, 26 Dec 2022 16:22:29 -0700 Subject: [PATCH 234/326] Fixed incorrect values in result files - Added call to `Time_new_year()` in `SW_MDL_construct()` to set yearly information for old sky monthly values to use when being interpolated in `_read_weather_hist()` - `SW_WTH_new_day()` now correctly gets daily values from the correct day, the first value in the daily arrays must be skipped --- src/SW_Model.c | 4 ++++ src/SW_Weather.c | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/SW_Model.c b/src/SW_Model.c index 01b034bf3..8a6d68dc8 100644 --- a/src/SW_Model.c +++ b/src/SW_Model.c @@ -92,6 +92,10 @@ void SW_MDL_construct(void) { SW_Model.newperiod[pd] = swFALSE; } SW_Model.newperiod[eSW_Day] = swTRUE; // every day is a new day + + // First year information needs to be initialized to interpolate + // weather data + Time_new_year(SW_Model.year); } /** diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 69179f462..e122f3733 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -993,9 +993,9 @@ void SW_WTH_new_day(void) { 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->cloudCover = w->allHist[yearIndex]->cloudcov_daily[day + 1]; + wn->windSpeed = w->allHist[yearIndex]->windspeed_daily[day + 1]; + wn->relHumidity = w->allHist[yearIndex]->r_humidity_daily[day + 1]; wn->temp_avg = w->allHist[yearIndex]->temp_avg[day]; From a03c2205e139a24c3a7f975bfab4d603a4443b98 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Wed, 28 Dec 2022 00:18:23 -0700 Subject: [PATCH 235/326] Moved `Time_new_year()` call to `_read_weather_hist()` - The previous location of `Time_new_year()` in `SW_MDL_construct()` was before "SW_Model.year" was initialized - The new location is within `_read_weather_hist()` to update day/month values to correspond to the current year being read in for use in the interpolation of monthly input values --- src/SW_Model.c | 4 ---- src/SW_Weather.c | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/SW_Model.c b/src/SW_Model.c index 8a6d68dc8..01b034bf3 100644 --- a/src/SW_Model.c +++ b/src/SW_Model.c @@ -92,10 +92,6 @@ void SW_MDL_construct(void) { SW_Model.newperiod[pd] = swFALSE; } SW_Model.newperiod[eSW_Day] = swTRUE; // every day is a new day - - // First year information needs to be initialized to interpolate - // weather data - Time_new_year(SW_Model.year); } /** diff --git a/src/SW_Weather.c b/src/SW_Weather.c index e122f3733..dc13ca518 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -1237,6 +1237,10 @@ void _read_weather_hist( */ } /* end of input lines */ + // Update yearly day/month information needed when interpolating + // cloud cover, wind speed, and relative humidity + Time_new_year(year); + interpolate_monthlyValues(sky->cloudcov, yearWeather->cloudcov_daily); interpolate_monthlyValues(sky->windspeed, yearWeather->windspeed_daily); interpolate_monthlyValues(sky->r_humidity, yearWeather->r_humidity_daily); From 45891f650ce9ec707f698363f148f69af370b925 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Wed, 28 Dec 2022 00:26:45 -0700 Subject: [PATCH 236/326] Made new `allHist` arrays base0 - Adjusted `SW_WTH_new_day()` to use current day instead of (day + 1) - Updated `interpolate_monthlyValues()` to adjust for base0 for cloud cover, wind speed, and relative humidity - Since *_daily[] values in `allHist` are defaulted to SW_MISSING, we can test to see if "dailyValues[]" is cloud cover, wind speed, or relative humidity and adjust accordingly --- include/SW_Weather.h | 2 +- include/Times.h | 1 + src/SW_Weather.c | 8 +++++--- src/Times.c | 28 ++++++++++++++++++++++------ 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/include/SW_Weather.h b/include/SW_Weather.h index f0e335d5f..a47b51574 100644 --- a/include/SW_Weather.h +++ b/include/SW_Weather.h @@ -48,7 +48,7 @@ typedef struct { 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+1], windspeed_daily[MAX_DAYS+1], r_humidity_daily[MAX_DAYS+1]; + cloudcov_daily[MAX_DAYS], windspeed_daily[MAX_DAYS], r_humidity_daily[MAX_DAYS]; // RealD temp_month_avg[MAX_MONTHS], temp_year_avg; // currently not used } SW_WEATHER_HIST; diff --git a/include/Times.h b/include/Times.h index 86cc9c876..13b5f333c 100644 --- a/include/Times.h +++ b/include/Times.h @@ -42,6 +42,7 @@ #include #include "include/generic.h" +#include "include/SW_Defines.h" #ifdef __cplusplus extern "C" { diff --git a/src/SW_Weather.c b/src/SW_Weather.c index dc13ca518..dda046cf0 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -993,9 +993,11 @@ void SW_WTH_new_day(void) { 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 + 1]; - wn->windSpeed = w->allHist[yearIndex]->windspeed_daily[day + 1]; - wn->relHumidity = w->allHist[yearIndex]->r_humidity_daily[day + 1]; + 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]; + + //swprintf("%f %d %d\n", wn->cloudCover, day, SW_Model.year); wn->temp_avg = w->allHist[yearIndex]->temp_avg[day]; diff --git a/src/Times.c b/src/Times.c index 6b0d91dbf..3193d767d 100644 --- a/src/Times.c +++ b/src/Times.c @@ -181,17 +181,33 @@ Bool isleapyear(const TimeInt year) { @param[in] monthlyValues Array with values for each month @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 Aside from cloud cover, relative humidity, and wind speed in `allHist` in SW_WEATHER, 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 When the function encounters cloud cover, relative humidity, or wind speed in `allHist` in SW_WEATHER, + `dailyValues` will be defaulted to base0 to be consistent with minimum/maximum temperature and + precipitation in `allHist`. The function will know when one of the three cases is met by detecting + SW_MISSING in dailyValues[0]. **/ void interpolate_monthlyValues(double monthlyValues[], 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 `monthlyValues` is for cloud cover, relative humidity, or + // wind speed + if(missing(dailyValues[0])) { + + // 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]; From f0fd127de290d3221a426b9312b5d553e9afd426 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Wed, 28 Dec 2022 01:14:27 -0700 Subject: [PATCH 237/326] Monthly interpolation tests in `test_Times.cc` now use base0 - Tests on lines 91 and 116 in "test_Times.cc" no longer need to test for an unchanged value in the first spot in "cloudcov_daily", and now must be an interpolated value - "doy" can no longer go up to the number of days in a year, but must be one less due to being base0 --- tests/gtests/test_Times.cc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/gtests/test_Times.cc b/tests/gtests/test_Times.cc index d7fa9e84d..e89c5cbc2 100644 --- a/tests/gtests/test_Times.cc +++ b/tests/gtests/test_Times.cc @@ -83,15 +83,15 @@ namespace{ for (i = 0; i < length(xintpl -> cloudcov); i++) { xintpl -> cloudcov[i] = 10; } - xintpl_weather[0] -> cloudcov_daily[0] = 0; + xintpl_weather[0] -> cloudcov_daily[0] = SW_MISSING; interpolate_monthlyValues(xintpl->cloudcov, 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); + // 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++) { + for (doy = 0; doy < Time_get_lastdoy_y(years[k]); doy++) { EXPECT_NEAR(xintpl_weather[0] -> cloudcov_daily[doy], 10.0, tol9); } @@ -112,8 +112,8 @@ namespace{ } */ - // value for daily index 0 is unchanged because we use here a base1 index - EXPECT_NEAR(xintpl_weather[0] -> 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., tol9); // Expect mid-Nov to mid-Jan and mid-Feb to mid-Apr values to vary, // all other are the same @@ -154,7 +154,7 @@ namespace{ } // 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_weather[0] -> cloudcov_daily[doy], From fcc17cfa3adbdf431943acd27bb59d4eb9aae30b Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 29 Dec 2022 00:04:28 -0700 Subject: [PATCH 238/326] Added base1/base0 parameter to `interpolate_monthlyValues()` - Instead of relying on the first element within "dailyValues", `interpolate_monthlyValues()` now takes in an argument specifying if "dailyValues" is to be base1 or base0 - Added new boolean variable "interpAsBase1" inside functions where `interpolate_monthlyValues()` is called - "Times.h" no longer includes "SW_Defines.h" as with this change, it is no longer needed --- include/Times.h | 4 ++-- src/SW_Sky.c | 3 ++- src/SW_VegProd.c | 17 ++++++++++------- src/SW_Weather.c | 9 ++++++--- src/Times.c | 13 +++++++------ 5 files changed, 27 insertions(+), 19 deletions(-) diff --git a/include/Times.h b/include/Times.h index 13b5f333c..ff3e1ee03 100644 --- a/include/Times.h +++ b/include/Times.h @@ -42,7 +42,6 @@ #include #include "include/generic.h" -#include "include/SW_Defines.h" #ifdef __cplusplus extern "C" { @@ -101,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/src/SW_Sky.c b/src/SW_Sky.c index b48c653bc..ad0128f29 100644 --- a/src/SW_Sky.c +++ b/src/SW_Sky.c @@ -119,12 +119,13 @@ void SW_SKY_read(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->snow_density, v->snow_density_daily); + interpolate_monthlyValues(v->snow_density, interpAsBase1, v->snow_density_daily); } } diff --git a/src/SW_VegProd.c b/src/SW_VegProd.c index a62251281..21b541c9e 100644 --- a/src/SW_VegProd.c +++ b/src/SW_VegProd.c @@ -677,7 +677,10 @@ void SW_VPD_new_year(void) { SW_VEGPROD *v = &SW_VegProd; /* convenience */ TimeInt doy; /* base1 */ - int k; + 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 */ @@ -698,8 +701,8 @@ void SW_VPD_new_year(void) { v->veg[k].co2_multipliers[BIO_INDEX][SW_Model.simyear] ); - interpolate_monthlyValues(biomass_after_CO2, v->veg[k].pct_live_daily); - interpolate_monthlyValues(v->veg[k].biomass, v->veg[k].biomass_daily); + 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., @@ -710,13 +713,13 @@ void SW_VPD_new_year(void) { v->veg[k].co2_multipliers[BIO_INDEX][SW_Model.simyear] ); - interpolate_monthlyValues(biomass_after_CO2, v->veg[k].biomass_daily); - interpolate_monthlyValues(v->veg[k].pct_live, v->veg[k].pct_live_daily); + 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, v->veg[k].litter_daily); - interpolate_monthlyValues(v->veg[k].lai_conv, v->veg[k].lai_conv_daily); + 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); } } diff --git a/src/SW_Weather.c b/src/SW_Weather.c index dda046cf0..24fb0fb20 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -1191,6 +1191,9 @@ void _read_weather_hist( // RealF acc = 0.0; SW_SKY *sky = &SW_Sky; + /* Interpolation is to be in base0 in `interpolate_monthlyValues()` */ + Bool interpAsBase1 = swFALSE; + char fname[MAX_FILENAMESIZE]; // Create file name: `[weather-file prefix].[year]` @@ -1243,9 +1246,9 @@ void _read_weather_hist( // cloud cover, wind speed, and relative humidity Time_new_year(year); - interpolate_monthlyValues(sky->cloudcov, yearWeather->cloudcov_daily); - interpolate_monthlyValues(sky->windspeed, yearWeather->windspeed_daily); - interpolate_monthlyValues(sky->r_humidity, yearWeather->r_humidity_daily); + interpolate_monthlyValues(sky->cloudcov, interpAsBase1, yearWeather->cloudcov_daily); + interpolate_monthlyValues(sky->windspeed, interpAsBase1, yearWeather->windspeed_daily); + interpolate_monthlyValues(sky->r_humidity, interpAsBase1, yearWeather->r_humidity_daily); // Calculate annual average temperature based on historical input // wh->temp_year_avg = acc / (k + 0.0); diff --git a/src/Times.c b/src/Times.c index 3193d767d..5a1acfa5f 100644 --- a/src/Times.c +++ b/src/Times.c @@ -179,6 +179,7 @@ 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 Aside from cloud cover, relative humidity, and wind speed in `allHist` in SW_WEATHER, dailyValues[0] @@ -187,17 +188,17 @@ Bool isleapyear(const TimeInt year) { @note When the function encounters cloud cover, relative humidity, or wind speed in `allHist` in SW_WEATHER, `dailyValues` will be defaulted to base0 to be consistent with minimum/maximum temperature and - precipitation in `allHist`. The function will know when one of the three cases is met by detecting - SW_MISSING in dailyValues[0]. + precipitation in `allHist`. The function will know when one of the three cases is met by the parameter + "interpAsBase1". **/ -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.; - // Check if `monthlyValues` is for cloud cover, relative humidity, or - // wind speed - if(missing(dailyValues[0])) { + // Check if we are interpolating values as base1 + if(!interpAsBase1) { // Make `dailyValues` base0 startdoy = 0; From 6fc80eb1e5dea71d7ecbe3905728f4ac2f3b1204 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 29 Dec 2022 00:07:43 -0700 Subject: [PATCH 239/326] Updated `interpolate_monthlyValues()` tests - `interpolate_monthlyValues()` tests now use "interpAsBase1" - Adjusted tests more to work with base0 (e.g., doy2mday(doy) to doy2mday(doy + 1)) --- tests/gtests/test_Times.cc | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/tests/gtests/test_Times.cc b/tests/gtests/test_Times.cc index e89c5cbc2..ee80ad4a6 100644 --- a/tests/gtests/test_Times.cc +++ b/tests/gtests/test_Times.cc @@ -67,6 +67,7 @@ namespace{ 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 @@ -85,7 +86,8 @@ namespace{ } xintpl_weather[0] -> cloudcov_daily[0] = SW_MISSING; - interpolate_monthlyValues(xintpl->cloudcov, xintpl_weather[0] -> cloudcov_daily); + interpolate_monthlyValues(xintpl->cloudcov, interpAsBase1, + xintpl_weather[0] -> cloudcov_daily); // 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); @@ -101,7 +103,8 @@ namespace{ xintpl -> cloudcov[Mar] = 20; xintpl -> cloudcov[Dec] = 20; - interpolate_monthlyValues(xintpl -> cloudcov, xintpl_weather[0] -> cloudcov_daily); + interpolate_monthlyValues(xintpl -> cloudcov, interpAsBase1, + xintpl_weather[0] -> cloudcov_daily); /* for (doy = 1; doy <= Time_get_lastdoy_y(years[k]); doy++) { printf( @@ -112,44 +115,44 @@ namespace{ } */ - // value for daily index 0 is 10 to make sure base0 is working correctly - EXPECT_NEAR(xintpl_weather[0] -> cloudcov_daily[0], 10., 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_weather[0] -> cloudcov_daily[doy], - valXd(10, 20, -1, doy2mday(doy), 31), + 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++) { + 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_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++) { + for (doy = 104 + lpadd; doy < 319 + lpadd; doy++) { EXPECT_NEAR(xintpl_weather[0] -> cloudcov_daily[doy], 10.0, tol9); } @@ -162,11 +165,11 @@ namespace{ 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); } From 0c719aad94eb924774b558e09d8d79a063d563d6 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 29 Dec 2022 13:03:12 -0700 Subject: [PATCH 240/326] Added new flags/scaling in `weathsetup.in` - Added two new sections for flags within "weathsetup.in": - Three flags for the user to specify if they would like to use monthly values instead of daily input - Fourteen flags for the user to specify if they are using daily input for a specific variable - Note: if both a monthly and daily flag are set to 1 for the same variable, the monthly flag will take priority - Monthly flags default to 1 and daily flags aside from minimum/maximum temperature and precipitation, default to 0 - Added "actual vapor pressure" and "shortwave radiation" to scaling factors to be consistent with the rest of the new input variables - Both scaling factors default to 1.0 --- tests/example/Input/weathsetup.in | 56 ++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/tests/example/Input/weathsetup.in b/tests/example/Input/weathsetup.in index c463e31eb..2be7af12f 100755 --- a/tests/example/Input/weathsetup.in +++ b/tests/example/Input/weathsetup.in @@ -15,6 +15,34 @@ 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 westward 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 [MJ/day] + #--- Monthly scaling parameters: # Month 1 = January, Month 2 = February, etc. @@ -24,16 +52,18 @@ # 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 +# 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 From 3bf6dadb08aba6059a1dd11861f42b4510a5b7e4 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 29 Dec 2022 13:17:46 -0700 Subject: [PATCH 241/326] Edited `solar_radiation()` and weather structs interfaces SW_WEATHER: - Added variables to hold input flags for each possible input variable - Added variables to hold indices where each input variable may reside when reading in weath.YYYY - Added "n_input_forcings" to keep track of how many daily input columns exist within weath.YYYY SW_WEATHER_HIST/SW_WEATHER_NOW: - Added two new variables: shortwave radiation and actual vapor pressure `solar_radiation()`: - Removed "rel_humidity" and "air_temp_mean" as parameters - Added "e_a" as a parameter and removed "e_a" calculation to be moved --- include/SW_Flow_lib_PET.h | 2 +- include/SW_Weather.h | 16 ++++++++++++++-- src/SW_Flow.c | 3 +-- src/SW_Flow_lib_PET.c | 14 +++----------- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/include/SW_Flow_lib_PET.h b/include/SW_Flow_lib_PET.h index e605d3541..563f01c39 100644 --- a/include/SW_Flow_lib_PET.h +++ b/include/SW_Flow_lib_PET.h @@ -38,7 +38,7 @@ double clearnessindex_diffuse(double K_b); 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 albedo, double cloud_cover, double e_a, double *H_oh, double *H_ot, double *H_gh); diff --git a/include/SW_Weather.h b/include/SW_Weather.h index a47b51574..2076cec00 100644 --- a/include/SW_Weather.h +++ b/include/SW_Weather.h @@ -42,13 +42,15 @@ extern "C" { typedef struct { /* Weather values of the current simulation day */ - RealD temp_avg, temp_max, temp_min, ppt, rain, cloudCover, windSpeed, relHumidity; + 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]; + 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; @@ -160,6 +162,16 @@ typedef struct { RealD snowRunoff, surfaceRunoff, surfaceRunon, soil_inf, surfaceAvg; RealD snow, snowmelt, snowloss, surfaceMax, surfaceMin; + Bool use_cloudCoverMonthly, use_windSpeedMonthly, use_relHumidityMonthly, + has_temp2, has_ppt, has_cloudCover, has_sfcWind, has_windComp, has_hurs, + has_hurs2, has_huss, has_tdps, has_vp, has_rsds; + + int tempComp1_index, tempComp2_index, ppt_index, cloudCover_index, sfcWind_index, + windComp1_index, windComp2_index, hurs_index, hurs_comp1_index, hurs_comp2_index, + huss_index, tdps_index, vp_index, rsds_index; + + Bool n_input_forcings; // Number of input columns found in weath.YYYY + /* This section is required for computing the output quantities. */ SW_WEATHER_OUTPUTS *p_accu[SW_OUTNPERIODS], // output accumulator: summed values for each time period diff --git a/src/SW_Flow.c b/src/SW_Flow.c index 62df35505..d54e06abd 100644 --- a/src/SW_Flow.c +++ b/src/SW_Flow.c @@ -406,8 +406,7 @@ void SW_Water_Flow(void) { SW_Site.slope, SW_Site.aspect, x, - w->now.cloudCover, - w->now.relHumidity, + w->now.actualVaporPressure, w->now.temp_avg, &sw->H_oh, &sw->H_ot, diff --git a/src/SW_Flow_lib_PET.c b/src/SW_Flow_lib_PET.c index aaa8d900c..d3dbe78ec 100644 --- a/src/SW_Flow_lib_PET.c +++ b/src/SW_Flow_lib_PET.c @@ -784,8 +784,7 @@ double clearnessindex_diffuse(double K_b) @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] e_a Actual vapor pressure [kPa] @param[out] H_oh Daily extraterrestrial horizontal irradiation [MJ / m2] @param[out] H_ot Daily extraterrestrial tilted irradiation [MJ / m2] @@ -794,11 +793,11 @@ double clearnessindex_diffuse(double K_b) */ 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 albedo, double cloud_cover, double e_a, 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, @@ -827,13 +826,6 @@ 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)); - - // Atmospheric attenuation: additional cloud effects //k_c = overcast_attenuation_KastenCzeplak1980(cloud_cover / 100.); From ac415536bf59e4b146e975d7b200ed6c4b4f8333 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 29 Dec 2022 13:22:44 -0700 Subject: [PATCH 242/326] Added actual vapor functions to SW_Weather.c - Added three new functions to calculate "actual vapor pressure": `actualVaporPressure1/2/3()` - Functions currently do not return anything useful --- include/SW_Weather.h | 3 +++ src/SW_Weather.c | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/include/SW_Weather.h b/include/SW_Weather.h index 2076cec00..eba46d4ef 100644 --- a/include/SW_Weather.h +++ b/include/SW_Weather.h @@ -247,6 +247,9 @@ void generateMissingWeather( unsigned int method, unsigned int optLOCF_nMax ); +double actualVaporPressure1(double hurs, double tmean); +double actualVaporPressure2(double hursMax, double hursMin, double maxTemp, double minTemp); +double actualVaporPressure3(double tdps); void allocateAllWeather(SW_WEATHER *w); void deallocateAllWeather(SW_WEATHER *w); void _clear_hist_weather(SW_WEATHER_HIST *yearWeather); diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 24fb0fb20..f2e579f89 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -1273,6 +1273,43 @@ void _read_weather_hist( fclose(f); } +/** + @brief Calculate actual vapor pressure based on relative humidity and mean temperature + + @param hurs Daily mean relative humidity [%] + @param tmean Daily mean air temperature [C] + + @return Calculated actual vapor pressure [kPa] + */ +double actualVaporPressure1(double hurs, double tmean) { + return 0.0; // Temporary return +} + +/** + @brief Calculate actual vapor pressure based on temperature and relative humidity components (min/max) + + @param hursMax Daily maximum relative humidity [%] + @param hursMin 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 hursMax, double hursMin, double maxTemp, double minTemp) { + return 0.0; // Temporary return +} + +/** + @brief Calculate actual vapor pressure based on dew point temperature + + @param tdps 2m dew point temperature [C] + + @return Calculated actual vapor pressure [kPa] + */ +double actualVaporPressure3(double tdps) { + return 0.0; // Temporary return +} + void allocateClimateStructs(int numYears, SW_CLIMATE_YEARLY *climateOutput, SW_CLIMATE_CLIM *climateAverages) { From 52b64258a183755dc15505f50b8c9d9d06b9b0ff Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 29 Dec 2022 13:23:25 -0700 Subject: [PATCH 243/326] New constant in SW_Defines.h: MAX_INPUT_COLUMNS --- include/SW_Defines.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/SW_Defines.h b/include/SW_Defines.h index 6c13cce8c..250b9f0b7 100644 --- a/include/SW_Defines.h +++ b/include/SW_Defines.h @@ -59,6 +59,8 @@ extern "C" { #define SW_MISSING 999. /**< Value to use as MISSING */ +#define MAX_INPUT_COLUMNS 14 /**< Maximum number of columns that can be input in a weath.YYYY file*/ + // Euler's constant #ifdef M_E From 78bc6bb22675304fce56d4d27fb4d853fb57a1d1 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 29 Dec 2022 13:36:16 -0700 Subject: [PATCH 244/326] New `actVapPress` & `shortWaveRad` scaling arrays in SW_WEATHER --- include/SW_Weather.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/SW_Weather.h b/include/SW_Weather.h index eba46d4ef..177f183c9 100644 --- a/include/SW_Weather.h +++ b/include/SW_Weather.h @@ -157,7 +157,9 @@ typedef struct { scale_temp_min[MAX_MONTHS], scale_skyCover[MAX_MONTHS], scale_wind[MAX_MONTHS], - scale_rH[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; From 84c29cc65008719b63af7f7de176a587c8386f1e Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 30 Dec 2022 12:02:35 -0700 Subject: [PATCH 245/326] New function `checkAllWeather()`: pseudocode - Create a new function skeleton with pseudocode, `checkAllWeather()` - This function will through all weather of all years and days in the simulation and checks if they are reasonable values --- include/SW_Weather.h | 1 + src/SW_Weather.c | 51 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/include/SW_Weather.h b/include/SW_Weather.h index 177f183c9..3a9ada16f 100644 --- a/include/SW_Weather.h +++ b/include/SW_Weather.h @@ -252,6 +252,7 @@ void generateMissingWeather( double actualVaporPressure1(double hurs, double tmean); double actualVaporPressure2(double hursMax, double hursMin, double maxTemp, double minTemp); double actualVaporPressure3(double tdps); +void checkAllWeather(SW_WEATHER *weather); void allocateAllWeather(SW_WEATHER *w); void deallocateAllWeather(SW_WEATHER *w); void _clear_hist_weather(SW_WEATHER_HIST *yearWeather); diff --git a/src/SW_Weather.c b/src/SW_Weather.c index f2e579f89..3a1eda09e 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -812,6 +812,57 @@ void generateMissingWeather( } } +/** + @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) { + + // Check if minimum or maximum temperature, or precipitation flags are 0 + + // Fail + + // Loop through `allHist` years + + // Loop through `allHist` days + + // Check if minimum temp greater than or equal to maximum temp + + // Fail + + // Otherwise, check if maximum or minimum temp, or + // dew point temp is not [-100, 100] + + // Fail + + // Otherwise, check if precipitation is less than 0cm + + // Fail + + // Otherwise, check if relative humidity is less than 0% or greater than 100% + + // Fail + + // Otherwise, check if cloud cover was input and + // if the value is less than 0% or greater than 100% + + // Fail + + // Otherwise, check if wind speed is less than 0 m/s + + // Fail + + // Otherwise, check if radiation if less than 0 W * m^-2 + + // Fail + + // Otherwise, check if actual vapor pressure is less than kPa + + // Fail +} + /** @brief Clears weather history. From 391be2007f4ae9ec71e0ae3db28b9084cb3b3706 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 30 Dec 2022 14:20:16 -0700 Subject: [PATCH 246/326] Fixed `n_input_forcings` type in SW_WEATHER --- include/SW_Weather.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/SW_Weather.h b/include/SW_Weather.h index 3a9ada16f..56e5ebfd9 100644 --- a/include/SW_Weather.h +++ b/include/SW_Weather.h @@ -172,7 +172,7 @@ typedef struct { windComp1_index, windComp2_index, hurs_index, hurs_comp1_index, hurs_comp2_index, huss_index, tdps_index, vp_index, rsds_index; - Bool n_input_forcings; // Number of input columns found in weath.YYYY + int n_input_forcings; // Number of input columns found in weath.YYYY /* This section is required for computing the output quantities. */ SW_WEATHER_OUTPUTS From 7e17b3a2e83712ffc3851978c54d9583666087df Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 30 Dec 2022 21:55:34 -0700 Subject: [PATCH 247/326] Updated equations in `actualVaporPressure2/3()` - Updated equations in `actualVaporPressure2()` and `actualVaporPressure3()` along with the function documentation to mention them --- src/SW_Weather.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 3a1eda09e..dd8256afe 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -1327,18 +1327,25 @@ void _read_weather_hist( /** @brief Calculate actual vapor pressure based on relative humidity and mean temperature + Calculation is based on Allen et al. 2006 @cite allen2006AaFM which is + based on Majumdar et al. 1972 and updated by ASCE-EWRI 2005. + @param hurs Daily mean relative humidity [%] @param tmean Daily mean air temperature [C] @return Calculated actual vapor pressure [kPa] */ double actualVaporPressure1(double hurs, double tmean) { - return 0.0; // Temporary return + // Allen et al. 2005 eqs 7 and 14 + return (hurs / 100.) * 0.6108 * exp((17.27 * tmean) / (tmean + 237.3)); } /** @brief Calculate actual vapor pressure based on temperature and relative humidity components (min/max) + Calculation is based on Allen et al. 2006 @cite allen2006AaFM which is + based on Majumdar et al. 1972 and updated by ASCE-EWRI 2005. + @param hursMax Daily maximum relative humidity [%] @param hursMin Daily minimum relative humidity [%] @param maxTemp Daily minimum air temperature [C] @@ -1347,18 +1354,29 @@ double actualVaporPressure1(double hurs, double tmean) { @return Calculated actual vapor pressure [kPa] */ double actualVaporPressure2(double hursMax, double hursMin, double maxTemp, double minTemp) { - return 0.0; // Temporary return + // Allen et al. 2005 eqs 7 and 11 + double satVapPressureMax = .6108 * exp((17.27 * maxTemp) / maxTemp + 237.3); + double satVapPressureMin = .6108 * exp((17.27 * minTemp) / minTemp + 237.3); + + double relHumVapPressMax = satVapPressureMin * (hursMax / 100); + double relHumVapPressMin = satVapPressureMax * (hursMin / 100); + + return (relHumVapPressMax + relHumVapPressMin) / 2; } /** @brief Calculate actual vapor pressure based on dew point temperature + Calculation is based on Allen et al. 2006 @cite allen2006AaFM which is + based on Majumdar et al. 1972 and updated by ASCE-EWRI 2005. + @param tdps 2m dew point temperature [C] @return Calculated actual vapor pressure [kPa] */ double actualVaporPressure3(double tdps) { - return 0.0; // Temporary return + // Allen et al. 2005 eqs 7 and 8 + return .6108 * exp((17.27 * tdps) / (tdps + 237.3)); } void allocateClimateStructs(int numYears, SW_CLIMATE_YEARLY *climateOutput, From 1de83119aec3b050132a503b8f8cd28b5f68eed1 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 30 Dec 2022 22:05:54 -0700 Subject: [PATCH 248/326] `SW_WTH_setup()` now handles new user flags - Created two arrays to hold input flags and index variables for SW_WEATHER for index calculation - Expanded switch statement to read in the new lines where the flags reside - Expanded `sscanf()` call to read actual vapor pressure and shortwave radiation scaling factors - Added five ternary operators to make sure if both monthly and daily flags are set to 1, the monthly flag takes priority - End of function calculates which index of "weathInput[]" in `_read_weather_hist()` will hold which variables in weath.YYYY --- src/SW_Weather.c | 170 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 164 insertions(+), 6 deletions(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index dd8256afe..a9b6c9848 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -1070,11 +1070,22 @@ void SW_WTH_new_day(void) { void SW_WTH_setup(void) { /* =================================================== */ SW_WEATHER *w = &SW_Weather; - const int nitems = 17; + const int nitems = 34; FILE *f; - int lineno = 0, month, x; + int lineno = 0, month, x, columnNum, varArrIndex = 0; + int tempCompIndex = 3, windCompIndex = 7, hursCompIndex = 9; + Bool componentFlag; RealF sppt, stmax, stmin; - RealF sky, wind, rH; + RealF sky, wind, rH, actVP, shortWaveRad; + + Bool *inputFlags[MAX_INPUT_COLUMNS] = {&w->use_cloudCoverMonthly, &w->use_windSpeedMonthly, + &w->use_relHumidityMonthly, &w->has_temp2, &w->has_ppt, &w->has_cloudCover, &w->has_sfcWind, + &w->has_windComp, &w->has_hurs, &w->has_hurs2, &w->has_huss, &w->has_tdps, &w->has_vp, &w->has_rsds}; + + int *variableIndices[MAX_INPUT_COLUMNS] = {&w->tempComp1_index, &w->tempComp2_index, + &w->ppt_index, &w->cloudCover_index, &w->sfcWind_index, &w->windComp1_index, &w->windComp2_index, + &w->hurs_index, &w->hurs_comp1_index, &w->hurs_comp2_index, &w->huss_index, &w->tdps_index, + &w->vp_index, &w->rsds_index}; MyFileName = SW_F_name(eWeather); f = OpenFile(MyFileName, "r"); @@ -1133,17 +1144,91 @@ void SW_WTH_setup(void) { w->rng_seed = atoi(inbuf); break; + case 5: + w->use_cloudCoverMonthly = atoi(inbuf); + break; + + case 6: + w->use_windSpeedMonthly = atoi(inbuf); + break; + + case 7: + w->use_relHumidityMonthly = atoi(inbuf); + break; + + case 8: + w->has_temp2 = itob(atoi(inbuf)); + break; + + case 9: + if(w->has_temp2) { + w->has_temp2 = itob(atoi(inbuf)); + } + break; + + case 10: + w->has_ppt = itob(atoi(inbuf)); + break; + + case 11: + w->has_cloudCover = itob(atoi(inbuf)); + break; + + case 12: + w->has_sfcWind = itob(atoi(inbuf)); + break; + + case 13: + w->has_windComp = itob(atoi(inbuf)); + break; + + case 14: + if(w->has_windComp) { + w->has_windComp = itob(atoi(inbuf)); + } + break; + + case 15: + w->has_hurs = itob(atoi(inbuf)); + break; + + case 16: + w->has_hurs2 = itob(atoi(inbuf)); + break; + + case 17: + if(w->has_hurs2) { + w->has_hurs2 = itob(atoi(inbuf)); + } + break; + + case 18: + w->has_huss = itob(atoi(inbuf)); + break; + + case 19: + w->has_tdps = itob(atoi(inbuf)); + break; + + case 20: + w->has_vp = itob(atoi(inbuf)); + break; + + case 21: + w->has_rsds = itob(atoi(inbuf)); + break; + default: if (lineno == 5 + MAX_MONTHS) break; x = sscanf( inbuf, - "%d %f %f %f %f %f %f", - &month, &sppt, &stmax, &stmin, &sky, &wind, &rH + "%d %f %f %f %f %f %f %f %f", + &month, &sppt, &stmax, &stmin, &sky, &wind, &rH, &actVP, &shortWaveRad ); - if (x != 7) { + if (x != 9) { CloseFile(&f); LogError(logfp, LOGFATAL, "%s : Bad record %d.", MyFileName, lineno); } @@ -1155,17 +1240,90 @@ void SW_WTH_setup(void) { 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++; } + // Check if monthly flags have been chosen to override daily flags + w->has_sfcWind = (w->use_windSpeedMonthly) ? swFALSE : w->has_sfcWind; + w->has_windComp = (w->use_windSpeedMonthly) ? swFALSE : w->has_windComp; + + w->has_hurs = (w->use_relHumidityMonthly) ? swFALSE : w->has_hurs; + w->has_hurs2 = (w->use_relHumidityMonthly) ? swFALSE : w->has_hurs2; + + w->has_cloudCover = (w->use_cloudCoverMonthly) ? swFALSE : w->has_cloudCover; + SW_WeatherPrefix(w->name_prefix); CloseFile(&f); if (lineno < nitems) { LogError(logfp, LOGFATAL, "%s : Too few input lines.", MyFileName); } + + // Calculate value indices for `allHist` + + // Default n_input_forcings to 0 + w->n_input_forcings = 0; + + // Loop through MAX_INPUT_COLUMNS + for(columnNum = 3; columnNum < MAX_INPUT_COLUMNS; columnNum++) + { + variableIndices[varArrIndex] = 0; + + // Check if current flag is in relation to component variables + if(&inputFlags[columnNum] == &inputFlags[tempCompIndex] || + &inputFlags[columnNum] == &inputFlags[windCompIndex] || + &inputFlags[columnNum] == &inputFlags[hursCompIndex]) { + + // Set component flag + componentFlag = swTRUE; + variableIndices[varArrIndex + 1] = 0; + } else { + componentFlag = swFALSE; + } + + // Check if current flag is set + if(*inputFlags[columnNum]) { + + // Set current index to "n_input_forcings" + variableIndices[varArrIndex] = w->n_input_forcings; + + // Check if flag is meant for components variables + if(componentFlag) { + + // Set next index to n_input_focings + 1 + variableIndices[varArrIndex + 1] = w->n_input_forcings + 1; + + // Increment "varArrIndex" by two + varArrIndex += 2; + + // Increment "n_input_forcings" by two + w->n_input_forcings += 2; + } else { + // Otherwise, current flag is not meant for components + + // Increment "varArrIndex" by one + varArrIndex++; + + // Increment "n_input_forcings" by one + w->n_input_forcings++; + } + } else { + // Otherwise, flag was not set, deal with "varArrayIndex" + + // Check if current flag is for component variables + if(componentFlag) { + // Increment "varArrIndex" by two + varArrIndex += 2; + } else { + // Increment "varArrIndex" by one + varArrIndex++; + } + } + } } From 7f9da8c6e10bb54e9640721def9f27352bd74b82 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 30 Dec 2022 22:13:30 -0700 Subject: [PATCH 249/326] `_read_weather_hist()` now reads all new input variables - Expanded `sscanf()` call to take in 15 variables (day of the year, min/max temperature, precipitation, and the new input variables) - Added calculation conditionals for wind speed, relative humidity, actual vapor pressure - Put `interpolate_monthlyValues()` calls in conditionals so they are only called when monthly values are desired to be used --- src/SW_Weather.c | 120 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 107 insertions(+), 13 deletions(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index a9b6c9848..3ed8810dd 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -1394,11 +1394,22 @@ void _read_weather_hist( */ FILE *f; + SW_SKY *sky = &SW_Sky; + SW_WEATHER *weath = &SW_Weather; int x, lineno = 0, doy; // TimeInt mon, j, k = 0; - RealF tmpmax, tmpmin, ppt; // RealF acc = 0.0; - SW_SKY *sky = &SW_Sky; + RealF weathInput[MAX_INPUT_COLUMNS + 2]; + + int minTempIndex = weath->tempComp1_index, maxTempIndex = weath->tempComp2_index, + precipIndex = weath->ppt_index, cloudcovIndex = weath->cloudCover_index, + windSpeedIndex = weath->sfcWind_index, windComp1Index = weath->windComp1_index, + windComp2Index = weath->windComp2_index, relHumIndex = weath->huss_index, + vaporPressIndex = weath->vp_index, hursComp1Index = weath->hurs_comp1_index, + hursComp2Index = weath->hurs_comp2_index, hussIndex = weath->huss_index, + dewPointIndex = weath->tdps_index; + + double es, e, relHum, averageTemp = 0.; /* Interpolation is to be in base0 in `interpolate_monthlyValues()` */ Bool interpAsBase1 = swFALSE; @@ -1413,12 +1424,17 @@ void _read_weather_hist( while (GetALine(f, inbuf)) { lineno++; - x = sscanf(inbuf, "%d %f %f %f", &doy, &tmpmax, &tmpmin, &ppt); - if (x < 4) { + x = sscanf(inbuf, "%d %f %f %f %f %f %f %f %f %f %f %f %f %f %f", + &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 != weath->n_input_forcings + 1) { CloseFile(&f); LogError(logfp, LOGFATAL, "%s : Incomplete record %d (doy=%d).", fname, lineno, doy); } - if (x > 4) { + if (x > 15) { CloseFile(&f); LogError(logfp, LOGFATAL, "%s : Too many values in record %d (doy=%d).", fname, lineno, doy); } @@ -1429,13 +1445,82 @@ void _read_weather_hist( /* --- Make the assignments ---- */ doy--; // base1 -> base0 - yearWeather->temp_max[doy] = tmpmax; - yearWeather->temp_min[doy] = tmpmin; - yearWeather->ppt[doy] = ppt; + yearWeather->temp_max[doy] = weathInput[maxTempIndex]; + yearWeather->temp_min[doy] = weathInput[minTempIndex]; + yearWeather->ppt[doy] = weathInput[precipIndex]; // Calculate average air temperature if min/max not missing - if (!missing(tmpmax) && !missing(tmpmin)) { - yearWeather->temp_avg[doy] = (tmpmax + tmpmin) / 2.0; + if (!missing(weathInput[maxTempIndex]) && !missing(weathInput[minTempIndex])) { + + yearWeather->temp_avg[doy] = (weathInput[maxTempIndex] + + weathInput[minTempIndex]) / 2.0; + + averageTemp = yearWeather->temp_avg[doy]; + } + + if(!weath->use_cloudCoverMonthly && weath->has_cloudCover) { + yearWeather->cloudcov_daily[doy] = weathInput[cloudcovIndex]; + } + + if(!weath->use_windSpeedMonthly) { + if(weath->has_sfcWind) { + yearWeather->windspeed_daily[doy] = weathInput[windSpeedIndex]; + + } else if(weath->has_windComp) { + yearWeather->windspeed_daily[doy] = sqrt(squared(weathInput[windComp1Index]) + + squared(weathInput[windComp2Index])); + + } + } + + if(!weath->use_relHumidityMonthly) { + if(weath->has_hurs) { + yearWeather->r_humidity_daily[doy] = weathInput[relHumIndex]; + + } else if(weath->has_vp) { + yearWeather->r_humidity_daily[doy] = weathInput[relHumIndex]; // Temporary + + } else if(weath->has_hurs2) { + yearWeather->r_humidity_daily[doy] = (weathInput[hursComp1Index] + + weathInput[hursComp2Index]) / 2; + + } else if(weath->has_huss) { + es = (6.112 * exp(17.67 * averageTemp)) / (averageTemp + 243.5); + + e = (weathInput[hussIndex] * 1013.25) / + (.378 * weathInput[hussIndex] + .622); + + relHum = e / es; + relHum = max(0., relHum); + + yearWeather->r_humidity_daily[doy] = min(1., relHum); + + } + } + + if(!weath->has_vp) { + if(weath->has_tdps) { + yearWeather->actualVaporPressure[doy] = + actualVaporPressure3(weathInput[dewPointIndex]); + + } else if(!weath->use_relHumidityMonthly && !weath->has_hurs) { + + yearWeather->actualVaporPressure[doy] = weathInput[dewPointIndex]; // Temporary + } else if(weath->has_hurs2 && weath->has_temp2) { + yearWeather->actualVaporPressure[doy] = + actualVaporPressure2(weathInput[hursComp2Index], + weathInput[hursComp1Index], + weathInput[maxTempIndex], + weathInput[minTempIndex]); + + } else { + yearWeather->actualVaporPressure[doy] = + actualVaporPressure1(weathInput[relHumIndex], + averageTemp); + + } + } else { + yearWeather->actualVaporPressure[doy] = weathInput[vaporPressIndex]; } @@ -1455,9 +1540,18 @@ void _read_weather_hist( // cloud cover, wind speed, and relative humidity Time_new_year(year); - interpolate_monthlyValues(sky->cloudcov, interpAsBase1, yearWeather->cloudcov_daily); - interpolate_monthlyValues(sky->windspeed, interpAsBase1, yearWeather->windspeed_daily); - interpolate_monthlyValues(sky->r_humidity, interpAsBase1, yearWeather->r_humidity_daily); + if(weath->use_cloudCoverMonthly) { + interpolate_monthlyValues(sky->cloudcov, interpAsBase1, yearWeather->cloudcov_daily); + } + + if(weath->use_windSpeedMonthly) { + interpolate_monthlyValues(sky->windspeed, interpAsBase1, yearWeather->windspeed_daily); + } + + if(weath->use_relHumidityMonthly) { + interpolate_monthlyValues(sky->r_humidity, interpAsBase1, yearWeather->r_humidity_daily); + } + // Calculate annual average temperature based on historical input // wh->temp_year_avg = acc / (k + 0.0); From 5b915fa464b28cdd236017247af91060d23dcf8d Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 30 Dec 2022 22:17:29 -0700 Subject: [PATCH 250/326] Filled `checkAllWeather()` with code - `checkAllWeather()` now loops through all years and days in the simulation and tests: - relative humidity, cloud cover, actual vapor pressure, temperature max/min, precipitation, shortwave radiation, and wind speed - `checkAllWeather()` also checks if the flags for maximum/minimum temperature and precipitation are set and fail if they are not - Added call to `finalizeAllWeather()` after scaling occurs --- src/SW_Weather.c | 75 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 65 insertions(+), 10 deletions(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 3ed8810dd..54709f8b0 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -562,6 +562,10 @@ void finalizeAllWeather(SW_WEATHER *w) { w->scale_wind, w->scale_rH ); + + // Make sure all input, scaled, generated, and calculated daily weather values + // are within reason + checkAllWeather(w); } @@ -820,47 +824,98 @@ void generateMissingWeather( */ void checkAllWeather(SW_WEATHER *weather) { - // Check if minimum or maximum temperature, or precipitation flags are 0 + // Initialize any variables + TimeInt year, doy, numDaysInYear; + SW_WEATHER_HIST **weathHist = weather->allHist; + + double dailyMinTemp, dailyMaxTemp; + // Check if minimum or maximum temperature, or precipitation flags are 0 + if(!weather->has_temp2 || !weather->has_ppt) { // Fail + LogError(logfp, LOGFATAL, "Temperature and/or precipitation flag(s) are unset."); + } // Loop through `allHist` years + for(year = 0; year < weather->n_years; year++) { + numDaysInYear = Time_get_lastdoy_y(year); // Loop through `allHist` days + for(doy = 0; doy < numDaysInYear; doy++) { - // Check if minimum temp greater than or equal to maximum temp + dailyMaxTemp = weathHist[year]->temp_max[doy]; + dailyMinTemp = weathHist[year]->temp_min[doy]; - // Fail + // Check if minimum temp greater than maximum temp + if(dailyMinTemp > dailyMaxTemp) { + // Fail + LogError(logfp, LOGFATAL, "Daily input value for minumum 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((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(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(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(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(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(weathHist[year]->shortWaveRad[doy] < 0.) { // Fail - - // Otherwise, check if actual vapor pressure is less than kPa + 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(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); + } + } + } } From 2c1396995215a6b6c2b956349a73cb0d5f5632d6 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 30 Dec 2022 22:27:33 -0700 Subject: [PATCH 251/326] Two new variable scalings to `scaleAllWeather()` - `scaleAllWeather()` now scales actual vapor pressure and shortwave radiation using the new scaling columns in "weathsetup.in" - Updated function header and documentation to include actual vapor pressure and shortwave radiation scaling parameters --- include/SW_Weather.h | 4 +++- src/SW_Weather.c | 26 +++++++++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/include/SW_Weather.h b/include/SW_Weather.h index 56e5ebfd9..d5534ce5c 100644 --- a/include/SW_Weather.h +++ b/include/SW_Weather.h @@ -240,7 +240,9 @@ void scaleAllWeather( double *scale_precip, double *scale_skyCover, double *scale_wind, - double *scale_rH + double *scale_rH, + double *scale_actVapPress, + double *scale_shortWaveRad ); void generateMissingWeather( SW_WEATHER_HIST **allHist, diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 54709f8b0..f19abd19b 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -560,7 +560,9 @@ void finalizeAllWeather(SW_WEATHER *w) { w->scale_precip, w->scale_skyCover, w->scale_wind, - w->scale_rH + w->scale_rH, + w->scale_actVapPress, + w->scale_shortWaveRad ); // Make sure all input, scaled, generated, and calculated daily weather values @@ -590,6 +592,8 @@ void SW_WTH_finalize_all_weather(void) { @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. @@ -605,7 +609,9 @@ void scaleAllWeather( double *scale_precip, double *scale_skyCover, double *scale_wind, - double *scale_rH + double *scale_rH, + double *scale_actVapPress, + double *scale_shortWaveRad ) { int year, month; @@ -665,6 +671,20 @@ void scaleAllWeather( ); } + 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]) && @@ -1524,7 +1544,7 @@ void _read_weather_hist( } else if(weath->has_windComp) { yearWeather->windspeed_daily[doy] = sqrt(squared(weathInput[windComp1Index]) + squared(weathInput[windComp2Index])); - + } } From 0765fe81fe7b2ee24093a4fc39722fb0ea32d02f Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Mon, 2 Jan 2023 14:07:10 -0700 Subject: [PATCH 252/326] Correct parameter placements in call to `solar_radiation()` --- src/SW_Flow.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SW_Flow.c b/src/SW_Flow.c index d54e06abd..3601cac27 100644 --- a/src/SW_Flow.c +++ b/src/SW_Flow.c @@ -406,8 +406,8 @@ void SW_Water_Flow(void) { SW_Site.slope, SW_Site.aspect, x, + w->now.cloudCover, w->now.actualVaporPressure, - w->now.temp_avg, &sw->H_oh, &sw->H_ot, &sw->H_gh From 97481f1d58012c7a4dcd2dfa6b9ffd2e28c5b4db Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Mon, 2 Jan 2023 14:17:49 -0700 Subject: [PATCH 253/326] Fixed index calculation for SW_WEATHER - Abandoned idea to set the value of SW_WEATHER flags through an array - Seemingly correct method of doing so would result in "Illegal Instruction: 4" on local machine, almost immediately and permanently eliminating the idea - `SW_WTH_setup()` now individually sets variable indices - Renamed `variableIndices` to `varIndices` to compact the name --- src/SW_Weather.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index f19abd19b..b27f19acb 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -1157,10 +1157,7 @@ void SW_WTH_setup(void) { &w->use_relHumidityMonthly, &w->has_temp2, &w->has_ppt, &w->has_cloudCover, &w->has_sfcWind, &w->has_windComp, &w->has_hurs, &w->has_hurs2, &w->has_huss, &w->has_tdps, &w->has_vp, &w->has_rsds}; - int *variableIndices[MAX_INPUT_COLUMNS] = {&w->tempComp1_index, &w->tempComp2_index, - &w->ppt_index, &w->cloudCover_index, &w->sfcWind_index, &w->windComp1_index, &w->windComp2_index, - &w->hurs_index, &w->hurs_comp1_index, &w->hurs_comp2_index, &w->huss_index, &w->tdps_index, - &w->vp_index, &w->rsds_index}; + int varIndices[MAX_INPUT_COLUMNS]; MyFileName = SW_F_name(eWeather); f = OpenFile(MyFileName, "r"); @@ -1346,7 +1343,7 @@ void SW_WTH_setup(void) { // Loop through MAX_INPUT_COLUMNS for(columnNum = 3; columnNum < MAX_INPUT_COLUMNS; columnNum++) { - variableIndices[varArrIndex] = 0; + varIndices[varArrIndex] = 0; // Check if current flag is in relation to component variables if(&inputFlags[columnNum] == &inputFlags[tempCompIndex] || @@ -1355,7 +1352,7 @@ void SW_WTH_setup(void) { // Set component flag componentFlag = swTRUE; - variableIndices[varArrIndex + 1] = 0; + varIndices[varArrIndex + 1] = 0; } else { componentFlag = swFALSE; } @@ -1364,13 +1361,13 @@ void SW_WTH_setup(void) { if(*inputFlags[columnNum]) { // Set current index to "n_input_forcings" - variableIndices[varArrIndex] = w->n_input_forcings; + varIndices[varArrIndex] = w->n_input_forcings; // Check if flag is meant for components variables if(componentFlag) { // Set next index to n_input_focings + 1 - variableIndices[varArrIndex + 1] = w->n_input_forcings + 1; + varIndices[varArrIndex + 1] = w->n_input_forcings + 1; // Increment "varArrIndex" by two varArrIndex += 2; @@ -1399,6 +1396,15 @@ void SW_WTH_setup(void) { } } } + + // Set variable indices in SW_WEATHER + // Note: SW_WEATHER index being set is guaranteed to have the respective value + // at the given index of `varIndices` (e.g., w->windComp1_index will only be at index 5 in `varIndices`) + w->tempComp1_index = varIndices[0], w->tempComp2_index = varIndices[1], w->ppt_index = varIndices[2]; + w->cloudCover_index = varIndices[3], w->sfcWind_index = varIndices[4], w->windComp1_index = varIndices[5]; + w->windComp2_index = varIndices[6], w->hurs_index = varIndices[7], w->hurs_comp1_index = varIndices[8]; + w->hurs_comp2_index = varIndices[9], w->huss_index = varIndices[10], w->tdps_index = varIndices[11]; + w->vp_index = varIndices[12], w->rsds_index = varIndices[13]; } From e5d15fc4a2b3b12faece86a37dedc13085353c1c Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Mon, 2 Jan 2023 14:36:05 -0700 Subject: [PATCH 254/326] Small fixes for various bugs in `_read_weather_hist()` - Moved interpolation of monthly values to before `_read_weather_hist()` reads weather values so relative humidity can be available if `actualVaporPressure()` requires it and program is only using monthly values * Cloud cover and wind speed interpolation just follow the movement of relative humidity interpolation - Corrected first and second index for respective max/min temperature data - `actualVaporPressure()` now takes in "yearWeather->r_humidity_daily[doy]" to get the current daily data, interpolated or read-in --- src/SW_Weather.c | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index b27f19acb..23f9d2256 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -1482,7 +1482,7 @@ void _read_weather_hist( // RealF acc = 0.0; RealF weathInput[MAX_INPUT_COLUMNS + 2]; - int minTempIndex = weath->tempComp1_index, maxTempIndex = weath->tempComp2_index, + int maxTempIndex = weath->tempComp1_index, minTempIndex = weath->tempComp2_index, precipIndex = weath->ppt_index, cloudcovIndex = weath->cloudCover_index, windSpeedIndex = weath->sfcWind_index, windComp1Index = weath->windComp1_index, windComp2Index = weath->windComp2_index, relHumIndex = weath->huss_index, @@ -1503,6 +1503,22 @@ void _read_weather_hist( if (NULL == (f = fopen(fname, "r"))) return; + // Update yearly day/month information needed when interpolating + // cloud cover, wind speed, and relative humidity if necessary + Time_new_year(year); + + if(weath->use_cloudCoverMonthly) { + interpolate_monthlyValues(sky->cloudcov, interpAsBase1, yearWeather->cloudcov_daily); + } + + if(weath->use_windSpeedMonthly) { + interpolate_monthlyValues(sky->windspeed, interpAsBase1, yearWeather->windspeed_daily); + } + + if(weath->use_relHumidityMonthly) { + interpolate_monthlyValues(sky->r_humidity, interpAsBase1, yearWeather->r_humidity_daily); + } + while (GetALine(f, inbuf)) { lineno++; x = sscanf(inbuf, "%d %f %f %f %f %f %f %f %f %f %f %f %f %f %f", @@ -1563,7 +1579,7 @@ void _read_weather_hist( } else if(weath->has_hurs2) { yearWeather->r_humidity_daily[doy] = (weathInput[hursComp1Index] + - weathInput[hursComp2Index]) / 2; + weathInput[hursComp2Index]) / 2; } else if(weath->has_huss) { es = (6.112 * exp(17.67 * averageTemp)) / (averageTemp + 243.5); @@ -1596,7 +1612,7 @@ void _read_weather_hist( } else { yearWeather->actualVaporPressure[doy] = - actualVaporPressure1(weathInput[relHumIndex], + actualVaporPressure1(yearWeather->r_humidity_daily[doy], averageTemp); } @@ -1617,23 +1633,6 @@ void _read_weather_hist( */ } /* end of input lines */ - // Update yearly day/month information needed when interpolating - // cloud cover, wind speed, and relative humidity - Time_new_year(year); - - if(weath->use_cloudCoverMonthly) { - interpolate_monthlyValues(sky->cloudcov, interpAsBase1, yearWeather->cloudcov_daily); - } - - if(weath->use_windSpeedMonthly) { - interpolate_monthlyValues(sky->windspeed, interpAsBase1, yearWeather->windspeed_daily); - } - - if(weath->use_relHumidityMonthly) { - interpolate_monthlyValues(sky->r_humidity, interpAsBase1, yearWeather->r_humidity_daily); - } - - // Calculate annual average temperature based on historical input // wh->temp_year_avg = acc / (k + 0.0); From 5fd87294faf71789b66251e723e351012fede7d6 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Mon, 2 Jan 2023 14:41:34 -0700 Subject: [PATCH 255/326] Misc. implementation/fixes: monthly flags and "now" data - Added shortwave radiation and actual vapor pressure to update the current daily value in `SW_WTH_new_day()` - The setting of flags in `SW_WTH_setup()` now use `itob()` to translate 0/1 to a Bool --- src/SW_Weather.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 23f9d2256..bb535d9d5 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -1122,8 +1122,8 @@ void SW_WTH_new_day(void) { 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]; - - //swprintf("%f %d %d\n", wn->cloudCover, day, SW_Model.year); + wn->shortWaveRad = w->allHist[yearIndex]->shortWaveRad[day]; + wn->actualVaporPressure = w->allHist[yearIndex]->actualVaporPressure[day]; wn->temp_avg = w->allHist[yearIndex]->temp_avg[day]; @@ -1217,15 +1217,15 @@ void SW_WTH_setup(void) { break; case 5: - w->use_cloudCoverMonthly = atoi(inbuf); + w->use_cloudCoverMonthly = itob(atoi(inbuf)); break; case 6: - w->use_windSpeedMonthly = atoi(inbuf); + w->use_windSpeedMonthly = itob(atoi(inbuf)); break; case 7: - w->use_relHumidityMonthly = atoi(inbuf); + w->use_relHumidityMonthly = itob(atoi(inbuf)); break; case 8: From fd10a8b66215d2a3396dd4daca8b9248c63b91c1 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 3 Jan 2023 12:05:14 -0700 Subject: [PATCH 256/326] New crash conditions in `SW_WTH_new_day()` --- src/SW_Weather.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 24fb0fb20..2f19455ae 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -979,7 +979,10 @@ void SW_WTH_new_day(void) { /* get the daily weather from allHist */ if ( missing(w->allHist[yearIndex]->temp_avg[day]) || - missing(w->allHist[yearIndex]->ppt[day]) + missing(w->allHist[yearIndex]->ppt[day]) || + missing(w->allHist[yearIndex]->cloudcov_daily[day]) || + missing(w->allHist[yearIndex]->windspeed_daily[day]) || + missing(w->allHist[yearIndex]->r_humidity_daily[day]) ) { LogError( logfp, From 5ec3012c812ea5eb31336b3746ebe09aa71d79a7 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 3 Jan 2023 12:08:44 -0700 Subject: [PATCH 257/326] Water balance tests avoid crashes within `SW_WTH_new_day()` - With the new crash conditions in `SW_WTH_new_day()` WaterBalanceTest.WithWeatherGeneratorForSomeMissingValues and WaterBalanceTest.WithWeatherGeneratorOnly would result in crashes - These tests would skip `_read_weather_hist()` resulting in SW_MISSING as daily values, so it has been fixed by setting the daily values to 0 before `SW_CTL_main()` is called --- tests/gtests/test_WaterBalance.cc | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/tests/gtests/test_WaterBalance.cc b/tests/gtests/test_WaterBalance.cc index a9bff44eb..f65e83b61 100644 --- a/tests/gtests/test_WaterBalance.cc +++ b/tests/gtests/test_WaterBalance.cc @@ -109,7 +109,7 @@ namespace { TEST(WaterBalanceTest, WithWeatherGeneratorOnly) { - int i; + int i, year, day; // Turn on Markov weather generator (and turn off use of historical weather) SW_Weather.generateWeatherMethod = 2; @@ -125,6 +125,16 @@ namespace { SW_WTH_read(); SW_WTH_finalize_all_weather(); + // Set daily cloud cover, wind speed, and relative humidity values aside from + // SW_MISSING so crash does not occur within `SW_WTH_new_day()` + for(year = 0; year < 31; year++) { + for(day = 0; day < MAX_DAYS; day++) { + SW_Weather.allHist[year]->cloudcov_daily[day] = 0.; + SW_Weather.allHist[year]->windspeed_daily[day] = 0.; + SW_Weather.allHist[year]->r_humidity_daily[day] = 0.; + } + } + // Run the simulation SW_CTL_main(); @@ -141,7 +151,7 @@ namespace { TEST(WaterBalanceTest, WithWeatherGeneratorForSomeMissingValues) { - int i; + int i, year, day; // Turn on Markov weather generator SW_Weather.generateWeatherMethod = 2; @@ -156,6 +166,16 @@ namespace { SW_WTH_read(); SW_WTH_finalize_all_weather(); + // Set daily cloud cover, wind speed, and relative humidity values aside from + // SW_MISSING so crash does not occur within `SW_WTH_new_day()` + for(year = 0; year < 31; year++) { + for(day = 0; day < MAX_DAYS; day++) { + SW_Weather.allHist[year]->cloudcov_daily[day] = 0.; + SW_Weather.allHist[year]->windspeed_daily[day] = 0.; + SW_Weather.allHist[year]->r_humidity_daily[day] = 0.; + } + } + // Run the simulation SW_CTL_main(); From f1c0efc8d33bab2b8849e5ab8f6b3eb47e4036c7 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 3 Jan 2023 12:54:42 -0700 Subject: [PATCH 258/326] Updated previously "Temporary" weather calculations - Replaced the previous setting of "yearWeather->r_humidity_daily" with a new calculation instead of setting it to the respective "weathInput" index/value - Moved previous hurs calculation within actual vapor pressure section to dew point section for further possible calculation of hurs --- src/SW_Weather.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 0941e331e..fe4b1ee82 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -50,6 +50,7 @@ #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_Weather.h" @@ -1493,7 +1494,7 @@ void _read_weather_hist( hursComp2Index = weath->hurs_comp2_index, hussIndex = weath->huss_index, dewPointIndex = weath->tdps_index; - double es, e, relHum, averageTemp = 0.; + double es, e, relHum, averageTemp = 0., tempSlope, svpVal; /* Interpolation is to be in base0 in `interpolate_monthlyValues()` */ Bool interpAsBase1 = swFALSE; @@ -1578,7 +1579,9 @@ void _read_weather_hist( yearWeather->r_humidity_daily[doy] = weathInput[relHumIndex]; } else if(weath->has_vp) { - yearWeather->r_humidity_daily[doy] = weathInput[relHumIndex]; // Temporary + svpVal = svp(yearWeather->temp_avg[doy], &tempSlope); + + yearWeather->r_humidity_daily[doy] = weathInput[vaporPressIndex] / svpVal; } else if(weath->has_hurs2) { yearWeather->r_humidity_daily[doy] = (weathInput[hursComp1Index] + @@ -1603,9 +1606,14 @@ void _read_weather_hist( yearWeather->actualVaporPressure[doy] = actualVaporPressure3(weathInput[dewPointIndex]); - } else if(!weath->use_relHumidityMonthly && !weath->has_hurs) { + // If hurs was calculated, replace previous calculation with this one + if(!weath->use_relHumidityMonthly && !weath->has_hurs) { + svpVal = svp(yearWeather->temp_avg[doy], &tempSlope); + + yearWeather->r_humidity_daily[doy] = + yearWeather->actualVaporPressure[doy] / svpVal; + } - yearWeather->actualVaporPressure[doy] = weathInput[dewPointIndex]; // Temporary } else if(weath->has_hurs2 && weath->has_temp2) { yearWeather->actualVaporPressure[doy] = actualVaporPressure2(weathInput[hursComp2Index], From dc5bd9b7a267ef8109d96a9e86d7e91ae903e447 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 3 Jan 2023 12:57:12 -0700 Subject: [PATCH 259/326] Default shortwave rad. and act. vapor pressure to SW_MISSING --- src/SW_Weather.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index fe4b1ee82..d562af3b2 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -956,6 +956,8 @@ void _clear_hist_weather(SW_WEATHER_HIST *yearWeather) { 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; } } From f8ebf589ff7bb669fcc9adc047f6c04994b52729 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 3 Jan 2023 13:05:47 -0700 Subject: [PATCH 260/326] Update call to `solar_radiation()` in tests relying on it - Created arrays of "e_a" to send into `solar_radiation()` based on e_a values that were previously calculated in the function - Replaced relative humidity and mean air temperature in calls to `solar_radiation()` with "e_a" values either as a single variable or an array --- tests/gtests/test_SW_Flow_Lib_PET.cc | 42 +++++++++++++++------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/tests/gtests/test_SW_Flow_Lib_PET.cc b/tests/gtests/test_SW_Flow_Lib_PET.cc index fddbfbf56..cd82894f9 100644 --- a/tests/gtests/test_SW_Flow_Lib_PET.cc +++ b/tests/gtests/test_SW_Flow_Lib_PET.cc @@ -731,12 +731,10 @@ namespace // 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 - 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) - 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}; + // Actual vapor pressure (kPa) + actual_vap_pressure[12] = + {0.232164, 0.278781, 0.442500, 0.682698, 1.024887, 1.500820, 1.843139, 1.761336, + 1.343817, 0.863361, 0.538767, 0.313456}; // Duffie & Beckman 2013: Example 2.19.1 for (k = 0; k < 12; k++) { @@ -748,8 +746,7 @@ namespace 0., // aspect albedo[k], cloud_cover[k], - rel_humidity[k], - air_temp_mean[k], + actual_vap_pressure[k], &H_oh, &H_ot, &H_gh @@ -835,14 +832,17 @@ namespace expected_pet_avgtemps[] = { 0.0100, 0.0184, 0.0346, 0.0576, 0.0896, 0.1290, 0.1867, 0.2736, 0.4027, 0.5890 - }; + }, + // Actual vapor pressure input + e_a_input[] = {0.030606, 0.076018, 0.174284, 0.372588, 0.749057, 1.426352, + 2.588270, 4.499124, 7.525423, 12.159202}; for (i = 0; i < 10; i++) { H_gt = solar_radiation( doy, lat, elev, slope0, aspect, reflec, - cloudcov, RH, avgtemps[i], + cloudcov, e_a_input[i], &H_oh, &H_ot, &H_gh ); @@ -859,12 +859,14 @@ namespace // Expected PET expected_pet_lats[] = {0.1550, 0.4360, 0.3597, 0.1216, 0.0421}; + double e_a = 1.932344; + 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, &H_oh, &H_ot, &H_gh ); @@ -889,7 +891,7 @@ namespace H_gt = solar_radiation( doy, lat, elevs[i], slope0, aspect, reflec, - cloudcov, RH, temp, + cloudcov, e_a, &H_oh, &H_ot, &H_gh ); @@ -911,7 +913,7 @@ namespace H_gt = solar_radiation( doy, lat, elev, slopes[i] * deg_to_rad, aspect, reflec, - cloudcov, RH, temp, + cloudcov, e_a, &H_oh, &H_ot, &H_gh ); @@ -938,7 +940,7 @@ namespace H_gt = solar_radiation( doy, lat, elev, sloped, aspects[i] * deg_to_rad, reflec, - cloudcov, RH, temp, + cloudcov, e_a, &H_oh, &H_ot, &H_gh ); @@ -962,7 +964,7 @@ namespace H_gt = solar_radiation( doy, lat, elev, sloped, aspect, reflecs[i], - cloudcov, RH, temp, + cloudcov, e_a, &H_oh, &H_ot, &H_gh ); @@ -977,14 +979,16 @@ namespace // Inputs RHs[] = {0, 34, 56, 79, 100}, // Expected PET - expected_pet_RHs[] = {0.2267, 0.2123, 0.1662, 0.1128, 0.0612}; + expected_pet_RHs[] = {0.2267, 0.2123, 0.1662, 0.1128, 0.0612}, + // Input actual vapor pressure + e_a_in[] = {0.0, 1.077044, 1.773956, 2.502544, 3.167778}; for (i = 0; i < 5; i++) { H_gt = solar_radiation( doy, lat, elev, slope0, aspect, reflec, - cloudcov, RHs[i], temp, + cloudcov, e_a_in[i], &H_oh, &H_ot, &H_gh ); @@ -1004,7 +1008,7 @@ namespace H_gt = solar_radiation( doy, lat, elev, slope0, aspect, reflec, - cloudcov, RH, temp, + cloudcov, e_a, &H_oh, &H_ot, &H_gh ); @@ -1029,7 +1033,7 @@ namespace H_gt = solar_radiation( doy, lat, elev, slope0, aspect, reflec, - cloudcovs[i], RH, temp, + cloudcovs[i], e_a, &H_oh, &H_ot, &H_gh ); From bf7c5f0bcac09927604a8eb8d48ae45e7f7c75f8 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 3 Jan 2023 17:18:34 -0700 Subject: [PATCH 261/326] Made unnecessary `allHist` values 0 in tests - Created a new function, `zero_unncessary_weather()`, in "test_SW_Weather.cc" - The new function zeroes all days of cloud cover, wind speed, relative humidity, shortwave radiation, and actual vapor pressure when they have no relevance to a test - Function is currently only used in "test_SW_Weather.cc" and not "test_WaterBalance.cc" --- tests/gtests/test_SW_Weather.cc | 27 +++++++++++++++++++++++++++ tests/gtests/test_WaterBalance.cc | 10 ++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/tests/gtests/test_SW_Weather.cc b/tests/gtests/test_SW_Weather.cc index 8606b6b87..65e324dd3 100644 --- a/tests/gtests/test_SW_Weather.cc +++ b/tests/gtests/test_SW_Weather.cc @@ -20,6 +20,24 @@ #include "include/SW_Markov.h" #include "include/SW_Model.h" +static void zero_unncessary_weather(int numYears); + +static void zero_unncessary_weather(int numYears) { + int year, day; + + // Set daily cloud cover, wind speed, and relative humidity values aside from + // SW_MISSING so crash does not occur within `SW_WTH_new_day()` + for(year = 0; year < numYears; year++) { + for(day = 0; day < MAX_DAYS; day++) { + SW_Weather.allHist[year]->cloudcov_daily[day] = 0.; + SW_Weather.allHist[year]->windspeed_daily[day] = 0.; + SW_Weather.allHist[year]->r_humidity_daily[day] = 0.; + SW_Weather.allHist[year]->shortWaveRad[day] = 0.; + SW_Weather.allHist[year]->actualVaporPressure[day] = 0.; + } + } +} + namespace { TEST(ReadAllWeatherTest, DefaultValues) { @@ -70,6 +88,9 @@ namespace { SW_MKV_setup(); SW_WTH_read(); + + zero_unncessary_weather(31); + SW_WTH_finalize_all_weather(); @@ -98,6 +119,9 @@ namespace { SW_Model.endyr = 1982; SW_WTH_read(); + + zero_unncessary_weather(2); + SW_WTH_finalize_all_weather(); @@ -125,6 +149,9 @@ namespace { strcpy(SW_Weather.name_prefix, "Input/data_weather_nonexisting/weath"); SW_WTH_read(); + + zero_unncessary_weather(31); + SW_WTH_finalize_all_weather(); // Check everyday's value and test if it's `MISSING` diff --git a/tests/gtests/test_WaterBalance.cc b/tests/gtests/test_WaterBalance.cc index f65e83b61..778a60635 100644 --- a/tests/gtests/test_WaterBalance.cc +++ b/tests/gtests/test_WaterBalance.cc @@ -123,7 +123,6 @@ namespace { // Prepare weather data SW_WTH_read(); - SW_WTH_finalize_all_weather(); // Set daily cloud cover, wind speed, and relative humidity values aside from // SW_MISSING so crash does not occur within `SW_WTH_new_day()` @@ -132,9 +131,13 @@ namespace { SW_Weather.allHist[year]->cloudcov_daily[day] = 0.; SW_Weather.allHist[year]->windspeed_daily[day] = 0.; SW_Weather.allHist[year]->r_humidity_daily[day] = 0.; + SW_Weather.allHist[year]->shortWaveRad[day] = 0.; + SW_Weather.allHist[year]->actualVaporPressure[day] = 0.; } } + SW_WTH_finalize_all_weather(); + // Run the simulation SW_CTL_main(); @@ -164,7 +167,6 @@ namespace { // Prepare weather data SW_WTH_read(); - SW_WTH_finalize_all_weather(); // Set daily cloud cover, wind speed, and relative humidity values aside from // SW_MISSING so crash does not occur within `SW_WTH_new_day()` @@ -173,9 +175,13 @@ namespace { SW_Weather.allHist[year]->cloudcov_daily[day] = 0.; SW_Weather.allHist[year]->windspeed_daily[day] = 0.; SW_Weather.allHist[year]->r_humidity_daily[day] = 0.; + SW_Weather.allHist[year]->shortWaveRad[day] = 0.; + SW_Weather.allHist[year]->actualVaporPressure[day] = 0.; } } + SW_WTH_finalize_all_weather(); + // Run the simulation SW_CTL_main(); From da73041ef4ae55fcb09def96da8ad3c58843fc63 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 3 Jan 2023 19:18:52 -0700 Subject: [PATCH 262/326] New crash conditions and updated `checkAllWeather()` last doy - Updated `SW_WTH_new_day()` to check shortwave radiation and actual vapor pressure * Since shortwave radiation is not guaranteed to be calculated when reading in the weather - Updated call to `Time_get_lastdoy_y()` to take in actual year (.., 1980, 1981, ...) instead of index year (0, 1, ...) --- src/SW_Weather.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index d562af3b2..689dff7a8 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -859,7 +859,7 @@ void checkAllWeather(SW_WEATHER *weather) { // Loop through `allHist` years for(year = 0; year < weather->n_years; year++) { - numDaysInYear = Time_get_lastdoy_y(year); + numDaysInYear = Time_get_lastdoy_y(year + weather->startYear); // Loop through `allHist` days for(doy = 0; doy < numDaysInYear; doy++) { @@ -1111,7 +1111,9 @@ void SW_WTH_new_day(void) { missing(w->allHist[yearIndex]->ppt[day]) || missing(w->allHist[yearIndex]->cloudcov_daily[day]) || missing(w->allHist[yearIndex]->windspeed_daily[day]) || - missing(w->allHist[yearIndex]->r_humidity_daily[day]) + missing(w->allHist[yearIndex]->r_humidity_daily[day]) || + (missing(w->allHist[yearIndex]->shortWaveRad[day]) && w->has_rsds) || + missing(w->allHist[yearIndex]->actualVaporPressure[day]) ) { LogError( logfp, From 24062eaadc8fb63ae2de2304fcd9abe0725b4b91 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 5 Jan 2023 12:16:32 -0700 Subject: [PATCH 263/326] New function `svp2()` - Created a new function within "SW_Flow_lib_PET.c" - `actualVaporPressureX()` all used equation 7 from Allen, 2005, so this function is to be used as an alternative for calculating saturated vapor pressure --- include/SW_Flow_lib_PET.h | 1 + src/SW_Flow_lib_PET.c | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/include/SW_Flow_lib_PET.h b/include/SW_Flow_lib_PET.h index 563f01c39..e0082157b 100644 --- a/include/SW_Flow_lib_PET.h +++ b/include/SW_Flow_lib_PET.h @@ -49,6 +49,7 @@ 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); diff --git a/src/SW_Flow_lib_PET.c b/src/SW_Flow_lib_PET.c index d3dbe78ec..d0473827a 100644 --- a/src/SW_Flow_lib_PET.c +++ b/src/SW_Flow_lib_PET.c @@ -980,6 +980,20 @@ double svp(double T, double *slope_svp_to_t) { return 1e-3 * svp; } +/** + @brief Saturation vapor pressure for calculation of actual vapor pressure + + Calculation is based on Allen et al. 2006 @cite allen2006AaFM which is + based on Majumdar et al. 1972 and updated by ASCE-EWRI 2005. + + @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)); +} /** From 9a9dcef4ba05df74dd60ce4999b86148ccbfb4d3 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 5 Jan 2023 12:20:30 -0700 Subject: [PATCH 264/326] Moved `actualVaporPressureX()` to "SW_Flow_lib_PET.h" - Moved `actualVaporPressureX()` functions to be with other functions similar to themselves - Updated functions to use new `svp2()` instead of containing code for the same equation --- include/SW_Flow_lib_PET.h | 4 +++ include/SW_Weather.h | 3 --- src/SW_Flow_lib_PET.c | 56 +++++++++++++++++++++++++++++++++++++++ src/SW_Weather.c | 55 -------------------------------------- 4 files changed, 60 insertions(+), 58 deletions(-) diff --git a/include/SW_Flow_lib_PET.h b/include/SW_Flow_lib_PET.h index e0082157b..57441aaa9 100644 --- a/include/SW_Flow_lib_PET.h +++ b/include/SW_Flow_lib_PET.h @@ -53,6 +53,10 @@ double svp2(double temp); double atmospheric_pressure(double elev); double psychrometric_constant(double pressure); +double actualVaporPressure1(double hurs, double tmean); +double actualVaporPressure2(double hursMax, double hursMin, double maxTemp, double minTemp); +double actualVaporPressure3(double tdps); + #ifdef __cplusplus } #endif diff --git a/include/SW_Weather.h b/include/SW_Weather.h index d5534ce5c..c0da02ccd 100644 --- a/include/SW_Weather.h +++ b/include/SW_Weather.h @@ -251,9 +251,6 @@ void generateMissingWeather( unsigned int method, unsigned int optLOCF_nMax ); -double actualVaporPressure1(double hurs, double tmean); -double actualVaporPressure2(double hursMax, double hursMin, double maxTemp, double minTemp); -double actualVaporPressure3(double tdps); void checkAllWeather(SW_WEATHER *weather); void allocateAllWeather(SW_WEATHER *w); void deallocateAllWeather(SW_WEATHER *w); diff --git a/src/SW_Flow_lib_PET.c b/src/SW_Flow_lib_PET.c index d0473827a..84decd69b 100644 --- a/src/SW_Flow_lib_PET.c +++ b/src/SW_Flow_lib_PET.c @@ -995,6 +995,62 @@ double svp2(double temp) { return .6108 * exp((17.27 * temp) / (temp + 237.3)); } +/** + @brief Calculate actual vapor pressure based on relative humidity and mean temperature + + Calculation is based on Allen et al. 2006 @cite allen2006AaFM which is + based on Majumdar et al. 1972 and updated by ASCE-EWRI 2005. + + @param hurs Daily mean relative humidity [%] + @param tmean Daily mean air temperature [C] + + @return Calculated actual vapor pressure [kPa] + */ +double actualVaporPressure1(double hurs, double tmean) { + // Allen et al. 2005 eqs 7 and 14 + return (hurs / 100.) * svp2(tmean); +} + +/** + @brief Calculate actual vapor pressure based on temperature and relative humidity components (min/max) + + Calculation is based on Allen et al. 2006 @cite allen2006AaFM which is + based on Majumdar et al. 1972 and updated by ASCE-EWRI 2005. + + @param hursMax Daily maximum relative humidity [%] + @param hursMin 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 hursMax, double hursMin, double maxTemp, double minTemp) { + // Allen et al. 2005 eqs 7 and 11 + double satVapPressureMax = svp2(maxTemp); + double satVapPressureMin = svp2(minTemp); + + double relHumVapPressMax = satVapPressureMin * (hursMax / 100); + double relHumVapPressMin = satVapPressureMax * (hursMin / 100); + + return (relHumVapPressMax + relHumVapPressMin) / 2; +} + +/** + @brief Calculate actual vapor pressure based on dew point temperature + + Calculation is based on Allen et al. 2006 @cite allen2006AaFM which is + based on Majumdar et al. 1972 and updated by ASCE-EWRI 2005. + + @param tdps 2m dew point temperature [C] + + @return Calculated actual vapor pressure [kPa] + */ +double actualVaporPressure3(double tdps) { + // Allen et al. 2005 eqs 7 and 8 + return svp2(tdps); +} + + /** @brief Daily potential evapotranspiration diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 689dff7a8..9c60f6c1b 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -1671,61 +1671,6 @@ void _read_weather_hist( fclose(f); } -/** - @brief Calculate actual vapor pressure based on relative humidity and mean temperature - - Calculation is based on Allen et al. 2006 @cite allen2006AaFM which is - based on Majumdar et al. 1972 and updated by ASCE-EWRI 2005. - - @param hurs Daily mean relative humidity [%] - @param tmean Daily mean air temperature [C] - - @return Calculated actual vapor pressure [kPa] - */ -double actualVaporPressure1(double hurs, double tmean) { - // Allen et al. 2005 eqs 7 and 14 - return (hurs / 100.) * 0.6108 * exp((17.27 * tmean) / (tmean + 237.3)); -} - -/** - @brief Calculate actual vapor pressure based on temperature and relative humidity components (min/max) - - Calculation is based on Allen et al. 2006 @cite allen2006AaFM which is - based on Majumdar et al. 1972 and updated by ASCE-EWRI 2005. - - @param hursMax Daily maximum relative humidity [%] - @param hursMin 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 hursMax, double hursMin, double maxTemp, double minTemp) { - // Allen et al. 2005 eqs 7 and 11 - double satVapPressureMax = .6108 * exp((17.27 * maxTemp) / maxTemp + 237.3); - double satVapPressureMin = .6108 * exp((17.27 * minTemp) / minTemp + 237.3); - - double relHumVapPressMax = satVapPressureMin * (hursMax / 100); - double relHumVapPressMin = satVapPressureMax * (hursMin / 100); - - return (relHumVapPressMax + relHumVapPressMin) / 2; -} - -/** - @brief Calculate actual vapor pressure based on dew point temperature - - Calculation is based on Allen et al. 2006 @cite allen2006AaFM which is - based on Majumdar et al. 1972 and updated by ASCE-EWRI 2005. - - @param tdps 2m dew point temperature [C] - - @return Calculated actual vapor pressure [kPa] - */ -double actualVaporPressure3(double tdps) { - // Allen et al. 2005 eqs 7 and 8 - return .6108 * exp((17.27 * tdps) / (tdps + 237.3)); -} - void allocateClimateStructs(int numYears, SW_CLIMATE_YEARLY *climateOutput, SW_CLIMATE_CLIM *climateAverages) { From fa327d005535e8b53136a83f3cbeb6d6e763fa19 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 5 Jan 2023 12:58:38 -0700 Subject: [PATCH 265/326] Moved interpolation and actual vapor pressure calculation - Monthly interpolation of cloud cover, wind speed, and relative humidity along with the calculation of actual vapor pressure using `actualVaporPressure1()` have been moved to `readAllWeather()` - Actual vapor pressure calculation that makes use of dewpoint temperature and minimum/maximum relative humidity/temperature still remains in `_read_weather_hist()` - The reason for this change is when tests skip `_read_weather_hist()` by using only the weather generator, interpolating and calculating actual vapor pressure in tests no longer need to zero any values so unnecessary crashes don't occur --- src/SW_Weather.c | 75 ++++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 9c60f6c1b..8b6a9db4c 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -512,13 +512,33 @@ void readAllWeather( Bool use_weathergenerator_only, char weather_prefix[] ) { - unsigned int yearIndex; + unsigned int yearIndex, year, day; + + /* Interpolation is to be in base0 in `interpolate_monthlyValues()` */ + Bool interpAsBase1 = swFALSE; for(yearIndex = 0; yearIndex < n_years; yearIndex++) { + year = yearIndex + SW_Weather.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(SW_Weather.use_cloudCoverMonthly) { + interpolate_monthlyValues(SW_Sky.cloudcov, interpAsBase1, allHist[yearIndex]->cloudcov_daily); + } + + if(SW_Weather.use_windSpeedMonthly) { + interpolate_monthlyValues(SW_Sky.windspeed, interpAsBase1, allHist[yearIndex]->windspeed_daily); + } + + if(SW_Weather.use_relHumidityMonthly) { + interpolate_monthlyValues(SW_Sky.r_humidity, interpAsBase1, allHist[yearIndex]->r_humidity_daily); + } + // Read daily weather values from disk if (!use_weathergenerator_only) { @@ -528,6 +548,16 @@ void readAllWeather( weather_prefix ); } + + // Check to see if actual vapor pressure needs to be calculated + if(!SW_Weather.has_vp && SW_Weather.use_relHumidityMonthly) { + + for(day = 0; day < MAX_DAYS; day++) { + allHist[yearIndex]->actualVaporPressure[day] = + actualVaporPressure1(allHist[yearIndex]->r_humidity_daily[day], + allHist[yearIndex]->temp_avg[day]); + } + } } } @@ -1483,7 +1513,6 @@ void _read_weather_hist( */ FILE *f; - SW_SKY *sky = &SW_Sky; SW_WEATHER *weath = &SW_Weather; int x, lineno = 0, doy; // TimeInt mon, j, k = 0; @@ -1498,10 +1527,7 @@ void _read_weather_hist( hursComp2Index = weath->hurs_comp2_index, hussIndex = weath->huss_index, dewPointIndex = weath->tdps_index; - double es, e, relHum, averageTemp = 0., tempSlope, svpVal; - - /* Interpolation is to be in base0 in `interpolate_monthlyValues()` */ - Bool interpAsBase1 = swFALSE; + double es, e, relHum, tempSlope, svpVal; char fname[MAX_FILENAMESIZE]; @@ -1511,22 +1537,6 @@ void _read_weather_hist( if (NULL == (f = fopen(fname, "r"))) return; - // Update yearly day/month information needed when interpolating - // cloud cover, wind speed, and relative humidity if necessary - Time_new_year(year); - - if(weath->use_cloudCoverMonthly) { - interpolate_monthlyValues(sky->cloudcov, interpAsBase1, yearWeather->cloudcov_daily); - } - - if(weath->use_windSpeedMonthly) { - interpolate_monthlyValues(sky->windspeed, interpAsBase1, yearWeather->windspeed_daily); - } - - if(weath->use_relHumidityMonthly) { - interpolate_monthlyValues(sky->r_humidity, interpAsBase1, yearWeather->r_humidity_daily); - } - while (GetALine(f, inbuf)) { lineno++; x = sscanf(inbuf, "%d %f %f %f %f %f %f %f %f %f %f %f %f %f %f", @@ -1559,8 +1569,6 @@ void _read_weather_hist( yearWeather->temp_avg[doy] = (weathInput[maxTempIndex] + weathInput[minTempIndex]) / 2.0; - - averageTemp = yearWeather->temp_avg[doy]; } if(!weath->use_cloudCoverMonthly && weath->has_cloudCover) { @@ -1592,7 +1600,8 @@ void _read_weather_hist( weathInput[hursComp2Index]) / 2; } else if(weath->has_huss) { - es = (6.112 * exp(17.67 * averageTemp)) / (averageTemp + 243.5); + es = (6.112 * exp(17.67 * yearWeather->temp_avg[doy])); + es /= (yearWeather->temp_avg[doy] + 243.5); e = (weathInput[hussIndex] * 1013.25) / (.378 * weathInput[hussIndex] + .622); @@ -1606,33 +1615,25 @@ void _read_weather_hist( } if(!weath->has_vp) { - if(weath->has_tdps) { + if(SW_Weather.has_tdps) { yearWeather->actualVaporPressure[doy] = actualVaporPressure3(weathInput[dewPointIndex]); // If hurs was calculated, replace previous calculation with this one - if(!weath->use_relHumidityMonthly && !weath->has_hurs) { + if(!SW_Weather.use_relHumidityMonthly && !SW_Weather.has_hurs) { svpVal = svp(yearWeather->temp_avg[doy], &tempSlope); yearWeather->r_humidity_daily[doy] = - yearWeather->actualVaporPressure[doy] / svpVal; + yearWeather->actualVaporPressure[doy] / svpVal; } - } else if(weath->has_hurs2 && weath->has_temp2) { + } else if(SW_Weather.has_hurs2 && SW_Weather.has_temp2) { yearWeather->actualVaporPressure[doy] = actualVaporPressure2(weathInput[hursComp2Index], weathInput[hursComp1Index], weathInput[maxTempIndex], weathInput[minTempIndex]); - - } else { - yearWeather->actualVaporPressure[doy] = - actualVaporPressure1(yearWeather->r_humidity_daily[doy], - averageTemp); - } - } else { - yearWeather->actualVaporPressure[doy] = weathInput[vaporPressIndex]; } From 2a4e824a490d8bb34d8a3da1ce0f87bfa32d25be Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 5 Jan 2023 12:59:45 -0700 Subject: [PATCH 266/326] Fixed incorrect setting of relative humidity in `_read_weather_hist()` --- src/SW_Weather.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 8b6a9db4c..1081c5559 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -1623,7 +1623,7 @@ void _read_weather_hist( if(!SW_Weather.use_relHumidityMonthly && !SW_Weather.has_hurs) { svpVal = svp(yearWeather->temp_avg[doy], &tempSlope); - yearWeather->r_humidity_daily[doy] = + yearWeather->actualVaporPressure[doy] = yearWeather->actualVaporPressure[doy] / svpVal; } From 3798f5f321887d78a6f73a261f99bf828f6e0467 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 5 Jan 2023 13:10:31 -0700 Subject: [PATCH 267/326] Weather/water balance tests no longer zero weather values - Previously, cloud cover, relative humidity, wind speed, and actual vapor pressure were zeroed to prevent crashes when `_read_weather_hist()` was not called - With the location changes of interpolation and actual vapor pressure calculation from `_read_weather_hist()` to `readAllWeather()`, this is no longer needed --- tests/gtests/test_SW_Weather.cc | 27 --------------------------- tests/gtests/test_WaterBalance.cc | 30 ++---------------------------- 2 files changed, 2 insertions(+), 55 deletions(-) diff --git a/tests/gtests/test_SW_Weather.cc b/tests/gtests/test_SW_Weather.cc index 65e324dd3..8606b6b87 100644 --- a/tests/gtests/test_SW_Weather.cc +++ b/tests/gtests/test_SW_Weather.cc @@ -20,24 +20,6 @@ #include "include/SW_Markov.h" #include "include/SW_Model.h" -static void zero_unncessary_weather(int numYears); - -static void zero_unncessary_weather(int numYears) { - int year, day; - - // Set daily cloud cover, wind speed, and relative humidity values aside from - // SW_MISSING so crash does not occur within `SW_WTH_new_day()` - for(year = 0; year < numYears; year++) { - for(day = 0; day < MAX_DAYS; day++) { - SW_Weather.allHist[year]->cloudcov_daily[day] = 0.; - SW_Weather.allHist[year]->windspeed_daily[day] = 0.; - SW_Weather.allHist[year]->r_humidity_daily[day] = 0.; - SW_Weather.allHist[year]->shortWaveRad[day] = 0.; - SW_Weather.allHist[year]->actualVaporPressure[day] = 0.; - } - } -} - namespace { TEST(ReadAllWeatherTest, DefaultValues) { @@ -88,9 +70,6 @@ namespace { SW_MKV_setup(); SW_WTH_read(); - - zero_unncessary_weather(31); - SW_WTH_finalize_all_weather(); @@ -119,9 +98,6 @@ namespace { SW_Model.endyr = 1982; SW_WTH_read(); - - zero_unncessary_weather(2); - SW_WTH_finalize_all_weather(); @@ -149,9 +125,6 @@ namespace { strcpy(SW_Weather.name_prefix, "Input/data_weather_nonexisting/weath"); SW_WTH_read(); - - zero_unncessary_weather(31); - SW_WTH_finalize_all_weather(); // Check everyday's value and test if it's `MISSING` diff --git a/tests/gtests/test_WaterBalance.cc b/tests/gtests/test_WaterBalance.cc index 778a60635..a9bff44eb 100644 --- a/tests/gtests/test_WaterBalance.cc +++ b/tests/gtests/test_WaterBalance.cc @@ -109,7 +109,7 @@ namespace { TEST(WaterBalanceTest, WithWeatherGeneratorOnly) { - int i, year, day; + int i; // Turn on Markov weather generator (and turn off use of historical weather) SW_Weather.generateWeatherMethod = 2; @@ -123,19 +123,6 @@ namespace { // Prepare weather data SW_WTH_read(); - - // Set daily cloud cover, wind speed, and relative humidity values aside from - // SW_MISSING so crash does not occur within `SW_WTH_new_day()` - for(year = 0; year < 31; year++) { - for(day = 0; day < MAX_DAYS; day++) { - SW_Weather.allHist[year]->cloudcov_daily[day] = 0.; - SW_Weather.allHist[year]->windspeed_daily[day] = 0.; - SW_Weather.allHist[year]->r_humidity_daily[day] = 0.; - SW_Weather.allHist[year]->shortWaveRad[day] = 0.; - SW_Weather.allHist[year]->actualVaporPressure[day] = 0.; - } - } - SW_WTH_finalize_all_weather(); // Run the simulation @@ -154,7 +141,7 @@ namespace { TEST(WaterBalanceTest, WithWeatherGeneratorForSomeMissingValues) { - int i, year, day; + int i; // Turn on Markov weather generator SW_Weather.generateWeatherMethod = 2; @@ -167,19 +154,6 @@ namespace { // Prepare weather data SW_WTH_read(); - - // Set daily cloud cover, wind speed, and relative humidity values aside from - // SW_MISSING so crash does not occur within `SW_WTH_new_day()` - for(year = 0; year < 31; year++) { - for(day = 0; day < MAX_DAYS; day++) { - SW_Weather.allHist[year]->cloudcov_daily[day] = 0.; - SW_Weather.allHist[year]->windspeed_daily[day] = 0.; - SW_Weather.allHist[year]->r_humidity_daily[day] = 0.; - SW_Weather.allHist[year]->shortWaveRad[day] = 0.; - SW_Weather.allHist[year]->actualVaporPressure[day] = 0.; - } - } - SW_WTH_finalize_all_weather(); // Run the simulation From 19f7b85aac4feaa8fa4196737fba2796bd73d83c Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 5 Jan 2023 14:44:50 -0700 Subject: [PATCH 268/326] Weather tests: Pseudocode - One test section was created for the testing of correct calculations within `_read_weather_hist()` or `actualVaporPressureX()` - These tests include correct calculation of relative humidity, wind speed, and actual vapor pressure - The problem with this is the current tests for wind speed, relative humidity, and actual vapor pressure based on dewpoint temperature rely on the correct values in weath.YYYY - A second test section was created to make sure `checkAllWeather()` is properly crashing when a value is not within a reasonable range * This section also covers an incorrect number of "n_input_forcings" within SW_WEATHER and should crash when `_read_weather_hist()` reads in the actual number of columns in weath.YYYY --- tests/gtests/test_SW_Weather.cc | 95 +++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/tests/gtests/test_SW_Weather.cc b/tests/gtests/test_SW_Weather.cc index 8606b6b87..b0d093da3 100644 --- a/tests/gtests/test_SW_Weather.cc +++ b/tests/gtests/test_SW_Weather.cc @@ -686,5 +686,100 @@ namespace { } + TEST(DailyInsteadOfMonthlyInputTest, InputValueCalculations) { + /* + This section covers the correct resulting values from different calculations of the + same variable. + Variables that are being tested are: + - Relative humidity + - Actual vapor pressure + - Wind speed + + (*): Most parts of the this test section are dependent on what is put in default + SOILWAT2 new daily input files + */ + + // Initialize any variables + + // `actualVaporPressureX()` input values + // `actualVaporPressureX()` resulting value arrays + // Expected results from `actualVaporPressureX()` + // Expected results from calls to `_read_weather_hist()` for + // relative humidity and wind speed + + /* Actual Vapor Pressure only */ + + /* Check all `actualVaporPressureX()` */ + + // Loop through X amount of daily values and store the values + // in respective array + + // Get and store value of call to `actualVaporPressure1()` + // Get and store value of call to `actualVaporPressure2()` + // Get and store value of call to `actualVaporPressure3()` + + // Test resulting value from last call to `actualVaporPressure1()` + // Test resulting value from last call to `actualVaporPressure2()` + // Test resulting value from last call to `actualVaporPressure3()` + + /* Relative humidity only based on input values (*) */ + + // Set flags accordingly to how we would like to calculate relative humidity and + // wind speed + // Reset values from `_read_weather_hist()` for current and following test + + // Test the first few days of calculated relative humidity calculated from: + // - min/max relative humidity + // - actual vapor pressure + // - Specific humidity + + /* Wind speed only based on input values (*) */ + + // Set flag to expect wind speed component input + // Test first X days of values and make sure they are the same as + // sqrt(comp1^2 + comp2^2) + + /* Actual vapor pressure/relative humidity based on dewpoint temperature? (*) */ + } + + TEST(DailyInsteadOfMonthlyInputDeathTest, ReasonableValuesAndFlags) { + // Initialize any variables + + /* + 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. + */ + + /* Not the same number of flags as columns */ + + // Set SW_WEATHER's n_input_forcings to a number that is + // not the columns being read in + + // Run death test + + /* 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]) + // Make precipitation unresonable (< 0) + // Make cloud cover unreasonable (probably greater than 100%) + // Make relative humidity unreasonable (probably less than 0%) + // Make shortwave radiation unreasonable (< 0) + // Make actual vapor pressure unreasonable (< 0) + + // Run death test for temperature + // Run death test for precipitation + // Run death test for cloud cover + // Run death test for relative humidity + // Run death test for shortwave radiation + // Run death test for actual vapor pressure + } } From 01caf94538f476f5771df4ca3ed40dc347ecfe92 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 6 Jan 2023 17:11:46 -0700 Subject: [PATCH 269/326] Fixed typos within `_read_weather_hist()` and `weathsetup.in` - Previously, the equation for calculating actual vapor pressure based on specific humidity was incorrect, "yearWeather->temp_avg[doy] + 243.5" is supposed to be in the exponent - Relative humidity is treated from [0, 100] % not [0, 1] %, so a call to `min()` was corrected to expect 100% - Wind component flags in "weathsetup.in" mentioned "westward" and "eastward" instead of the correct "westward" and "northward" descriptions --- src/SW_Weather.c | 9 +++++---- tests/example/Input/weathsetup.in | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 1081c5559..866ce1128 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -1593,15 +1593,16 @@ void _read_weather_hist( } else if(weath->has_vp) { svpVal = svp(yearWeather->temp_avg[doy], &tempSlope); - yearWeather->r_humidity_daily[doy] = weathInput[vaporPressIndex] / svpVal; + yearWeather->r_humidity_daily[doy] = weathInput[vaporPressIndex] / svpVal; } else if(weath->has_hurs2) { yearWeather->r_humidity_daily[doy] = (weathInput[hursComp1Index] + weathInput[hursComp2Index]) / 2; } else if(weath->has_huss) { - es = (6.112 * exp(17.67 * yearWeather->temp_avg[doy])); - es /= (yearWeather->temp_avg[doy] + 243.5); + // Specific Humidity (Bolton 1980) + es = (6.112 * exp(17.67 * yearWeather->temp_avg[doy]) / + (yearWeather->temp_avg[doy] + 243.5)); e = (weathInput[hussIndex] * 1013.25) / (.378 * weathInput[hussIndex] + .622); @@ -1609,7 +1610,7 @@ void _read_weather_hist( relHum = e / es; relHum = max(0., relHum); - yearWeather->r_humidity_daily[doy] = min(1., relHum); + yearWeather->r_humidity_daily[doy] = min(100., relHum); } } diff --git a/tests/example/Input/weathsetup.in b/tests/example/Input/weathsetup.in index 2be7af12f..30614827f 100755 --- a/tests/example/Input/weathsetup.in +++ b/tests/example/Input/weathsetup.in @@ -34,7 +34,7 @@ 0 # Cloud cover [%] 0 # Wind speed [m/s] 0 # Wind speed eastward component [m/s] -0 # Wind speed westward component [m/s] +0 # Wind speed northward component [m/s] 0 # Relative humidity [%] 0 # Maximum relative humidity [%] 0 # Minimum relative humidity [%] From 601d03aa01e66aed50e144344ec2276de8e77b1b Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 6 Jan 2023 17:19:59 -0700 Subject: [PATCH 270/326] Updated pseudocode for input value calculation tests - Removed previous plan for tests - New focus of pseudocode: * Make sure monthly values are not being interpolated/used when it is not requested * Priorities of input values are correctly given when deciding how to calculate a variable (e.g., using min/max relative humidity over calculating from specific humidity) --- tests/gtests/test_SW_Weather.cc | 56 ++++++++++----------------------- 1 file changed, 16 insertions(+), 40 deletions(-) diff --git a/tests/gtests/test_SW_Weather.cc b/tests/gtests/test_SW_Weather.cc index b0d093da3..060f09764 100644 --- a/tests/gtests/test_SW_Weather.cc +++ b/tests/gtests/test_SW_Weather.cc @@ -688,59 +688,35 @@ namespace { TEST(DailyInsteadOfMonthlyInputTest, InputValueCalculations) { /* - This section covers the correct resulting values from different calculations of the - same variable. - - Variables that are being tested are: - - Relative humidity - - Actual vapor pressure - - Wind speed - - (*): Most parts of the this test section are dependent on what is put in default - SOILWAT2 new daily input files + This section covers the correct prioritization of input values resulting + in the use of the appropriate equation used for calculation */ - // Initialize any variables + // NOTE: capture behaviors and don't be comprehensive (more than likely keep one `EXPECT_NEAR()`) - // `actualVaporPressureX()` input values - // `actualVaporPressureX()` resulting value arrays - // Expected results from `actualVaporPressureX()` - // Expected results from calls to `_read_weather_hist()` for - // relative humidity and wind speed - - /* Actual Vapor Pressure only */ + // Initialize any variables + int day, year = 1980, yearIndex = 0; - /* Check all `actualVaporPressureX()` */ + /* Test if monthly values are not being used */ - // Loop through X amount of daily values and store the values - // in respective array + /* Only test if monthly values are being used for January in relative humidity */ - // Get and store value of call to `actualVaporPressure1()` - // Get and store value of call to `actualVaporPressure2()` - // Get and store value of call to `actualVaporPressure3()` + // Read in all weather - // Test resulting value from last call to `actualVaporPressure1()` - // Test resulting value from last call to `actualVaporPressure2()` - // Test resulting value from last call to `actualVaporPressure3()` + // Test the 15th day of January in year 1980 and see if it's not equal to 61.0 - /* Relative humidity only based on input values (*) */ + /* Test correct priority is being given to input values */ - // Set flags accordingly to how we would like to calculate relative humidity and - // wind speed - // Reset values from `_read_weather_hist()` for current and following test + /* Test with lesser priority than the input */ - // Test the first few days of calculated relative humidity calculated from: - // - min/max relative humidity - // - actual vapor pressure - // - Specific humidity + // Switch directory to new inputs folder - /* Wind speed only based on input values (*) */ + // Using the new inputs folder, read in year = 1980 - // Set flag to expect wind speed component input - // Test first X days of values and make sure they are the same as - // sqrt(comp1^2 + comp2^2) + // Focusing on hurs max and min, artificially set "has_huss" - /* Actual vapor pressure/relative humidity based on dewpoint temperature? (*) */ + // Test if the average of relative humidity has been calculated + // instead of being based on huss for the first day of 1980 } TEST(DailyInsteadOfMonthlyInputDeathTest, ReasonableValuesAndFlags) { From b77c0a1bd7a029fc3155eb41e4185535ebd3aa8d Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 6 Jan 2023 17:33:14 -0700 Subject: [PATCH 271/326] Added code to `ReasonableValuesAndFlags` weather test - Removed half of the tests expressed in the previous pseudocode - The three remaining tests now focus on testing ranges only once: * [-100, 100] for temperature * value >= 0 (precipitation is being tested) * Percentage [0, 100] (tested with relative humidity) - Added a test for when "SW_Weather.n_input_forcings" does not equal the number of input columns read-in which is commented out since it does not currently work --- tests/gtests/test_SW_Weather.cc | 59 +++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/tests/gtests/test_SW_Weather.cc b/tests/gtests/test_SW_Weather.cc index 060f09764..fd2701cf9 100644 --- a/tests/gtests/test_SW_Weather.cc +++ b/tests/gtests/test_SW_Weather.cc @@ -720,8 +720,6 @@ namespace { } TEST(DailyInsteadOfMonthlyInputDeathTest, ReasonableValuesAndFlags) { - // Initialize any variables - /* This section covers number of flags and the testing of reasonable results (`checkAllWeather()`). @@ -732,30 +730,63 @@ namespace { `checkAllWeather()` should result in a crash. */ + // Initialize any variables + char weathPrefix[] = "weath"; + TimeInt year = 1980; + double originVal; + /* Not the same number of flags as columns */ // Set SW_WEATHER's n_input_forcings to a number that is // not the columns being read in + allocateAllWeather(&SW_Weather); + // SW_Weather.n_input_forcings = 7; + SW_Weather.n_years = 1; + // Run death test + // EXPECT_DEATH_IF_SUPPORTED( + // _read_weather_hist(year, SW_Weather.allHist[0], weathPrefix), + // "" + // ); /* 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]) + // Make temperature unreasonable (not within [-100, 100] or max < min) + SW_WTH_read(); + + originVal = SW_Weather.allHist[0]->temp_max[0]; + + SW_Weather.allHist[0]->temp_max[0] = -102.; + + EXPECT_DEATH_IF_SUPPORTED( + checkAllWeather(&SW_Weather), + "" + ); + // Make precipitation unresonable (< 0) - // Make cloud cover unreasonable (probably greater than 100%) - // Make relative humidity unreasonable (probably less than 0%) - // Make shortwave radiation unreasonable (< 0) - // Make actual vapor pressure unreasonable (< 0) - - // Run death test for temperature - // Run death test for precipitation - // Run death test for cloud cover - // Run death test for relative humidity - // Run death test for shortwave radiation - // Run death test for actual vapor pressure + SW_Weather.allHist[0]->temp_max[0] = originVal; + + originVal = SW_Weather.allHist[0]->ppt[0]; + + SW_Weather.allHist[0]->ppt[0] = -1.; + + EXPECT_DEATH_IF_SUPPORTED( + checkAllWeather(&SW_Weather), + "" + ); + + // Make relative humidity unreasonable (< 0%) + SW_Weather.allHist[0]->ppt[0] = originVal; + + SW_Weather.allHist[0]->r_humidity_daily[0] = -.1252; + + EXPECT_DEATH_IF_SUPPORTED( + checkAllWeather(&SW_Weather), + "" + ); } } From 4791a88d76c804f075c00b86574eceeb9bb76319 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Wed, 11 Jan 2023 19:02:28 -0700 Subject: [PATCH 272/326] Updated `actualVaporPressureX()` documentation and headers - `actualVaporPressureX()` headers were inconsistent when compared to each other or to themselves (`actualVaporPressure2()`), so max/min "hurs" has been updated with temperature of the pattern "xTemp" - Documentation for equations used in the functions `actualVaporPressureX()` and `svp2()` previously did not have the correct citation, so the documentation now matches the comment within the functions --- include/SW_Flow_lib_PET.h | 6 +++--- src/SW_Flow_lib_PET.c | 41 +++++++++++++++------------------------ 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/include/SW_Flow_lib_PET.h b/include/SW_Flow_lib_PET.h index 57441aaa9..0b248654f 100644 --- a/include/SW_Flow_lib_PET.h +++ b/include/SW_Flow_lib_PET.h @@ -53,9 +53,9 @@ double svp2(double temp); double atmospheric_pressure(double elev); double psychrometric_constant(double pressure); -double actualVaporPressure1(double hurs, double tmean); -double actualVaporPressure2(double hursMax, double hursMin, double maxTemp, double minTemp); -double actualVaporPressure3(double tdps); +double actualVaporPressure1(double hurs, double meanTemp); +double actualVaporPressure2(double maxHurs, double minHurs, double maxTemp, double minTemp); +double actualVaporPressure3(double dewpointTemp); #ifdef __cplusplus } diff --git a/src/SW_Flow_lib_PET.c b/src/SW_Flow_lib_PET.c index 84decd69b..a801d58e1 100644 --- a/src/SW_Flow_lib_PET.c +++ b/src/SW_Flow_lib_PET.c @@ -983,8 +983,7 @@ double svp(double T, double *slope_svp_to_t) { /** @brief Saturation vapor pressure for calculation of actual vapor pressure - Calculation is based on Allen et al. 2006 @cite allen2006AaFM which is - based on Majumdar et al. 1972 and updated by ASCE-EWRI 2005. + Implements equation 7 by Allen et al. (2005) @cite ASCE2005 @param[in] temp Daily mean, minimum, or maximum temperature or dewpoint temperature [C] @@ -998,56 +997,48 @@ double svp2(double temp) { /** @brief Calculate actual vapor pressure based on relative humidity and mean temperature - Calculation is based on Allen et al. 2006 @cite allen2006AaFM which is - based on Majumdar et al. 1972 and updated by ASCE-EWRI 2005. + Implements equation 7 and 14 by Allen et al. (2005) @cite ASCE2005 @param hurs Daily mean relative humidity [%] - @param tmean Daily mean air temperature [C] + @param meanTemp Daily mean air temperature [C] @return Calculated actual vapor pressure [kPa] */ -double actualVaporPressure1(double hurs, double tmean) { +double actualVaporPressure1(double hurs, double meanTemp) { // Allen et al. 2005 eqs 7 and 14 - return (hurs / 100.) * svp2(tmean); + return (hurs / 100.) * svp2(meanTemp); } /** - @brief Calculate actual vapor pressure based on temperature and relative humidity components (min/max) + @brief Calculate actual vapor pressure from daily minimum and maximum of + air temperature and relative humidity - Calculation is based on Allen et al. 2006 @cite allen2006AaFM which is - based on Majumdar et al. 1972 and updated by ASCE-EWRI 2005. + Implements equation 7 and 11 by Allen et al. (2005) @cite ASCE2005 - @param hursMax Daily maximum relative humidity [%] - @param hursMin Daily minimum relative humidity [%] + @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 hursMax, double hursMin, double maxTemp, double minTemp) { +double actualVaporPressure2(double maxHurs, double minHurs, double maxTemp, double minTemp) { // Allen et al. 2005 eqs 7 and 11 - double satVapPressureMax = svp2(maxTemp); - double satVapPressureMin = svp2(minTemp); - - double relHumVapPressMax = satVapPressureMin * (hursMax / 100); - double relHumVapPressMin = satVapPressureMax * (hursMin / 100); - - return (relHumVapPressMax + relHumVapPressMin) / 2; + return (actualVaporPressure1(minHurs, maxTemp) + actualVaporPressure1(maxHurs, minTemp)) / 2; } /** @brief Calculate actual vapor pressure based on dew point temperature - Calculation is based on Allen et al. 2006 @cite allen2006AaFM which is - based on Majumdar et al. 1972 and updated by ASCE-EWRI 2005. + Implements equation 7 and 8 by Allen et al. (2005) @cite ASCE2005 - @param tdps 2m dew point temperature [C] + @param dewpointTemp 2m dew point temperature [C] @return Calculated actual vapor pressure [kPa] */ -double actualVaporPressure3(double tdps) { +double actualVaporPressure3(double dewpointTemp) { // Allen et al. 2005 eqs 7 and 8 - return svp2(tdps); + return svp2(dewpointTemp); } From 852b83c181de21093aa614090a520a50f83bc311 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 12 Jan 2023 23:28:07 -0700 Subject: [PATCH 273/326] Modified SW_WEATHER and `SW_WTH_setup()` flags SW_WEATHER: - SW_WEATHER previously contained indices for weather input. After proving to be a bit troublesome when calculating their values, they have been replaced with an array of size MAX_INPUT_COLUMNS named "dailyInputIndices" * `_read_weather_hist()` now uses this array instead of using individual variables from SW_WEATHER * Renamed "use_relHumidityMonthly" to "use_humidityMonthly" in order to be general about the type of humidity `SW_WTH_setup()`: - Changed "componentFlag" to "currFlagHasMaxMin" along with any comments mentioning "component" to "max/min" to correctly specify what the flag represents - Function now uses "w->dailyInputIndices" to set index values instead of gathering all indices and setting them at the end of the function --- include/SW_Weather.h | 13 +++---- src/SW_Weather.c | 90 +++++++++++++++++++++----------------------- 2 files changed, 48 insertions(+), 55 deletions(-) diff --git a/include/SW_Weather.h b/include/SW_Weather.h index c0da02ccd..376364d2d 100644 --- a/include/SW_Weather.h +++ b/include/SW_Weather.h @@ -164,15 +164,12 @@ typedef struct { RealD snowRunoff, surfaceRunoff, surfaceRunon, soil_inf, surfaceAvg; RealD snow, snowmelt, snowloss, surfaceMax, surfaceMin; - Bool use_cloudCoverMonthly, use_windSpeedMonthly, use_relHumidityMonthly, - has_temp2, has_ppt, has_cloudCover, has_sfcWind, has_windComp, has_hurs, - has_hurs2, has_huss, has_tdps, has_vp, has_rsds; + Bool use_cloudCoverMonthly, use_windSpeedMonthly, use_humidityMonthly, + has_temp2, has_ppt, has_cloudCover, has_sfcWind, has_windComp, has_hurs, + has_hurs2, has_huss, has_tdps, has_vp, has_rsds; - int tempComp1_index, tempComp2_index, ppt_index, cloudCover_index, sfcWind_index, - windComp1_index, windComp2_index, hurs_index, hurs_comp1_index, hurs_comp2_index, - huss_index, tdps_index, vp_index, rsds_index; - - int n_input_forcings; // Number of input columns found in weath.YYYY + unsigned int dailyInputIndices[MAX_INPUT_COLUMNS], + n_input_forcings; // Number of input columns found in weath.YYYY /* This section is required for computing the output quantities. */ SW_WEATHER_OUTPUTS diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 866ce1128..0af2fdb3a 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -535,7 +535,7 @@ void readAllWeather( interpolate_monthlyValues(SW_Sky.windspeed, interpAsBase1, allHist[yearIndex]->windspeed_daily); } - if(SW_Weather.use_relHumidityMonthly) { + if(SW_Weather.use_humidityMonthly) { interpolate_monthlyValues(SW_Sky.r_humidity, interpAsBase1, allHist[yearIndex]->r_humidity_daily); } @@ -1185,18 +1185,16 @@ void SW_WTH_setup(void) { SW_WEATHER *w = &SW_Weather; const int nitems = 34; FILE *f; - int lineno = 0, month, x, columnNum, varArrIndex = 0; + int lineno = 0, month, x, currFlag, varArrIndex = 0; int tempCompIndex = 3, windCompIndex = 7, hursCompIndex = 9; - Bool componentFlag; + Bool currFlagHasMaxMin; RealF sppt, stmax, stmin; RealF sky, wind, rH, actVP, shortWaveRad; Bool *inputFlags[MAX_INPUT_COLUMNS] = {&w->use_cloudCoverMonthly, &w->use_windSpeedMonthly, - &w->use_relHumidityMonthly, &w->has_temp2, &w->has_ppt, &w->has_cloudCover, &w->has_sfcWind, + &w->use_humidityMonthly, &w->has_temp2, &w->has_ppt, &w->has_cloudCover, &w->has_sfcWind, &w->has_windComp, &w->has_hurs, &w->has_hurs2, &w->has_huss, &w->has_tdps, &w->has_vp, &w->has_rsds}; - int varIndices[MAX_INPUT_COLUMNS]; - MyFileName = SW_F_name(eWeather); f = OpenFile(MyFileName, "r"); @@ -1263,7 +1261,7 @@ void SW_WTH_setup(void) { break; case 7: - w->use_relHumidityMonthly = itob(atoi(inbuf)); + w->use_humidityMonthly = itob(atoi(inbuf)); break; case 8: @@ -1361,8 +1359,12 @@ void SW_WTH_setup(void) { w->has_sfcWind = (w->use_windSpeedMonthly) ? swFALSE : w->has_sfcWind; w->has_windComp = (w->use_windSpeedMonthly) ? swFALSE : w->has_windComp; - w->has_hurs = (w->use_relHumidityMonthly) ? swFALSE : w->has_hurs; - w->has_hurs2 = (w->use_relHumidityMonthly) ? swFALSE : w->has_hurs2; + if(w->use_humidityMonthly) { + w->has_hurs = swFALSE; + w->has_hurs2 = swFALSE; + w->has_huss = swFALSE; + w->has_vp = swFALSE; + } w->has_cloudCover = (w->use_cloudCoverMonthly) ? swFALSE : w->has_cloudCover; @@ -1378,34 +1380,36 @@ void SW_WTH_setup(void) { // Default n_input_forcings to 0 w->n_input_forcings = 0; - // Loop through MAX_INPUT_COLUMNS - for(columnNum = 3; columnNum < MAX_INPUT_COLUMNS; columnNum++) + // Loop through MAX_INPUT_COLUMNS starting at flag 3 + // The loop starts at 3 due to "inputFlags[]" containing monthly flags + // in the first three slots (indices 0, 1, and 2) + for(currFlag = 3; currFlag < MAX_INPUT_COLUMNS; currFlag++) { - varIndices[varArrIndex] = 0; + w->dailyInputIndices[varArrIndex] = 0; - // Check if current flag is in relation to component variables - if(&inputFlags[columnNum] == &inputFlags[tempCompIndex] || - &inputFlags[columnNum] == &inputFlags[windCompIndex] || - &inputFlags[columnNum] == &inputFlags[hursCompIndex]) { + // Check if current flag is in relation to max/min variables + if(&inputFlags[currFlag] == &inputFlags[tempCompIndex] || + &inputFlags[currFlag] == &inputFlags[windCompIndex] || + &inputFlags[currFlag] == &inputFlags[hursCompIndex]) { - // Set component flag - componentFlag = swTRUE; - varIndices[varArrIndex + 1] = 0; + // Set max/min flag + currFlagHasMaxMin = swTRUE; + w->dailyInputIndices[varArrIndex + 1] = 0; } else { - componentFlag = swFALSE; + currFlagHasMaxMin = swFALSE; } // Check if current flag is set - if(*inputFlags[columnNum]) { + if(*inputFlags[currFlag]) { // Set current index to "n_input_forcings" - varIndices[varArrIndex] = w->n_input_forcings; + w->dailyInputIndices[varArrIndex] = w->n_input_forcings; - // Check if flag is meant for components variables - if(componentFlag) { + // Check if flag is meant for max/min values + if(currFlagHasMaxMin) { // Set next index to n_input_focings + 1 - varIndices[varArrIndex + 1] = w->n_input_forcings + 1; + w->dailyInputIndices[varArrIndex + 1] = w->n_input_forcings + 1; // Increment "varArrIndex" by two varArrIndex += 2; @@ -1413,7 +1417,7 @@ void SW_WTH_setup(void) { // Increment "n_input_forcings" by two w->n_input_forcings += 2; } else { - // Otherwise, current flag is not meant for components + // Otherwise, current flag is not meant for max/min values // Increment "varArrIndex" by one varArrIndex++; @@ -1424,8 +1428,8 @@ void SW_WTH_setup(void) { } else { // Otherwise, flag was not set, deal with "varArrayIndex" - // Check if current flag is for component variables - if(componentFlag) { + // Check if current flag is for max/min variables + if(currFlagHasMaxMin) { // Increment "varArrIndex" by two varArrIndex += 2; } else { @@ -1434,15 +1438,6 @@ void SW_WTH_setup(void) { } } } - - // Set variable indices in SW_WEATHER - // Note: SW_WEATHER index being set is guaranteed to have the respective value - // at the given index of `varIndices` (e.g., w->windComp1_index will only be at index 5 in `varIndices`) - w->tempComp1_index = varIndices[0], w->tempComp2_index = varIndices[1], w->ppt_index = varIndices[2]; - w->cloudCover_index = varIndices[3], w->sfcWind_index = varIndices[4], w->windComp1_index = varIndices[5]; - w->windComp2_index = varIndices[6], w->hurs_index = varIndices[7], w->hurs_comp1_index = varIndices[8]; - w->hurs_comp2_index = varIndices[9], w->huss_index = varIndices[10], w->tdps_index = varIndices[11]; - w->vp_index = varIndices[12], w->rsds_index = varIndices[13]; } @@ -1514,18 +1509,19 @@ void _read_weather_hist( FILE *f; SW_WEATHER *weath = &SW_Weather; - int x, lineno = 0, doy; + unsigned int *dailyInputIndices = weath->dailyInputIndices; + unsigned int x, lineno = 0, doy; // TimeInt mon, j, k = 0; // RealF acc = 0.0; - RealF weathInput[MAX_INPUT_COLUMNS + 2]; - - int maxTempIndex = weath->tempComp1_index, minTempIndex = weath->tempComp2_index, - precipIndex = weath->ppt_index, cloudcovIndex = weath->cloudCover_index, - windSpeedIndex = weath->sfcWind_index, windComp1Index = weath->windComp1_index, - windComp2Index = weath->windComp2_index, relHumIndex = weath->huss_index, - vaporPressIndex = weath->vp_index, hursComp1Index = weath->hurs_comp1_index, - hursComp2Index = weath->hurs_comp2_index, hussIndex = weath->huss_index, - dewPointIndex = weath->tdps_index; + RealF weathInput[MAX_INPUT_COLUMNS]; + + int maxTempIndex = dailyInputIndices[0], minTempIndex = dailyInputIndices[1], + precipIndex = dailyInputIndices[2], cloudcovIndex = dailyInputIndices[3], + windSpeedIndex = dailyInputIndices[4], windEastIndex = dailyInputIndices[5], + windNorthIndex = dailyInputIndices[6], relHumIndex = dailyInputIndices[7], + vaporPressIndex = dailyInputIndices[12], hursMaxIndex = dailyInputIndices[8], + hursMinIndex = dailyInputIndices[9], hussIndex = dailyInputIndices[10], + dewPointIndex = dailyInputIndices[11]; double es, e, relHum, tempSlope, svpVal; From 93599538d31757f83cb7f4a4e038c359d5257b19 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 12 Jan 2023 23:45:55 -0700 Subject: [PATCH 274/326] Corrected weather calculation priorities - Calculation of actual vapor pressure within `readAllWeather()` has been moved to `finalizeAllWeather()` when only monthly values are being used for relative humidity * This is due to the fact that the weather generator can be used and average temperature will not be filled with reasonable values - Allowed relative humidity calculation through max/min values to have a higher priority than daily relative humidity input - Added the calculation of actual vapor pressure through `actualVaporPressure1()` if daily relative humidity is input through weath.YYYY - Actual vapor pressure now sets its daily value if it has been input through weath.YYYY - Conditions where monthly humidity and daily relative humidity are not interpolated/input/calculated and if daily actual vapor pressure or dewpoint temperature are input, relative humidity is recalculated --- src/SW_Weather.c | 85 ++++++++++++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 0af2fdb3a..eadba30ef 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -512,13 +512,13 @@ void readAllWeather( Bool use_weathergenerator_only, char weather_prefix[] ) { - unsigned int yearIndex, year, day; + 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 + SW_Weather.startYear; + year = yearIndex + startYear; // Set all daily weather values to missing _clear_hist_weather(allHist[yearIndex]); @@ -543,21 +543,11 @@ void readAllWeather( if (!use_weathergenerator_only) { _read_weather_hist( - startYear + yearIndex, + year, allHist[yearIndex], weather_prefix ); } - - // Check to see if actual vapor pressure needs to be calculated - if(!SW_Weather.has_vp && SW_Weather.use_relHumidityMonthly) { - - for(day = 0; day < MAX_DAYS; day++) { - allHist[yearIndex]->actualVaporPressure[day] = - actualVaporPressure1(allHist[yearIndex]->r_humidity_daily[day], - allHist[yearIndex]->temp_avg[day]); - } - } } } @@ -571,6 +561,9 @@ void readAllWeather( (the latter also handles (re-)allocation). */ void finalizeAllWeather(SW_WEATHER *w) { + + unsigned int day, yearIndex; + // Impute missing values generateMissingWeather( w->allHist, @@ -580,6 +573,17 @@ void finalizeAllWeather(SW_WEATHER *w) { 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++) { + 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( @@ -1545,7 +1549,7 @@ void _read_weather_hist( CloseFile(&f); LogError(logfp, LOGFATAL, "%s : Incomplete record %d (doy=%d).", fname, lineno, doy); } - if (x > 15) { + if (x > MAX_INPUT_COLUMNS + 1) { CloseFile(&f); LogError(logfp, LOGFATAL, "%s : Too many values in record %d (doy=%d).", fname, lineno, doy); } @@ -1576,25 +1580,19 @@ void _read_weather_hist( yearWeather->windspeed_daily[doy] = weathInput[windSpeedIndex]; } else if(weath->has_windComp) { - yearWeather->windspeed_daily[doy] = sqrt(squared(weathInput[windComp1Index]) + - squared(weathInput[windComp2Index])); + yearWeather->windspeed_daily[doy] = sqrt(squared(weathInput[windEastIndex]) + + squared(weathInput[windNorthIndex])); } } - if(!weath->use_relHumidityMonthly) { - if(weath->has_hurs) { + if(!weath->use_humidityMonthly) { + if(weath->has_hurs2) { + yearWeather->r_humidity_daily[doy] = (weathInput[hursMaxIndex] + + weathInput[hursMinIndex]) / 2; + } else if(weath->has_hurs) { yearWeather->r_humidity_daily[doy] = weathInput[relHumIndex]; - } else if(weath->has_vp) { - svpVal = svp(yearWeather->temp_avg[doy], &tempSlope); - - yearWeather->r_humidity_daily[doy] = weathInput[vaporPressIndex] / svpVal; - - } else if(weath->has_hurs2) { - yearWeather->r_humidity_daily[doy] = (weathInput[hursComp1Index] + - weathInput[hursComp2Index]) / 2; - } else if(weath->has_huss) { // Specific Humidity (Bolton 1980) es = (6.112 * exp(17.67 * yearWeather->temp_avg[doy]) / @@ -1612,27 +1610,36 @@ void _read_weather_hist( } if(!weath->has_vp) { - if(SW_Weather.has_tdps) { + if(weath->has_tdps) { yearWeather->actualVaporPressure[doy] = actualVaporPressure3(weathInput[dewPointIndex]); - // If hurs was calculated, replace previous calculation with this one - if(!SW_Weather.use_relHumidityMonthly && !SW_Weather.has_hurs) { - svpVal = svp(yearWeather->temp_avg[doy], &tempSlope); - - yearWeather->actualVaporPressure[doy] = - yearWeather->actualVaporPressure[doy] / svpVal; - } - - } else if(SW_Weather.has_hurs2 && SW_Weather.has_temp2) { + } else if(weath->has_hurs2 && weath->has_temp2) { yearWeather->actualVaporPressure[doy] = - actualVaporPressure2(weathInput[hursComp2Index], - weathInput[hursComp1Index], + actualVaporPressure2(weathInput[hursMaxIndex], + weathInput[hursMinIndex], weathInput[maxTempIndex], weathInput[minTempIndex]); + } else if(weath->has_hurs) { + yearWeather->actualVaporPressure[doy] = + actualVaporPressure1(yearWeather->r_humidity_daily[doy], + yearWeather->temp_avg[doy]); } + } else { + yearWeather->actualVaporPressure[doy] = weathInput[vaporPressIndex]; } + // Check if a better calculation of relative humisity is available + // (using dewpoint temperature or the daily actual vapor pressure input value) + if((!weath->use_humidityMonthly && missing(yearWeather->r_humidity_daily[doy])) + && (weath->has_vp || weath->has_tdps)) { + + svpVal = svp(yearWeather->temp_avg[doy], &tempSlope); + + yearWeather->r_humidity_daily[doy] = + yearWeather->actualVaporPressure[doy] / svpVal; + } + // Calculate annual average temperature based on historical input, i.e., // the `temp_year_avg` calculated here is prospective and unsuitable when From dad0409044246f325de91e72f5492f2e5e872bd0 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 12 Jan 2023 23:52:24 -0700 Subject: [PATCH 275/326] `Weather gen. method = 1`: Generates all weather in SW_WEATHER - `generateMissingWeather()` previously only generated max/min temperature and precipitation when the weather generator method was set to 1 in weathsetup.in and now covers: * Cloud cover, relative humidity, shortwave radiation, actual vapor pressure, and wind speed - Added new variables to carry the previous day's information to the current day along with new flags if a value is missing ("missing_***") --- src/SW_Weather.c | 52 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index eadba30ef..5cbff6742 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -781,9 +781,12 @@ void generateMissingWeather( int year; unsigned int yearIndex, numDaysYear, day, iMissing; - double yesterdayPPT = 0., yesterdayMin = 0., yesterdayMax = 0.; + double yesterdayPPT = 0., yesterdayTempMin = 0., yesterdayTempMax = 0., + yesterdayCloudCov = 0., yesterdayWindSpeed = 0., yesterdayRelHum = 0., + yesterdayShortWR = 0., yesterdayActVP = 0.; - Bool any_missing, missing_Tmax, missing_Tmin, missing_PPT; + Bool any_missing, missing_Tmax, missing_Tmin, missing_PPT, missing_CloudCov, + missing_WindSpeed, missing_RelHum, missing_ShortWR, missing_ActVP; // Pass through method: return early @@ -811,8 +814,15 @@ void generateMissingWeather( 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]); + 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); + 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 @@ -828,15 +838,36 @@ void generateMissingWeather( ); } else if (method == 1) { - // LOCF (temp) + 0 (PPT) + // LOCF (temp, cloud cover, wind speed, relative humidity, + // shortwave radiation, and actual vapor pressure) + 0 (PPT) allHist[yearIndex]->temp_max[day] = missing_Tmax ? - yesterdayMax : + yesterdayTempMax : allHist[yearIndex]->temp_max[day]; allHist[yearIndex]->temp_min[day] = missing_Tmin ? - yesterdayMin : + 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]; @@ -865,8 +896,13 @@ void generateMissingWeather( } yesterdayPPT = allHist[yearIndex]->ppt[day]; - yesterdayMax = allHist[yearIndex]->temp_max[day]; - yesterdayMin = allHist[yearIndex]->temp_min[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]; } } } From a5645d9d2f74b3f2f60b477358c63d4d0ce8919d Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Thu, 12 Jan 2023 23:53:25 -0700 Subject: [PATCH 276/326] Descriptive `interpolate_monthValues()` documentation (base1/0) --- src/Times.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Times.c b/src/Times.c index 5a1acfa5f..f7bd4e687 100644 --- a/src/Times.c +++ b/src/Times.c @@ -182,14 +182,10 @@ Bool isleapyear(const TimeInt year) { @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 Aside from cloud cover, relative humidity, and wind speed in `allHist` in SW_WEATHER, 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 When the function encounters cloud cover, relative humidity, or wind speed in `allHist` in SW_WEATHER, - `dailyValues` will be defaulted to base0 to be consistent with minimum/maximum temperature and - precipitation in `allHist`. The function will know when one of the three cases is met by the parameter - "interpAsBase1". + @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[], Bool interpAsBase1, double dailyValues[]) { From 28baf26cb2fcd34f6dc10750b674499e08cdd153 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 13 Jan 2023 00:02:06 -0700 Subject: [PATCH 277/326] Flow Lib tests: Backtracked to use `actualVaporPressure1()` - Previously, tests that call `solar_radiation()` were changed to use hard-coded actual vapor pressure values - To make it flexible, instead of hard-coded values, calls to `actualVaporPressure1()` are used and called every iteration of a loop if needed - Added "rel_humidity" and "air_temp_mean" back with are referenced to source --- tests/gtests/test_SW_Flow_Lib_PET.cc | 29 +++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/tests/gtests/test_SW_Flow_Lib_PET.cc b/tests/gtests/test_SW_Flow_Lib_PET.cc index cd82894f9..7378b6b94 100644 --- a/tests/gtests/test_SW_Flow_Lib_PET.cc +++ b/tests/gtests/test_SW_Flow_Lib_PET.cc @@ -731,13 +731,20 @@ namespace // 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 + 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) + 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}, // Actual vapor pressure (kPa) - actual_vap_pressure[12] = - {0.232164, 0.278781, 0.442500, 0.682698, 1.024887, 1.500820, 1.843139, 1.761336, - 1.343817, 0.863361, 0.538767, 0.313456}; + actual_vap_pressure; // Duffie & Beckman 2013: Example 2.19.1 for (k = 0; k < 12; k++) { + + actual_vap_pressure = actualVaporPressure1(rel_humidity[k], air_temp_mean[k]); + H_gt = solar_radiation( doys_Table1_6_1[k], 43. * deg_to_rad, // latitude @@ -746,7 +753,7 @@ namespace 0., // aspect albedo[k], cloud_cover[k], - actual_vap_pressure[k], + actual_vap_pressure, &H_oh, &H_ot, &H_gh @@ -821,7 +828,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] @@ -832,17 +840,16 @@ namespace expected_pet_avgtemps[] = { 0.0100, 0.0184, 0.0346, 0.0576, 0.0896, 0.1290, 0.1867, 0.2736, 0.4027, 0.5890 - }, - // Actual vapor pressure input - e_a_input[] = {0.030606, 0.076018, 0.174284, 0.372588, 0.749057, 1.426352, - 2.588270, 4.499124, 7.525423, 12.159202}; + }; for (i = 0; i < 10; i++) { + actual_vap_pressure = actualVaporPressure1(RH, avgtemps[i]); + H_gt = solar_radiation( doy, lat, elev, slope0, aspect, reflec, - cloudcov, e_a_input[i], + cloudcov, actual_vap_pressure, &H_oh, &H_ot, &H_gh ); @@ -859,7 +866,7 @@ namespace // Expected PET expected_pet_lats[] = {0.1550, 0.4360, 0.3597, 0.1216, 0.0421}; - double e_a = 1.932344; + double e_a = actualVaporPressure1(RH, temp); for (i = 0; i < 5; i++) { From 5d2267077d82828e1356113b9fdc10d050ddf8f9 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 13 Jan 2023 00:05:58 -0700 Subject: [PATCH 278/326] New base1 interpolation test in `test_Times.cc` - Previously, TimesTest.InterpolateMonthlyValues within `test_Times.cc` was changed to make "cloudcov_daily" base0 - A new test was added to make sure base1 interpolation works properly using "cloudcov_daily" * Since the placement of this test is after a base0 interpolation, the first element within "cloudcov_daily" needs to be zeroed --- tests/gtests/test_Times.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/gtests/test_Times.cc b/tests/gtests/test_Times.cc index ee80ad4a6..b1dadb537 100644 --- a/tests/gtests/test_Times.cc +++ b/tests/gtests/test_Times.cc @@ -97,6 +97,14 @@ namespace{ 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) From 90cfd0ab92927c8d90dacf7dd44648cc7575dba9 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 13 Jan 2023 00:11:41 -0700 Subject: [PATCH 279/326] New weather test: Correct use of monthly interpolation values - With the addition of new daily inputs which can replace the daily values from monthly interpolation, it is a good idea to make sure interpolated values are being used when asked for - This test includes the setting of humidity, wind speed, and cloud cover monthly flags, "has_hurs" has been set to swTRUE for a bit more complex of a test and try to get the program to do that wrong thing (use daily hurs values) - Commented out any unused variables for the time being to get "test_severe" to run --- tests/gtests/test_SW_Weather.cc | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/tests/gtests/test_SW_Weather.cc b/tests/gtests/test_SW_Weather.cc index fd2701cf9..851bb84c2 100644 --- a/tests/gtests/test_SW_Weather.cc +++ b/tests/gtests/test_SW_Weather.cc @@ -693,17 +693,30 @@ namespace { */ // NOTE: capture behaviors and don't be comprehensive (more than likely keep one `EXPECT_NEAR()`) + SW_SKY *sky = &SW_Sky; // Initialize any variables - int day, year = 1980, yearIndex = 0; + //int day, year = 1980 + int yearIndex = 0, midJanDay = 14; /* Test if monthly values are not being used */ - /* Only test if monthly values are being used for January in relative humidity */ + SW_WTH_setup(); + + SW_Weather.use_humidityMonthly = swTRUE; + SW_Weather.use_windSpeedMonthly = swTRUE; + SW_Weather.use_cloudCoverMonthly = swTRUE; + SW_Weather.has_hurs = swTRUE; // Read in all weather + SW_WTH_read(); + + /* Only test if monthly values are being used for January in relative humidity */ - // Test the 15th day of January in year 1980 and see if it's not equal to 61.0 + // Test the middle of January in year 1980 and see if it's not equal to SW_Sky.r_humidity[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(SW_Weather.allHist[yearIndex]->r_humidity_daily[midJanDay], sky->r_humidity[0], tol6); /* Test correct priority is being given to input values */ @@ -731,8 +744,8 @@ namespace { */ // Initialize any variables - char weathPrefix[] = "weath"; - TimeInt year = 1980; + //char weathPrefix[] = "weath"; + //TimeInt year = 1980; double originVal; /* Not the same number of flags as columns */ @@ -741,8 +754,8 @@ namespace { // not the columns being read in allocateAllWeather(&SW_Weather); - // SW_Weather.n_input_forcings = 7; - SW_Weather.n_years = 1; + //SW_Weather.n_input_forcings = 0; + //SW_Weather.n_years = 1; // Run death test // EXPECT_DEATH_IF_SUPPORTED( From 522bc470034a2c38c0c8c416c80fe1b73f0bbcde Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 13 Jan 2023 10:10:40 -0500 Subject: [PATCH 280/326] Fix previous merge commit - I made an error resolving merge conflicts with the previous commit 8a76c82b2297f4306d42b502f0cc72bafc15ece8 "Merge branch 'master' (v6.7.0) into release/devel_v7.0.0" -> `SW_OUT_read_onekey()` gained argument `sizeof_msg` --- include/SW_Output.h | 2 +- src/SW_Output.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/SW_Output.h b/include/SW_Output.h index 4438427f5..8b1917ac1 100644 --- a/include/SW_Output.h +++ b/include/SW_Output.h @@ -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/src/SW_Output.c b/src/SW_Output.c index f13f97d2a..363621667 100644 --- a/src/SW_Output.c +++ b/src/SW_Output.c @@ -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 From 8831fdef2aabf7e8c7af4565eec6d744d92638f4 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 17 Jan 2023 00:15:39 -0700 Subject: [PATCH 281/326] Input flag array & new `#define` for input flag/indices arrays - New array: "dailyInputFlags" in SW_WEATHER * Replaces all daily flags previously in SW_WEATHER but is not used for monthly input flags * Of size "MAX_INPUT_COLUMNS" due to splitting previously max/min flags into two e.g., has_temp2 -> has_tempMax and has_tempMin (ignoring names since they are not needed in an array) - Created new #defines in SW_Defines.h for both input flags and calculated indices arrays, where the same value corresponds to the same variable in each SW_WTH_setup(): - Makes use of new #define index values, i.e.: * The setting of input flags now uses "dailyInputFlags[TEMP_MAX]" and so on * If-statements, wherever needed, now use "dailyInputFlags[]" instead of targeting a specific variable within SW_WEATHER * Calculation of input indices has been intensely simplified due to no longer needing to accommodate for flags handling both max/min - Testing and actions for turning off flags due to monthly flag priority have been moved to the end of the function -- after index calculation * The reason is that if daily flags are turned off before index calculation, the incorrect number of "n_input_forcings" could result and unexpectedly crash the program SW_WTH_new_day(): - Added a note as to why shortwave radiation is treated differently from the other variables when testing for a missing daily value - Swapped "w->has_rsds" with "w->dailyInputFlags[SHORT_WR]" to use new #define "SHORT_WR" --- include/SW_Defines.h | 16 +++++ include/SW_Weather.h | 5 +- src/SW_Weather.c | 168 ++++++++++++++++++++----------------------- 3 files changed, 97 insertions(+), 92 deletions(-) diff --git a/include/SW_Defines.h b/include/SW_Defines.h index 250b9f0b7..f6df46906 100644 --- a/include/SW_Defines.h +++ b/include/SW_Defines.h @@ -119,6 +119,22 @@ extern "C" { #define SW_FORBS 2 #define SW_GRASS 3 +/* Indices to daily input flags/indices (dailyInputFlags & dailyInputIndices in SW_WEATHER)*/ +#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/include/SW_Weather.h b/include/SW_Weather.h index 376364d2d..8d4d718b5 100644 --- a/include/SW_Weather.h +++ b/include/SW_Weather.h @@ -164,9 +164,8 @@ typedef struct { RealD snowRunoff, surfaceRunoff, surfaceRunon, soil_inf, surfaceAvg; RealD snow, snowmelt, snowloss, surfaceMax, surfaceMin; - Bool use_cloudCoverMonthly, use_windSpeedMonthly, use_humidityMonthly, - has_temp2, has_ppt, has_cloudCover, has_sfcWind, has_windComp, has_hurs, - has_hurs2, has_huss, has_tdps, has_vp, has_rsds; + 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 diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 5cbff6742..209113ddd 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -1176,13 +1176,19 @@ void SW_WTH_new_day(void) { */ /* get the daily weather from allHist */ + /* + Note: Shortwave radiation needs to check if it was input, this is due to the + fact that shortwave radiation is not necessary for any calculations in `_read_weather_hist()` + and does not get calculated within `_read_weather_hist()` and is thus expected to + hold "SW_MISSING" values if daily input is not provided. + */ if ( missing(w->allHist[yearIndex]->temp_avg[day]) || missing(w->allHist[yearIndex]->ppt[day]) || missing(w->allHist[yearIndex]->cloudcov_daily[day]) || missing(w->allHist[yearIndex]->windspeed_daily[day]) || missing(w->allHist[yearIndex]->r_humidity_daily[day]) || - (missing(w->allHist[yearIndex]->shortWaveRad[day]) && w->has_rsds) || + (missing(w->allHist[yearIndex]->shortWaveRad[day]) && w->dailyInputFlags[SHORT_WR]) || missing(w->allHist[yearIndex]->actualVaporPressure[day]) ) { LogError( @@ -1225,15 +1231,12 @@ void SW_WTH_setup(void) { SW_WEATHER *w = &SW_Weather; const int nitems = 34; FILE *f; - int lineno = 0, month, x, currFlag, varArrIndex = 0; - int tempCompIndex = 3, windCompIndex = 7, hursCompIndex = 9; - Bool currFlagHasMaxMin; + int lineno = 0, month, x, currFlag; + Bool monthlyFlagPrioritized = swFALSE; RealF sppt, stmax, stmin; RealF sky, wind, rH, actVP, shortWaveRad; - Bool *inputFlags[MAX_INPUT_COLUMNS] = {&w->use_cloudCoverMonthly, &w->use_windSpeedMonthly, - &w->use_humidityMonthly, &w->has_temp2, &w->has_ppt, &w->has_cloudCover, &w->has_sfcWind, - &w->has_windComp, &w->has_hurs, &w->has_hurs2, &w->has_huss, &w->has_tdps, &w->has_vp, &w->has_rsds}; + Bool *dailyInputFlags = w->dailyInputFlags; MyFileName = SW_F_name(eWeather); f = OpenFile(MyFileName, "r"); @@ -1305,65 +1308,59 @@ void SW_WTH_setup(void) { break; case 8: - w->has_temp2 = itob(atoi(inbuf)); + w->dailyInputFlags[TEMP_MAX] = itob(atoi(inbuf)); break; case 9: - if(w->has_temp2) { - w->has_temp2 = itob(atoi(inbuf)); - } + w->dailyInputFlags[TEMP_MIN] = itob(atoi(inbuf)); break; case 10: - w->has_ppt = itob(atoi(inbuf)); + w->dailyInputFlags[PPT] = itob(atoi(inbuf)); break; case 11: - w->has_cloudCover = itob(atoi(inbuf)); + w->dailyInputFlags[CLOUD_COV] = itob(atoi(inbuf)); break; case 12: - w->has_sfcWind = itob(atoi(inbuf)); + w->dailyInputFlags[WIND_SPEED] = itob(atoi(inbuf)); break; case 13: - w->has_windComp = itob(atoi(inbuf)); + w->dailyInputFlags[WIND_EAST] = itob(atoi(inbuf)); break; case 14: - if(w->has_windComp) { - w->has_windComp = itob(atoi(inbuf)); - } + w->dailyInputFlags[WIND_NORTH] = itob(atoi(inbuf)); break; case 15: - w->has_hurs = itob(atoi(inbuf)); + w->dailyInputFlags[REL_HUMID] = itob(atoi(inbuf)); break; case 16: - w->has_hurs2 = itob(atoi(inbuf)); + w->dailyInputFlags[REL_HUMID_MAX] = itob(atoi(inbuf)); break; case 17: - if(w->has_hurs2) { - w->has_hurs2 = itob(atoi(inbuf)); - } + w->dailyInputFlags[REL_HUMID_MIN] = itob(atoi(inbuf)); break; case 18: - w->has_huss = itob(atoi(inbuf)); + w->dailyInputFlags[SPEC_HUMID] = itob(atoi(inbuf)); break; case 19: - w->has_tdps = itob(atoi(inbuf)); + w->dailyInputFlags[TEMP_DEWPOINT] = itob(atoi(inbuf)); break; case 20: - w->has_vp = itob(atoi(inbuf)); + w->dailyInputFlags[ACTUAL_VP] = itob(atoi(inbuf)); break; case 21: - w->has_rsds = itob(atoi(inbuf)); + w->dailyInputFlags[SHORT_WR] = itob(atoi(inbuf)); break; default: @@ -1395,19 +1392,6 @@ void SW_WTH_setup(void) { lineno++; } - // Check if monthly flags have been chosen to override daily flags - w->has_sfcWind = (w->use_windSpeedMonthly) ? swFALSE : w->has_sfcWind; - w->has_windComp = (w->use_windSpeedMonthly) ? swFALSE : w->has_windComp; - - if(w->use_humidityMonthly) { - w->has_hurs = swFALSE; - w->has_hurs2 = swFALSE; - w->has_huss = swFALSE; - w->has_vp = swFALSE; - } - - w->has_cloudCover = (w->use_cloudCoverMonthly) ? swFALSE : w->has_cloudCover; - SW_WeatherPrefix(w->name_prefix); CloseFile(&f); @@ -1420,63 +1404,69 @@ void SW_WTH_setup(void) { // Default n_input_forcings to 0 w->n_input_forcings = 0; - // Loop through MAX_INPUT_COLUMNS starting at flag 3 - // The loop starts at 3 due to "inputFlags[]" containing monthly flags - // in the first three slots (indices 0, 1, and 2) - for(currFlag = 3; currFlag < MAX_INPUT_COLUMNS; currFlag++) + // Loop through MAX_INPUT_COLUMNS (# of input flags) + for(currFlag = 0; currFlag < MAX_INPUT_COLUMNS; currFlag++) { - w->dailyInputIndices[varArrIndex] = 0; - - // Check if current flag is in relation to max/min variables - if(&inputFlags[currFlag] == &inputFlags[tempCompIndex] || - &inputFlags[currFlag] == &inputFlags[windCompIndex] || - &inputFlags[currFlag] == &inputFlags[hursCompIndex]) { - - // Set max/min flag - currFlagHasMaxMin = swTRUE; - w->dailyInputIndices[varArrIndex + 1] = 0; - } else { - currFlagHasMaxMin = swFALSE; - } + // Default the current index to zero + w->dailyInputIndices[currFlag] = 0; // Check if current flag is set - if(*inputFlags[currFlag]) { - - // Set current index to "n_input_forcings" - w->dailyInputIndices[varArrIndex] = w->n_input_forcings; - - // Check if flag is meant for max/min values - if(currFlagHasMaxMin) { + if(dailyInputFlags[currFlag]) { + // Set current index to current number of "n_input_forcings" + // which is the current number of flags found + w->dailyInputIndices[currFlag] = w->n_input_forcings; - // Set next index to n_input_focings + 1 - w->dailyInputIndices[varArrIndex + 1] = w->n_input_forcings + 1; - - // Increment "varArrIndex" by two - varArrIndex += 2; + // Increment "n_input_forcings" + w->n_input_forcings++; + } + } - // Increment "n_input_forcings" by two - w->n_input_forcings += 2; - } else { - // Otherwise, current flag is not meant for max/min values + /* + 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 + // Asude from checking for a monthly flag, we must make sure we have + // daily flags to turn off instead of turning off flags that are already off + if(w->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; + } - // Increment "varArrIndex" by one - varArrIndex++; + if(w->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; + } + } - // Increment "n_input_forcings" by one - w->n_input_forcings++; - } - } else { - // Otherwise, flag was not set, deal with "varArrayIndex" + if(w->use_cloudCoverMonthly && dailyInputFlags[CLOUD_COV]) { + dailyInputFlags[CLOUD_COV] = swFALSE; + monthlyFlagPrioritized = swTRUE; + } - // Check if current flag is for max/min variables - if(currFlagHasMaxMin) { - // Increment "varArrIndex" by two - varArrIndex += 2; - } else { - // Increment "varArrIndex" by one - varArrIndex++; - } - } + // 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."); } } From 89467195b020fea63f019ddfca2b7997c10646b9 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 17 Jan 2023 00:21:47 -0700 Subject: [PATCH 282/326] `checkAllWeather()` ignores missing - When checking all weather, `checkAllWeather()` now ignores values that are missing * If it were to keep inspecting possible missing values, undesired crashes may occur, we only want to test values that have been calculated/input --- src/SW_Weather.c | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 209113ddd..a14d39cec 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -938,7 +938,8 @@ void checkAllWeather(SW_WEATHER *weather) { dailyMinTemp = weathHist[year]->temp_min[doy]; // Check if minimum temp greater than maximum temp - if(dailyMinTemp > dailyMaxTemp) { + if(!missing(dailyMaxTemp) && !missing(dailyMinTemp) && + dailyMinTemp > dailyMaxTemp) { // Fail LogError(logfp, LOGFATAL, "Daily input value for minumum temperature" @@ -947,8 +948,11 @@ void checkAllWeather(SW_WEATHER *weather) { } // Otherwise, check if maximum or minimum temp, or // dew point temp is not [-100, 100] - else if((dailyMinTemp > 100. || dailyMinTemp < -100.) || - (dailyMaxTemp > 100. || dailyMaxTemp < -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 " @@ -956,15 +960,18 @@ void checkAllWeather(SW_WEATHER *weather) { doy, year + weather->startYear); } // Otherwise, check if precipitation is less than 0cm - else if(weathHist[year]->ppt[doy] < 0) { + 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(weathHist[year]->r_humidity_daily[doy] < 0. || - weathHist[year]->r_humidity_daily[doy] > 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" @@ -973,8 +980,11 @@ void checkAllWeather(SW_WEATHER *weather) { } // Otherwise, check if cloud cover was input and // if the value is less than 0% or greater than 100% - else if(weathHist[year]->cloudcov_daily[doy] < 0. || - weathHist[year]->cloudcov_daily[doy] > 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" @@ -982,15 +992,17 @@ void checkAllWeather(SW_WEATHER *weather) { weathHist[year]->cloudcov_daily[doy]); } // Otherwise, check if wind speed is less than 0 m/s - else if(weathHist[year]->windspeed_daily[doy] < 0.) { + 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(weathHist[year]->shortWaveRad[doy] < 0.) { + // 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." @@ -998,7 +1010,8 @@ void checkAllWeather(SW_WEATHER *weather) { weathHist[year]->shortWaveRad[doy], doy + 1, year + weather->startYear); } // Otherwise, check if actual vapor pressure is less than 0 kPa - else if(weathHist[year]->actualVaporPressure[doy] < 0.) { + 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." From c0f6a3e63990fc9694124dad556f9c2bc99ba7b3 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 17 Jan 2023 00:33:44 -0700 Subject: [PATCH 283/326] `readAllWeather()` & `_read_weather_hist()`: removed globals - As stated in a previous commit: https://github.com/DrylandEcology/SOILWAT2/commit/679226f03acfb8993b09fca3659ea2e141cce3a2, globals in these functions should not exist `readAllWeather()`: - Gains a few new parameters: * Three monthly flags - specify if the respective information to the flag will be from monthly or daily input values (humidity, cloud cover, and wind speed) * "n_input_forcings", "dailyInputIndices", and "dailyInputFlags" * Three monthly value arrays - holding monthly values respective to their monthly flags with the possibility to be interpolated into daily values - The function uses these parameters instead of SW_WEATHER and SW_SKY globals - Updated function documentation to include these new parameters `_read_weather_hist()`: - Gains parameters: * "n_input_forcings", "dailyInputIndices", and "dailyInputFlags" * Three monthly flags - specify if the respective information to the flag will be from monthly or daily input values (humidity, cloud cover, and wind speed) - Updated function documentation to include these new parameters --- include/SW_Weather.h | 19 +++++++++-- src/SW_Weather.c | 78 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 85 insertions(+), 12 deletions(-) diff --git a/include/SW_Weather.h b/include/SW_Weather.h index 8d4d718b5..12f04499e 100644 --- a/include/SW_Weather.h +++ b/include/SW_Weather.h @@ -216,14 +216,29 @@ void deallocateClimateStructs(SW_CLIMATE_YEARLY *climateOutput, void _read_weather_hist( TimeInt year, SW_WEATHER_HIST *yearWeather, - char weather_prefix[] + char weather_prefix[], + unsigned int n_input_forcings, + unsigned int *dailyInputIndices, + Bool *dailyInputFlags, + Bool use_cloudCoverMonthly, + Bool use_windSpeedMonthly, + Bool use_humidityMonthly ); void readAllWeather( SW_WEATHER_HIST **allHist, int startYear, unsigned int n_years, Bool use_weathergenerator_only, - char weather_prefix[] + 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); diff --git a/src/SW_Weather.c b/src/SW_Weather.c index a14d39cec..480bcee1b 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -503,6 +503,23 @@ void driestQtrSouthAdjMonYears(int month, int *adjustedYearZero, int *adjustedYe @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 + monthly values for cloud cover read-in from disk + @param[in] use_humidityMonthly A boolean; if `swTRUE`, function will interpolate + monthly values for humidity read-in from disk + @param[in] use_windSpeedMonthly A boolean; if `swTRUE`, function will interpolate + monthly values for wind speed read-in from disk + @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( @@ -510,7 +527,16 @@ void readAllWeather( int startYear, unsigned int n_years, Bool use_weathergenerator_only, - char weather_prefix[] + 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; @@ -527,16 +553,16 @@ void readAllWeather( // cloud cover, wind speed, and relative humidity if necessary Time_new_year(year); - if(SW_Weather.use_cloudCoverMonthly) { - interpolate_monthlyValues(SW_Sky.cloudcov, interpAsBase1, allHist[yearIndex]->cloudcov_daily); + if(use_cloudCoverMonthly) { + interpolate_monthlyValues(cloudcov, interpAsBase1, allHist[yearIndex]->cloudcov_daily); } - if(SW_Weather.use_windSpeedMonthly) { - interpolate_monthlyValues(SW_Sky.windspeed, interpAsBase1, allHist[yearIndex]->windspeed_daily); + if(use_humidityMonthly) { + interpolate_monthlyValues(r_humidity, interpAsBase1, allHist[yearIndex]->r_humidity_daily); } - if(SW_Weather.use_humidityMonthly) { - interpolate_monthlyValues(SW_Sky.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 @@ -545,7 +571,13 @@ void readAllWeather( _read_weather_hist( year, allHist[yearIndex], - weather_prefix + weather_prefix, + n_input_forcings, + dailyInputIndices, + dailyInputFlags, + use_cloudCoverMonthly, + use_windSpeedMonthly, + use_humidityMonthly ); } } @@ -1510,7 +1542,16 @@ void SW_WTH_read(void) { SW_Weather.startYear, SW_Weather.n_years, SW_Weather.use_weathergenerator_only, - SW_Weather.name_prefix + SW_Weather.name_prefix, + SW_Weather.use_cloudCoverMonthly, + SW_Weather.use_windSpeedMonthly, + SW_Weather.use_humidityMonthly, + SW_Weather.n_input_forcings, + SW_Weather.dailyInputIndices, + SW_Weather.dailyInputFlags, + SW_Sky.cloudcov, + SW_Sky.windspeed, + SW_Sky.r_humidity ); } @@ -1529,11 +1570,28 @@ void SW_WTH_read(void) { @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 + @param use_cloudCoverMonthly A boolean; specifying whether or not cloud cover monthly values + are to be used instead of daily input values + @param use_windSpeedMonthly A boolean; specifying whether or not wind speed monthly values + are to be used instead of daily input values + @param use_humidityMonthly A boolean; specifying whether or not humidity monthly values + are to be used instead of daily input values */ void _read_weather_hist( TimeInt year, SW_WEATHER_HIST *yearWeather, - char weather_prefix[] + char weather_prefix[], + unsigned int n_input_forcings, + unsigned int *dailyInputIndices, + Bool *dailyInputFlags, + Bool use_cloudCoverMonthly, + Bool use_windSpeedMonthly, + Bool use_humidityMonthly ) { /* =================================================== */ /* Read the historical (measured) weather files. From 537427c5e13d0770bf955a249ea506245115db9f Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 17 Jan 2023 00:43:21 -0700 Subject: [PATCH 284/326] `_read_weather_hist()`: Uses new parameters/adjusted calculations - Removed use of SW_WEATHER global - Function makes use of its new "dailyInputFlags" parameter by replacing "weathInput[...]" with "weathInput[dailyInputFlags[...]]" and removes the need for individual array indices in the function (e.g., maxTempIndex) - Actual Vapor pressure is now calculated when specific humidity (huss) is input as a daily value - Calculation of actual vapor pressure has been moved inside the check for "use_humidityMonthly" to prevent calculation of all humidity-related values if monthly humidity values are to be used - If any missing weather values are used in a calculation of actual vapor pressure or relative humidity, the daily value of actual vapor pressure and/or relative humidity is set to missing --- src/SW_Weather.c | 129 ++++++++++++++++++++++++++--------------------- 1 file changed, 71 insertions(+), 58 deletions(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 480bcee1b..a6a9b367a 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -1609,20 +1609,14 @@ void _read_weather_hist( */ FILE *f; - SW_WEATHER *weath = &SW_Weather; - unsigned int *dailyInputIndices = weath->dailyInputIndices; - unsigned int x, lineno = 0, doy; + unsigned int x, lineno = 0; + int doy; // TimeInt mon, j, k = 0; // RealF acc = 0.0; RealF weathInput[MAX_INPUT_COLUMNS]; - int maxTempIndex = dailyInputIndices[0], minTempIndex = dailyInputIndices[1], - precipIndex = dailyInputIndices[2], cloudcovIndex = dailyInputIndices[3], - windSpeedIndex = dailyInputIndices[4], windEastIndex = dailyInputIndices[5], - windNorthIndex = dailyInputIndices[6], relHumIndex = dailyInputIndices[7], - vaporPressIndex = dailyInputIndices[12], hursMaxIndex = dailyInputIndices[8], - hursMinIndex = dailyInputIndices[9], hussIndex = dailyInputIndices[10], - dewPointIndex = dailyInputIndices[11]; + Bool hasMaxMinTemp = (Bool) (dailyInputFlags[TEMP_MAX] && dailyInputFlags[TEMP_MIN]); + Bool hasMaxMinRelHumid = (Bool) (dailyInputFlags[REL_HUMID_MAX] && dailyInputFlags[REL_HUMID_MIN]); double es, e, relHum, tempSlope, svpVal; @@ -1642,7 +1636,7 @@ void _read_weather_hist( &weathInput[8], &weathInput[9], &weathInput[10], &weathInput[11], &weathInput[12], &weathInput[13]); - if (x != weath->n_input_forcings + 1) { + if (x != n_input_forcings + 1) { CloseFile(&f); LogError(logfp, LOGFATAL, "%s : Incomplete record %d (doy=%d).", fname, lineno, doy); } @@ -1657,85 +1651,104 @@ void _read_weather_hist( /* --- Make the assignments ---- */ doy--; // base1 -> base0 - yearWeather->temp_max[doy] = weathInput[maxTempIndex]; - yearWeather->temp_min[doy] = weathInput[minTempIndex]; - yearWeather->ppt[doy] = weathInput[precipIndex]; + 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[maxTempIndex]) && !missing(weathInput[minTempIndex])) { + if (!missing(weathInput[dailyInputIndices[TEMP_MAX]]) && + !missing(weathInput[dailyInputIndices[TEMP_MIN]])) { - yearWeather->temp_avg[doy] = (weathInput[maxTempIndex] + - weathInput[minTempIndex]) / 2.0; + yearWeather->temp_avg[doy] = (weathInput[dailyInputIndices[TEMP_MAX]] + + weathInput[dailyInputIndices[TEMP_MIN]]) / 2.0; } - if(!weath->use_cloudCoverMonthly && weath->has_cloudCover) { - yearWeather->cloudcov_daily[doy] = weathInput[cloudcovIndex]; + if(!use_cloudCoverMonthly) { + if(dailyInputFlags[CLOUD_COV]) { + yearWeather->cloudcov_daily[doy] = weathInput[dailyInputIndices[CLOUD_COV]]; + } } - if(!weath->use_windSpeedMonthly) { - if(weath->has_sfcWind) { - yearWeather->windspeed_daily[doy] = weathInput[windSpeedIndex]; - - } else if(weath->has_windComp) { - yearWeather->windspeed_daily[doy] = sqrt(squared(weathInput[windEastIndex]) + - squared(weathInput[windNorthIndex])); - + if(!use_windSpeedMonthly) { + if(dailyInputFlags[WIND_SPEED]) { + yearWeather->windspeed_daily[doy] = weathInput[dailyInputIndices[WIND_SPEED]]; + } else if(dailyInputFlags[WIND_EAST] && dailyInputFlags[WIND_NORTH]) { + yearWeather->windspeed_daily[doy] = sqrt(squared(weathInput[dailyInputIndices[WIND_EAST]]) + + squared(weathInput[dailyInputIndices[WIND_NORTH]])); } } - if(!weath->use_humidityMonthly) { - if(weath->has_hurs2) { - yearWeather->r_humidity_daily[doy] = (weathInput[hursMaxIndex] + - weathInput[hursMinIndex]) / 2; - } else if(weath->has_hurs) { - yearWeather->r_humidity_daily[doy] = weathInput[relHumIndex]; + // Check to see if monthly humidity values are being used + // If not, skip entire code block to elimiate unecessary checking of many flags + if(!use_humidityMonthly) { + if(hasMaxMinRelHumid) { + yearWeather->r_humidity_daily[doy] = (weathInput[dailyInputIndices[REL_HUMID_MAX]] + + weathInput[dailyInputIndices[REL_HUMID_MIN]]) / 2; - } else if(weath->has_huss) { + } else if(dailyInputFlags[REL_HUMID]) { + yearWeather->r_humidity_daily[doy] = weathInput[dailyInputIndices[REL_HUMID]]; + + } else if(dailyInputFlags[SPEC_HUMID]) { // Specific Humidity (Bolton 1980) es = (6.112 * exp(17.67 * yearWeather->temp_avg[doy]) / (yearWeather->temp_avg[doy] + 243.5)); - e = (weathInput[hussIndex] * 1013.25) / - (.378 * weathInput[hussIndex] + .622); + 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); + // Check if a calculation of actual vapor pressure occurred with + // bad temperature values (SW_MISSING) + if(missing(yearWeather->temp_avg[doy])) { + + yearWeather->r_humidity_daily[doy] = SW_MISSING; + } + } - } - if(!weath->has_vp) { - if(weath->has_tdps) { - yearWeather->actualVaporPressure[doy] = - actualVaporPressure3(weathInput[dewPointIndex]); + // Deal with actual vapor pressure + if(dailyInputFlags[ACTUAL_VP]){ - } else if(weath->has_hurs2 && weath->has_temp2) { + yearWeather->actualVaporPressure[doy] = weathInput[dailyInputIndices[ACTUAL_VP]]; + + } else if(dailyInputFlags[TEMP_DEWPOINT]) { + yearWeather->actualVaporPressure[doy] = + actualVaporPressure3(weathInput[dailyInputIndices[TEMP_DEWPOINT]]); + + } else if(hasMaxMinTemp && hasMaxMinRelHumid) { yearWeather->actualVaporPressure[doy] = - actualVaporPressure2(weathInput[hursMaxIndex], - weathInput[hursMinIndex], - weathInput[maxTempIndex], - weathInput[minTempIndex]); - } else if(weath->has_hurs) { + actualVaporPressure2(weathInput[dailyInputIndices[REL_HUMID_MAX]], + weathInput[dailyInputIndices[REL_HUMID_MIN]], + weathInput[dailyInputIndices[TEMP_MAX]], + weathInput[dailyInputIndices[TEMP_MIN]]); + + // Check if a calculation of relative humidity occurred with + // bad temperature values (SW_MISSING) + if(missing(yearWeather->temp_max[doy]) || missing(yearWeather->temp_min[doy])) { + + yearWeather->r_humidity_daily[doy] = SW_MISSING; + } + + } else if(dailyInputFlags[REL_HUMID] || dailyInputFlags[SPEC_HUMID]) { yearWeather->actualVaporPressure[doy] = actualVaporPressure1(yearWeather->r_humidity_daily[doy], yearWeather->temp_avg[doy]); } - } else { - yearWeather->actualVaporPressure[doy] = weathInput[vaporPressIndex]; - } - // Check if a better calculation of relative humisity is available - // (using dewpoint temperature or the daily actual vapor pressure input value) - if((!weath->use_humidityMonthly && missing(yearWeather->r_humidity_daily[doy])) - && (weath->has_vp || weath->has_tdps)) { + // Check if a better calculation of relative humidity is available + // (using dewpoint temperature or the daily actual vapor pressure input value) + if(dailyInputFlags[ACTUAL_VP] || dailyInputFlags[TEMP_DEWPOINT]) { - svpVal = svp(yearWeather->temp_avg[doy], &tempSlope); + svpVal = svp(yearWeather->temp_avg[doy], &tempSlope); - yearWeather->r_humidity_daily[doy] = - yearWeather->actualVaporPressure[doy] / svpVal; - } + yearWeather->r_humidity_daily[doy] = + yearWeather->actualVaporPressure[doy] / svpVal; + } + } // Calculate annual average temperature based on historical input, i.e., From 770f75db167f8f715ecd08d56b328605dc52bd9d Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 17 Jan 2023 00:47:11 -0700 Subject: [PATCH 285/326] FlowLib test: Removed fixed actual vapor pressure values - In a previous commit: https://github.com/DrylandEcology/SOILWAT2/commit/28baf26cb2fcd34f6dc10750b674499e08cdd153, fixed actual vapor pressure values were replaced with called to `actualVaporPressure1()` - One test got missed, so "e_a_in[]" was removed and the variable "actual_vap_pressure" is refreshed every iteration of the loop with a call to `actualVaporPressure1()` --- tests/gtests/test_SW_Flow_Lib_PET.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/gtests/test_SW_Flow_Lib_PET.cc b/tests/gtests/test_SW_Flow_Lib_PET.cc index 7378b6b94..12bc16aa0 100644 --- a/tests/gtests/test_SW_Flow_Lib_PET.cc +++ b/tests/gtests/test_SW_Flow_Lib_PET.cc @@ -986,16 +986,16 @@ namespace // Inputs RHs[] = {0, 34, 56, 79, 100}, // Expected PET - expected_pet_RHs[] = {0.2267, 0.2123, 0.1662, 0.1128, 0.0612}, - // Input actual vapor pressure - e_a_in[] = {0.0, 1.077044, 1.773956, 2.502544, 3.167778}; + expected_pet_RHs[] = {0.2267, 0.2123, 0.1662, 0.1128, 0.0612}; for (i = 0; i < 5; i++) { + actual_vap_pressure = actualVaporPressure1(RHs[i], temp); + H_gt = solar_radiation( doy, lat, elev, slope0, aspect, reflec, - cloudcov, e_a_in[i], + cloudcov, actual_vap_pressure, &H_oh, &H_ot, &H_gh ); From 101af7f13ee3d06f4cc8f2bb24adc5091e04f81d Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 17 Jan 2023 00:59:52 -0700 Subject: [PATCH 286/326] SW_Weather: New tests and new directories/files - Three new directories totaling six new weather input files have been added * These three directories hold values from three different sources: daymet, gridmet, and maca * The purpose of these new files is to provide the ability to test the program with files with more than four columns and to provide examples to users Tests: - Added to InputValueCalculations test section: * Now tests for correct calculation of variables, i.e., relative humidity and actual vapor pressure, in this case * Manually sets SW_Weather variables so that relative humidity is averaged and actual vapor pressure is calculated based on it * Values for expected results are currently hard-coded due to the fact that we cannot access the input file values to dynamically calculate it based on changing file values * Comments are provided above the expected result calculation as to where the literals have come from * Expected result for relative humidity is an average, whereas the expected result for actual vapor pressure makes use of `actualVaporPressure1()` - Removed the setting of flag "has_hurs" due to its uselessness when the flag for monthly humidity is set --- .../Input/data_weather_daymet/weath.1980 | 368 ++++++++++++++++++ .../Input/data_weather_daymet/weath.1981 | 367 +++++++++++++++++ .../Input/data_weather_gridmet/weath.1980 | 368 ++++++++++++++++++ .../Input/data_weather_gridmet/weath.1981 | 367 +++++++++++++++++ .../Input/data_weather_maca/weath.1980 | 368 ++++++++++++++++++ .../Input/data_weather_maca/weath.1981 | 367 +++++++++++++++++ tests/gtests/test_SW_Weather.cc | 66 +++- 7 files changed, 2262 insertions(+), 9 deletions(-) create mode 100644 tests/example/Input/data_weather_daymet/weath.1980 create mode 100644 tests/example/Input/data_weather_daymet/weath.1981 create mode 100644 tests/example/Input/data_weather_gridmet/weath.1980 create mode 100644 tests/example/Input/data_weather_gridmet/weath.1981 create mode 100644 tests/example/Input/data_weather_maca/weath.1980 create mode 100644 tests/example/Input/data_weather_maca/weath.1981 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/tests/gtests/test_SW_Weather.cc b/tests/gtests/test_SW_Weather.cc index 851bb84c2..6381c9dd1 100644 --- a/tests/gtests/test_SW_Weather.cc +++ b/tests/gtests/test_SW_Weather.cc @@ -19,18 +19,30 @@ #include "tests/gtests/sw_testhelpers.h" #include "include/SW_Markov.h" #include "include/SW_Model.h" +#include "include/SW_Flow_lib_PET.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.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 @@ -692,12 +704,9 @@ namespace { in the use of the appropriate equation used for calculation */ - // NOTE: capture behaviors and don't be comprehensive (more than likely keep one `EXPECT_NEAR()`) - SW_SKY *sky = &SW_Sky; - // Initialize any variables - //int day, year = 1980 - int yearIndex = 0, midJanDay = 14; + int yearIndex = 0, midJanDay = 14, year = 1980; + double result, expectedResult; /* Test if monthly values are not being used */ @@ -706,7 +715,6 @@ namespace { SW_Weather.use_humidityMonthly = swTRUE; SW_Weather.use_windSpeedMonthly = swTRUE; SW_Weather.use_cloudCoverMonthly = swTRUE; - SW_Weather.has_hurs = swTRUE; // Read in all weather SW_WTH_read(); @@ -716,20 +724,60 @@ namespace { // Test the middle of January in year 1980 and see if it's not equal to SW_Sky.r_humidity[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(SW_Weather.allHist[yearIndex]->r_humidity_daily[midJanDay], sky->r_humidity[0], tol6); + EXPECT_NEAR(SW_Weather.allHist[yearIndex]->r_humidity_daily[midJanDay], SW_Sky.r_humidity[0], tol6); /* Test correct priority is being given to input values */ /* Test with lesser priority than the input */ // Switch directory to new inputs folder + strcpy(SW_Weather.name_prefix, "Input/data_weather_gridmet/weath"); + + // Manually edit index/flag arrays in SW_WEATHER for relative humidity max/min + // and turn off monthly flag for humidity + // Note: Indices of max/min relative humidity is based on the directory: + // Input/data_weather_gridmet/weath.1980 + SW_Weather.use_humidityMonthly = swFALSE; + SW_Weather.dailyInputIndices[REL_HUMID_MAX] = 4; + SW_Weather.dailyInputIndices[REL_HUMID_MIN] = 5; + SW_Weather.dailyInputFlags[REL_HUMID_MAX] = swTRUE; + SW_Weather.dailyInputFlags[REL_HUMID_MIN] = swTRUE; + SW_Weather.n_input_forcings = 7; + SW_Weather.n_years = 1; + SW_Weather.use_weathergenerator_only = swFALSE; // Using the new inputs folder, read in year = 1980 + _read_weather_hist( + year, + SW_Weather.allHist[0], + SW_Weather.name_prefix, + SW_Weather.n_input_forcings, + SW_Weather.dailyInputIndices, + SW_Weather.dailyInputFlags, + SW_Weather.use_cloudCoverMonthly, + SW_Weather.use_windSpeedMonthly, + SW_Weather.use_humidityMonthly + ); + + result = SW_Weather.allHist[yearIndex]->r_humidity_daily[0]; - // Focusing on hurs max and min, artificially set "has_huss" + // Get expected result 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); + + result = SW_Weather.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); } TEST(DailyInsteadOfMonthlyInputDeathTest, ReasonableValuesAndFlags) { From 5dafc025e37700ed59f27d134ba73e0700dad8b3 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Tue, 17 Jan 2023 01:05:04 -0700 Subject: [PATCH 287/326] Weather misc: input file, weather flag crash, and updated doc - Updated description for downward shortwave radiation in weather setup file, "weathsetup.in" - Updated function documentation for `generageMissingWeather()` to now include mention of new variables that make use of "LOCF" (weather generator method = 1) - `checkAllWeather()` was revoked of its ability to crash the program if any of the max/min temp or precipitation flags are turned off, `SW_WTH_setup()` gains this since it's the most immediate location to crash under these conditions --- src/SW_Weather.c | 17 +++++++++++------ tests/example/Input/weathsetup.in | 3 +-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index a6a9b367a..5495a6e53 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -782,6 +782,11 @@ void scaleAllWeather( 1. Pass through (`method` = 0) 2. Imputation by last-value-carried forward "LOCF" (`method` = 1) - for minimum and maximum temperature + - cloud cover + - wind speed + - relative humidity + - downard surface shortwave radiation + - actual vapor pressure - precipitation is set to 0 - error if more than `optLOCF_nMax` days per calendar year are missing 3. First-order Markov weather generator (`method` = 2) @@ -953,12 +958,6 @@ void checkAllWeather(SW_WEATHER *weather) { double dailyMinTemp, dailyMaxTemp; - // Check if minimum or maximum temperature, or precipitation flags are 0 - if(!weather->has_temp2 || !weather->has_ppt) { - // Fail - LogError(logfp, LOGFATAL, "Temperature and/or precipitation flag(s) are unset."); - } - // Loop through `allHist` years for(year = 0; year < weather->n_years; year++) { numDaysInYear = Time_get_lastdoy_y(year + weather->startYear); @@ -1440,6 +1439,12 @@ void SW_WTH_setup(void) { SW_WeatherPrefix(w->name_prefix); CloseFile(&f); + // Check if minimum or maximum temperature, or precipitation flags are 0 + if(!dailyInputFlags[TEMP_MAX] || !dailyInputFlags[TEMP_MIN] || !dailyInputFlags[PPT]) { + // Fail + LogError(logfp, LOGFATAL, "Temperature and/or precipitation flag(s) are unset."); + } + if (lineno < nitems) { LogError(logfp, LOGFATAL, "%s : Too few input lines.", MyFileName); } diff --git a/tests/example/Input/weathsetup.in b/tests/example/Input/weathsetup.in index 30614827f..21cd8591a 100755 --- a/tests/example/Input/weathsetup.in +++ b/tests/example/Input/weathsetup.in @@ -41,8 +41,7 @@ 0 # Specific humidity [%] 0 # Dew point temperature [C] 0 # Actual vapor pressure [kPa] -0 # Downward surface shortwave radiation [MJ/day] - +0 # Downward surface shortwave radiation [W/m2], i.e., flux density for a (hypothetical) flat horizon averaged over the daylight period of the day #--- Monthly scaling parameters: # Month 1 = January, Month 2 = February, etc. From f2351ad70733f1026240e10edce275c05d5c4f6f Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 18 Jan 2023 15:42:40 -0500 Subject: [PATCH 288/326] Fix doxyfile to locate source code at new locations - commit 296f123e35836d29d59dbc5ec4974f7e55f9cfa7 "Merge pull request #333 from DrylandEcology/feature_restructure" caused doxygen documentation to no longer find source code (see also commit dc521a4a5036c964503a99891337e15b1fe21af6 "Re-organize repository (part 2)" that worked on the doxyfile) -> update doxyfile "INPUT" to include the new locations "include/" and "src/" --- doc/Doxyfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/Doxyfile b/doc/Doxyfile index ceb06ca65..bc5352110 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -875,6 +875,8 @@ WARN_LOGFILE = # Note: If this tag is empty the current directory is searched. INPUT = . \ + include/ \ + src/ \ doc/additional_pages/ \ tests/example/ \ tests/example/Input From cd3a1b368f009a47dca2d13e8dc23050569b4273 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 18 Jan 2023 16:58:10 -0500 Subject: [PATCH 289/326] Miscellaneous fixes in documentation - change format of doxygen "sideffect" alias as recommended since update to doxygen v1.9.3 (see commit 4248d96fcf0d33547c44bbd33dcd53c786d0f3bc) - correctly specify path to output file in "A_SOILWAT2_user_guide.md" --- doc/Doxyfile | 2 +- doc/additional_pages/A_SOILWAT2_user_guide.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/Doxyfile b/doc/Doxyfile index bc5352110..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 diff --git a/doc/additional_pages/A_SOILWAT2_user_guide.md b/doc/additional_pages/A_SOILWAT2_user_guide.md index 3636b9d1f..6a19030f1 100644 --- a/doc/additional_pages/A_SOILWAT2_user_guide.md +++ b/doc/additional_pages/A_SOILWAT2_user_guide.md @@ -152,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("tests/example/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). From 9fd5ac5680491ba0b98dc6b3f05cba0a3d6020b1 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 18 Jan 2023 17:50:30 -0500 Subject: [PATCH 290/326] Update `tools/run_doxygen.sh` script: warn if bibtex is missing - bibtex is required to parse "\cite" commands in the documentation and build a bibliography - if bibtex is missing, then doxygen still creates a working documentation * however, the documentation will be without citations and without a bibliography * warnings are issued and the script signals an error --- tools/run_doxygen.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/run_doxygen.sh b/tools/run_doxygen.sh index 5fc5cef11..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 From 9e98b219ad84c1ff40f50b68641c54d5a038d45a Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 22 Jan 2023 14:47:52 -0700 Subject: [PATCH 291/326] Group MAX_INPUT_COLUMNS with array indices in `defines.h` - Grouped MAX_INPUT_COLUMNS #define with related array indices for input flags/indices - Updated comment for array indices to mention they must match the order of the input flags from "weathsetup.in" --- include/SW_Defines.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/include/SW_Defines.h b/include/SW_Defines.h index f6df46906..ffe4cb8c7 100644 --- a/include/SW_Defines.h +++ b/include/SW_Defines.h @@ -59,9 +59,6 @@ extern "C" { #define SW_MISSING 999. /**< Value to use as MISSING */ -#define MAX_INPUT_COLUMNS 14 /**< Maximum number of columns that can be input in a weath.YYYY file*/ - - // Euler's constant #ifdef M_E #define swE M_E @@ -119,7 +116,11 @@ extern "C" { #define SW_FORBS 2 #define SW_GRASS 3 -/* Indices to daily input flags/indices (dailyInputFlags & dailyInputIndices in SW_WEATHER)*/ +/* + 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 From efe5693cb5569824e0387f7c2a4edbf75b2a0a58 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 22 Jan 2023 15:15:41 -0700 Subject: [PATCH 292/326] New crash conditions within `SW_WTH_setup()` - We need `SW_WTH_setup()` to cover the condition where max/min, or east/north flags within "weathsetup.in" are unevenly set (1/0 or 0/1) * The function now checks to see if temperature flags have been set unevenly and crash ** The old test for temperature flags has been switched to only test for if both flags are not set, along with precipitation * Added tests for unevenly set flags for east/north wind speed and max/min relative humidity after flags have a chance to be turned off ** This testing happens after flags have been turned off to eliminate the chance of crashing unnecessarily when only monthly values are to be used --- src/SW_Weather.c | 177 ++++++++++++++++++++++++++++------------------- 1 file changed, 106 insertions(+), 71 deletions(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 5495a6e53..495b092c4 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -1439,85 +1439,120 @@ void SW_WTH_setup(void) { SW_WeatherPrefix(w->name_prefix); CloseFile(&f); - // Check if minimum or maximum temperature, or precipitation flags are 0 - if(!dailyInputFlags[TEMP_MAX] || !dailyInputFlags[TEMP_MIN] || !dailyInputFlags[PPT]) { - // Fail - LogError(logfp, LOGFATAL, "Temperature and/or precipitation flag(s) are unset."); - } - - if (lineno < nitems) { - LogError(logfp, LOGFATAL, "%s : Too few input lines.", MyFileName); - } + // 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])) { - // Calculate value indices for `allHist` + // Fail + LogError(logfp, LOGFATAL, "Maximum/minimum temperature flags are unevenly set. " + "Both flags for temperature must be set."); - // Default n_input_forcings to 0 - w->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 - w->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 - w->dailyInputIndices[currFlag] = w->n_input_forcings; + } - // Increment "n_input_forcings" - w->n_input_forcings++; - } - } + // 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 (lineno < nitems) { + LogError(logfp, LOGFATAL, "%s : Too few input lines.", MyFileName); + } + + // Calculate value indices for `allHist` + + // Default n_input_forcings to 0 + w->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 + w->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 + w->dailyInputIndices[currFlag] = w->n_input_forcings; + + // Increment "n_input_forcings" + w->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. + */ + + + if(w->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(w->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(w->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)."); - /* - 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 - // Asude from checking for a monthly flag, we must make sure we have - // daily flags to turn off instead of turning off flags that are already off - if(w->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; - } + // 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])) { - if(w->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; - } - } + // 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)."); - if(w->use_cloudCoverMonthly && dailyInputFlags[CLOUD_COV]) { - dailyInputFlags[CLOUD_COV] = swFALSE; - monthlyFlagPrioritized = swTRUE; - } + } - // 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."); - } + // 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."); + } } From 0d9f9bc588f9a1e58a98bd48140439790f03e3c6 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 22 Jan 2023 15:21:06 -0700 Subject: [PATCH 293/326] `_read_weather_hist()` loses monthly input flag parameters - `_read_weather_hist()` should only deal with daily flags, and as a result, the function does not need to have monthly flags - Function calculates if monthly input flags are set for humidity based on individual flags: relative humidity, max/min relative humidity, actual vapor pressure, and specific humidity * `SW_WTH_setup()` will handle the turning off of these flags, and if one is set, it means that the monthly flag is not set * The reason for this calculation is that without it, many checks will be carried out over the simulation years, reducing the efficiency: * For a 31-year simulation, with the humidity monthly flag, the comparison of checks would be: ~102,114 and ~11,532, not using a local variable versus using a local variable, respectively (for the humidity section alone) --- include/SW_Weather.h | 5 +---- src/SW_Weather.c | 52 ++++++++++++++++++-------------------------- 2 files changed, 22 insertions(+), 35 deletions(-) diff --git a/include/SW_Weather.h b/include/SW_Weather.h index 12f04499e..bca721928 100644 --- a/include/SW_Weather.h +++ b/include/SW_Weather.h @@ -219,10 +219,7 @@ void _read_weather_hist( char weather_prefix[], unsigned int n_input_forcings, unsigned int *dailyInputIndices, - Bool *dailyInputFlags, - Bool use_cloudCoverMonthly, - Bool use_windSpeedMonthly, - Bool use_humidityMonthly + Bool *dailyInputFlags ); void readAllWeather( SW_WEATHER_HIST **allHist, diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 495b092c4..f99662d16 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -574,10 +574,7 @@ void readAllWeather( weather_prefix, n_input_forcings, dailyInputIndices, - dailyInputFlags, - use_cloudCoverMonthly, - use_windSpeedMonthly, - use_humidityMonthly + dailyInputFlags ); } } @@ -1615,12 +1612,6 @@ void SW_WTH_read(void) { 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 - @param use_cloudCoverMonthly A boolean; specifying whether or not cloud cover monthly values - are to be used instead of daily input values - @param use_windSpeedMonthly A boolean; specifying whether or not wind speed monthly values - are to be used instead of daily input values - @param use_humidityMonthly A boolean; specifying whether or not humidity monthly values - are to be used instead of daily input values */ void _read_weather_hist( TimeInt year, @@ -1628,10 +1619,7 @@ void _read_weather_hist( char weather_prefix[], unsigned int n_input_forcings, unsigned int *dailyInputIndices, - Bool *dailyInputFlags, - Bool use_cloudCoverMonthly, - Bool use_windSpeedMonthly, - Bool use_humidityMonthly + Bool *dailyInputFlags ) { /* =================================================== */ /* Read the historical (measured) weather files. @@ -1658,6 +1646,11 @@ void _read_weather_hist( Bool hasMaxMinTemp = (Bool) (dailyInputFlags[TEMP_MAX] && dailyInputFlags[TEMP_MIN]); Bool hasMaxMinRelHumid = (Bool) (dailyInputFlags[REL_HUMID_MAX] && dailyInputFlags[REL_HUMID_MIN]); + // 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]; @@ -1697,30 +1690,27 @@ void _read_weather_hist( // Calculate average air temperature if min/max not missing if (!missing(weathInput[dailyInputIndices[TEMP_MAX]]) && - !missing(weathInput[dailyInputIndices[TEMP_MIN]])) { + !missing(weathInput[dailyInputIndices[TEMP_MIN]])) { - yearWeather->temp_avg[doy] = (weathInput[dailyInputIndices[TEMP_MAX]] + - weathInput[dailyInputIndices[TEMP_MIN]]) / 2.0; + yearWeather->temp_avg[doy] = (weathInput[dailyInputIndices[TEMP_MAX]] + + weathInput[dailyInputIndices[TEMP_MIN]]) / 2.0; } - if(!use_cloudCoverMonthly) { - if(dailyInputFlags[CLOUD_COV]) { - yearWeather->cloudcov_daily[doy] = weathInput[dailyInputIndices[CLOUD_COV]]; - } + if(dailyInputFlags[CLOUD_COV]) { + yearWeather->cloudcov_daily[doy] = weathInput[dailyInputIndices[CLOUD_COV]]; } - if(!use_windSpeedMonthly) { - if(dailyInputFlags[WIND_SPEED]) { - yearWeather->windspeed_daily[doy] = weathInput[dailyInputIndices[WIND_SPEED]]; - } else if(dailyInputFlags[WIND_EAST] && dailyInputFlags[WIND_NORTH]) { - yearWeather->windspeed_daily[doy] = sqrt(squared(weathInput[dailyInputIndices[WIND_EAST]]) + - squared(weathInput[dailyInputIndices[WIND_NORTH]])); - } + if(dailyInputFlags[WIND_SPEED]) { + yearWeather->windspeed_daily[doy] = weathInput[dailyInputIndices[WIND_SPEED]]; + + } else if(dailyInputFlags[WIND_EAST] && dailyInputFlags[WIND_NORTH]) { + + yearWeather->windspeed_daily[doy] = sqrt(squared(weathInput[dailyInputIndices[WIND_EAST]]) + + squared(weathInput[dailyInputIndices[WIND_NORTH]])); } - // Check to see if monthly humidity values are being used - // If not, skip entire code block to elimiate unecessary checking of many flags - if(!use_humidityMonthly) { + // Check to see if daily humidity values are being used + if(useHumidityDaily) { if(hasMaxMinRelHumid) { yearWeather->r_humidity_daily[doy] = (weathInput[dailyInputIndices[REL_HUMID_MAX]] + weathInput[dailyInputIndices[REL_HUMID_MIN]]) / 2; From 388da0024fb74dd347d29669f399bd1c35645549 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 22 Jan 2023 15:34:19 -0700 Subject: [PATCH 294/326] Safeguard `_read_weather_hist()` calculations from SW_MISSING - Under the circumstances where maximum/average/minimum temperature is SW_MISSING, the following calculations are set to SW_MISSING: * Specific humidity is used and the average temperature is missing * Maximum and minimum relative humidity are used and maximum and minimum temperature are SW_MISSING - When actual vapor pressure and/or dew point temperature are input, relative humidity now must be missing to calculate it * If these conditions are met, if average temperature and/or actual vapor pressure are missing, relative humidity is not calculated --- src/SW_Weather.c | 69 +++++++++++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 27 deletions(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index f99662d16..1ec722b97 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -1719,22 +1719,25 @@ void _read_weather_hist( yearWeather->r_humidity_daily[doy] = weathInput[dailyInputIndices[REL_HUMID]]; } else if(dailyInputFlags[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); + // Make sure the calculation of relative humidity will not be + // executed while average temperature is holding the value "SW_MISSING" + if(!missing(yearWeather->temp_avg[doy])) { - relHum = e / es; - relHum = max(0., relHum); + // Specific Humidity (Bolton 1980) + es = (6.112 * exp(17.67 * yearWeather->temp_avg[doy]) / + (yearWeather->temp_avg[doy] + 243.5)); - yearWeather->r_humidity_daily[doy] = min(100., relHum); + e = (weathInput[dailyInputIndices[SPEC_HUMID]] * 1013.25) / + (.378 * weathInput[dailyInputIndices[SPEC_HUMID]] + .622); - // Check if a calculation of actual vapor pressure occurred with - // bad temperature values (SW_MISSING) - if(missing(yearWeather->temp_avg[doy])) { + 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; } @@ -1750,17 +1753,20 @@ void _read_weather_hist( actualVaporPressure3(weathInput[dailyInputIndices[TEMP_DEWPOINT]]); } else if(hasMaxMinTemp && hasMaxMinRelHumid) { - yearWeather->actualVaporPressure[doy] = - actualVaporPressure2(weathInput[dailyInputIndices[REL_HUMID_MAX]], - weathInput[dailyInputIndices[REL_HUMID_MIN]], - weathInput[dailyInputIndices[TEMP_MAX]], - weathInput[dailyInputIndices[TEMP_MIN]]); - // Check if a calculation of relative humidity occurred with - // bad temperature values (SW_MISSING) - if(missing(yearWeather->temp_max[doy]) || missing(yearWeather->temp_min[doy])) { + // Make sure the calculation of actual vapor pressure will not be + // executed while max and min temperature are holding the value "SW_MISSING" + if(!missing(yearWeather->temp_max[doy]) && + !missing(yearWeather->temp_min[doy])) { - yearWeather->r_humidity_daily[doy] = SW_MISSING; + 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]) { @@ -1769,15 +1775,24 @@ void _read_weather_hist( yearWeather->temp_avg[doy]); } - // Check if a better calculation of relative humidity is available - // (using dewpoint temperature or the daily actual vapor pressure input value) - if(dailyInputFlags[ACTUAL_VP] || dailyInputFlags[TEMP_DEWPOINT]) { + // 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])) { - svpVal = svp(yearWeather->temp_avg[doy], &tempSlope); + // 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])) { - yearWeather->r_humidity_daily[doy] = - yearWeather->actualVaporPressure[doy] / svpVal; - } + svpVal = svp(yearWeather->temp_avg[doy], &tempSlope); + + yearWeather->r_humidity_daily[doy] = + yearWeather->actualVaporPressure[doy] / svpVal; + } + } } From 25c0c0aad9985d8fbc62062ac8331f099c5d94d4 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 22 Jan 2023 15:49:41 -0700 Subject: [PATCH 295/326] New weather tests: New directories - Renamed previous subroutine "InputValueCalculations" to "MonthlyInputPrioritization" - "MonthlyInputPrioritization" now only tests if monthly flags take priority instead of daily flags (which are not set) - The previous test of "gridmet" has been moved to its own subroutine - New subroutine: "DailyGridMet": * Mostly the same as when it was in the subroutine "MonthlyInputPrioritization" * Only differences are that it now checks all weather values through `checkAllWeather()` and it now sets all flags related to the input file to be as realistic as possible - New subroutine: "DailyDayMet": * Uses the new directory "Input/data_weather_daymet/weath" * Sets all flags with respect to weath.YYYY variables to make tests as realistic as possible * Tests if daily input values are correctly set when dealing with humidity values along with calculating a reasonable value of relative humidity based on actual vapor pressure * Checks that all weather values are within reasonable range through `checkAllWeather()` - New subroutine: "DailyMACA": * Uses the new directory "Input/data_weather_maca/weath" * Sets all flags with respect to weath.YYYY variables to make tests as realistic as possible * Tests reasonable values aside from humidity-related variables, in this case, wind speed using the Pythagorean theorem from east/north values * Checks that all weather values are within reasonable range through `checkAllWeather()` --- tests/gtests/test_SW_Weather.cc | 301 +++++++++++++++++++++++--------- 1 file changed, 223 insertions(+), 78 deletions(-) diff --git a/tests/gtests/test_SW_Weather.cc b/tests/gtests/test_SW_Weather.cc index 6381c9dd1..4b9b46c69 100644 --- a/tests/gtests/test_SW_Weather.cc +++ b/tests/gtests/test_SW_Weather.cc @@ -698,87 +698,232 @@ namespace { } - TEST(DailyInsteadOfMonthlyInputTest, InputValueCalculations) { + TEST(DailyInsteadOfMonthlyInputTest, MonthlyInputPrioritization) { /* - This section covers the correct prioritization of input values resulting - in the use of the appropriate equation used for calculation + This section covers the correct prioritization of monthly input values + instead of daily read-in values */ - // Initialize any variables - int yearIndex = 0, midJanDay = 14, year = 1980; - double result, expectedResult; - - /* Test if monthly values are not being used */ - - SW_WTH_setup(); - - SW_Weather.use_humidityMonthly = swTRUE; - SW_Weather.use_windSpeedMonthly = swTRUE; - SW_Weather.use_cloudCoverMonthly = swTRUE; - - // Read in all weather - SW_WTH_read(); - - /* Only test if monthly values are being used for January in relative humidity */ - - // Test the middle of January in year 1980 and see if it's not equal to SW_Sky.r_humidity[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(SW_Weather.allHist[yearIndex]->r_humidity_daily[midJanDay], SW_Sky.r_humidity[0], tol6); - - /* Test correct priority is being given to input values */ - - /* Test with lesser priority than the input */ - - // Switch directory to new inputs folder - strcpy(SW_Weather.name_prefix, "Input/data_weather_gridmet/weath"); - - // Manually edit index/flag arrays in SW_WEATHER for relative humidity max/min - // and turn off monthly flag for humidity - // Note: Indices of max/min relative humidity is based on the directory: - // Input/data_weather_gridmet/weath.1980 - SW_Weather.use_humidityMonthly = swFALSE; - SW_Weather.dailyInputIndices[REL_HUMID_MAX] = 4; - SW_Weather.dailyInputIndices[REL_HUMID_MIN] = 5; - SW_Weather.dailyInputFlags[REL_HUMID_MAX] = swTRUE; - SW_Weather.dailyInputFlags[REL_HUMID_MIN] = swTRUE; - SW_Weather.n_input_forcings = 7; - SW_Weather.n_years = 1; - SW_Weather.use_weathergenerator_only = swFALSE; - - // Using the new inputs folder, read in year = 1980 - _read_weather_hist( - year, - SW_Weather.allHist[0], - SW_Weather.name_prefix, - SW_Weather.n_input_forcings, - SW_Weather.dailyInputIndices, - SW_Weather.dailyInputFlags, - SW_Weather.use_cloudCoverMonthly, - SW_Weather.use_windSpeedMonthly, - SW_Weather.use_humidityMonthly - ); - - result = SW_Weather.allHist[yearIndex]->r_humidity_daily[0]; - - // Get expected result 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); - - result = SW_Weather.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); - } + // 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); + } + + 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; + + /* 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"); + + // 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[REL_HUMID_MAX] = swTRUE; + w->dailyInputFlags[REL_HUMID_MIN] = swTRUE; + w->dailyInputFlags[WIND_SPEED] = swTRUE; + w->dailyInputFlags[SHORT_WR] = swTRUE; + w->n_input_forcings = 7; + + // 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); + + 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); + + // Make sure calculations and set input values are within reasonable range + checkAllWeather(w); + } + + 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; + + /* 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"); + + // 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->dailyInputFlags[REL_HUMID_MAX] = swFALSE; + w->dailyInputFlags[REL_HUMID_MIN] = swFALSE; + w->n_input_forcings = 5; + + // Fill the first day of relative humidity with SW_MISSING so that + // the desired calculation to be tested is able to trigger + w->allHist[yearIndex]->r_humidity_daily[0] = SW_MISSING; + + // 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); + + // Make sure calculations and set input values are within reasonable range + checkAllWeather(w); + } + + 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; + + /* 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"); + + // 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[ACTUAL_VP] = 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[ACTUAL_VP] = swTRUE; + w->n_input_forcings = 8; + + // 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); + + // Make sure calculations and set input values are within reasonable range + checkAllWeather(w); + } TEST(DailyInsteadOfMonthlyInputDeathTest, ReasonableValuesAndFlags) { /* From 11f3d6b6ce78fdea1603c12630a5f491e71417b4 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 22 Jan 2023 15:53:14 -0700 Subject: [PATCH 296/326] Fixed weather death test with `_read_weather_hist()` - The test suite/subroutine "DailyInsteadOfMonthlyInputDeathTest.ReasonableValuesAndFlags" previously contained a commented-out death test that did not work * Death test is now uncommented and working, the problem was the action of sending in the wrong weather prefix of "weath" instead of "Input/weath_data/weath" - Switched using SW_Weather to using a pointer to SW_Weather --- tests/gtests/test_SW_Weather.cc | 105 +++++++++++++++++--------------- 1 file changed, 55 insertions(+), 50 deletions(-) diff --git a/tests/gtests/test_SW_Weather.cc b/tests/gtests/test_SW_Weather.cc index 4b9b46c69..73e8f2f7f 100644 --- a/tests/gtests/test_SW_Weather.cc +++ b/tests/gtests/test_SW_Weather.cc @@ -925,74 +925,79 @@ namespace { checkAllWeather(w); } - TEST(DailyInsteadOfMonthlyInputDeathTest, ReasonableValuesAndFlags) { - /* - This section covers number of flags and the testing of reasonable results (`checkAllWeather()`). + 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. + 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. - */ + 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 - //char weathPrefix[] = "weath"; - //TimeInt year = 1980; - double originVal; + // Initialize any variables + TimeInt year = 1980; + double originVal; + SW_WEATHER *w = &SW_Weather; - /* Not the same number of flags as columns */ + /* Not the same number of flags as columns */ - // Set SW_WEATHER's n_input_forcings to a number that is - // not the columns being read in + // Set SW_WEATHER's n_input_forcings to a number that is + // not the columns being read in - allocateAllWeather(&SW_Weather); - //SW_Weather.n_input_forcings = 0; - //SW_Weather.n_years = 1; + /* Check for value(s) that are not within reasonable range these + tests will make use of `checkAllWeather()` */ - // Run death test - // EXPECT_DEATH_IF_SUPPORTED( - // _read_weather_hist(year, SW_Weather.allHist[0], weathPrefix), - // "" - // ); + SW_WTH_read(); - /* Check for value(s) that are not within reasonable range these - tests will make use of `checkAllWeather()` */ + 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 + ), "" + ); - // Edit SW_WEATHER_HIST values from their original value + // Edit SW_WEATHER_HIST values from their original value - // Make temperature unreasonable (not within [-100, 100] or max < min) - SW_WTH_read(); + // Make temperature unreasonable (not within [-100, 100] or max < min) - originVal = SW_Weather.allHist[0]->temp_max[0]; + originVal = SW_Weather.allHist[0]->temp_max[0]; - SW_Weather.allHist[0]->temp_max[0] = -102.; + w->allHist[0]->temp_max[0] = -102.; - EXPECT_DEATH_IF_SUPPORTED( - checkAllWeather(&SW_Weather), - "" - ); + EXPECT_DEATH_IF_SUPPORTED( + checkAllWeather(w), + "" + ); - // Make precipitation unresonable (< 0) - SW_Weather.allHist[0]->temp_max[0] = originVal; + // Make precipitation unresonable (< 0) + w->allHist[0]->temp_max[0] = originVal; - originVal = SW_Weather.allHist[0]->ppt[0]; + originVal = SW_Weather.allHist[0]->ppt[0]; - SW_Weather.allHist[0]->ppt[0] = -1.; + w->allHist[0]->ppt[0] = -1.; - EXPECT_DEATH_IF_SUPPORTED( - checkAllWeather(&SW_Weather), - "" - ); + EXPECT_DEATH_IF_SUPPORTED( + checkAllWeather(w), + "" + ); - // Make relative humidity unreasonable (< 0%) - SW_Weather.allHist[0]->ppt[0] = originVal; + // Make relative humidity unreasonable (< 0%) + w->allHist[0]->ppt[0] = originVal; - SW_Weather.allHist[0]->r_humidity_daily[0] = -.1252; + w->allHist[0]->r_humidity_daily[0] = -.1252; - EXPECT_DEATH_IF_SUPPORTED( - checkAllWeather(&SW_Weather), - "" - ); - } + EXPECT_DEATH_IF_SUPPORTED( + checkAllWeather(w), + "" + ); + } } From a867299886f8534e6015a3190c5ce1c34071f425 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 22 Jan 2023 16:00:53 -0700 Subject: [PATCH 297/326] `_read_weather_hist()` variable switched from RealF to RealD - "weathInput" in `_read_weather_hist()` is now of type RealD instead of RealF to help fix any precision problems from float to double * There was evidence of the program using values that were off from the actual values within weath.YYYY, e.g., 74.139931 versus 74.14 for relative humidity (gridmet day 1 of 1980) - This change made three tests fail with seventh-month minimum temperature and seventh-month precipitation for climate variables * This is just a fix in precision which values have just been rounded to the next hundredths place --- src/SW_Weather.c | 4 ++-- tests/gtests/test_SW_Weather.cc | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 1ec722b97..eea6017a6 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -1641,7 +1641,7 @@ void _read_weather_hist( int doy; // TimeInt mon, j, k = 0; // RealF acc = 0.0; - RealF weathInput[MAX_INPUT_COLUMNS]; + 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]); @@ -1663,7 +1663,7 @@ void _read_weather_hist( while (GetALine(f, inbuf)) { lineno++; - x = sscanf(inbuf, "%d %f %f %f %f %f %f %f %f %f %f %f %f %f %f", + 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], diff --git a/tests/gtests/test_SW_Weather.cc b/tests/gtests/test_SW_Weather.cc index 73e8f2f7f..a95aadf68 100644 --- a/tests/gtests/test_SW_Weather.cc +++ b/tests/gtests/test_SW_Weather.cc @@ -216,7 +216,7 @@ namespace { // Climate variables used for C4 grass cover // (stdev of one value is undefined) - EXPECT_NEAR(climateOutput.minTemp7thMon_C[0], 2.809999, tol6); + 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); @@ -374,7 +374,7 @@ namespace { // Climate variables used for C4 grass cover // (stdev of one value is undefined) - EXPECT_NEAR(climateAverages.minTemp7thMon_C, 2.809999, tol6); + 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])); @@ -461,7 +461,7 @@ namespace { // Climate variables used for cheatgrass cover // (stdev of one value is undefined) - EXPECT_NEAR(climateOutput.PPT7thMon_mm[1], 22.199999, tol6); + 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); From 68e1ee18834b0da60fb7d812e304b61eca585ada Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 22 Jan 2023 16:04:44 -0700 Subject: [PATCH 298/326] Weather misc: calculation safeguarding and documentation readAllWeather(): - Corrected monthly input flag documentation to not mention values being read in from disk finalizeAllWeather(): - Safeguarded the calculation of actual vapor pressure from including relative humidity or average temperature values that are SW_MISSING --- src/SW_Weather.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index eea6017a6..6320b6ddf 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -503,12 +503,12 @@ void driestQtrSouthAdjMonYears(int month, int *adjustedYearZero, int *adjustedYe @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 - monthly values for cloud cover read-in from disk - @param[in] use_humidityMonthly A boolean; if `swTRUE`, function will interpolate - monthly values for humidity read-in from disk - @param[in] use_windSpeedMonthly A boolean; if `swTRUE`, function will interpolate - monthly values for wind speed read-in from disk + @param[in] use_cloudCoverMonthly A boolean; if `swTRUE`, function will interpolate mean + monthly values provided by \ref cloudcov to daily time series + @param[in] use_humidityMonthly A boolean; if `swTRUE`, function will interpolate mean + monthly values provided by \ref cloudcov to daily time series + @param[in] use_windSpeedMonthly A boolean; if `swTRUE`, function will interpolate mean + monthly values provided by \ref cloudcov 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 @@ -606,9 +606,16 @@ void finalizeAllWeather(SW_WEATHER *w) { if(w->use_humidityMonthly) { for(yearIndex = 0; yearIndex < w->n_years; yearIndex++) { for(day = 0; day < MAX_DAYS; day++) { - w->allHist[yearIndex]->actualVaporPressure[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]); + } } } } From 9fd974b75330bdd359d5206ded221856e2d67be9 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 22 Jan 2023 17:51:26 -0700 Subject: [PATCH 299/326] Fix Window Cygwin tests: Attempt 1 - Test subroutines using the new directories for gridmet, daymet, and maca now reset their directory to "Input/data_weather/weath" so other tests don't use the new directories unintentionally --- tests/gtests/test_SW_Weather.cc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/gtests/test_SW_Weather.cc b/tests/gtests/test_SW_Weather.cc index a95aadf68..58e9798ad 100644 --- a/tests/gtests/test_SW_Weather.cc +++ b/tests/gtests/test_SW_Weather.cc @@ -789,6 +789,9 @@ namespace { // Make sure calculations and set input values are within reasonable range checkAllWeather(w); + + // Reset directory to "data_weather" + strcpy(w->name_prefix, "Input/data_weather/weath"); } TEST(DailyWeatherInputTest, DailyDayMet) { @@ -863,6 +866,9 @@ namespace { // Make sure calculations and set input values are within reasonable range checkAllWeather(w); + + // Reset directory to "data_weather" + strcpy(w->name_prefix, "Input/data_weather/weath"); } TEST(DailyWeatherInputTest, DailyMACA) { @@ -923,6 +929,9 @@ namespace { // Make sure calculations and set input values are within reasonable range checkAllWeather(w); + + // Reset directory to "data_weather" + strcpy(w->name_prefix, "Input/data_weather/weath"); } TEST(DailyInsteadOfMonthlyInputDeathTest, ReasonableValuesAndFlags) { From 33e09f4fe83aa7543e2f9ef283f8b157da6da845 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sun, 22 Jan 2023 17:57:26 -0700 Subject: [PATCH 300/326] Fixed documentation of `readAllWeather()`s three monthly flags - Corrected documentation for wind speed and relative humidity to reference their respective monthly array within SW_SKY (NOTE: references do not work currently) --- src/SW_Weather.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 6320b6ddf..4fe659f85 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -506,9 +506,9 @@ void driestQtrSouthAdjMonYears(int month, int *adjustedYearZero, int *adjustedYe @param[in] use_cloudCoverMonthly A boolean; if `swTRUE`, function will interpolate mean monthly values provided by \ref cloudcov to daily time series @param[in] use_humidityMonthly A boolean; if `swTRUE`, function will interpolate mean - monthly values provided by \ref cloudcov to daily time series + monthly values provided by \ref r_humidity to daily time series @param[in] use_windSpeedMonthly A boolean; if `swTRUE`, function will interpolate mean - monthly values provided by \ref cloudcov to daily time series + monthly values provided by \ref 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 From a226fbdc810371cc52ec27645c635e43c3c8d7d4 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Mon, 23 Jan 2023 09:25:02 -0500 Subject: [PATCH 301/326] Update documentation of `readAllWeather()` - see doxygen documentation: * for use of \p: https://www.doxygen.nl/manual/commands.html#cmdp * for use of class::member, #enum, #define: https://www.doxygen.nl/manual/autolink.html#linkother --- src/SW_Weather.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 4fe659f85..6ae2f202d 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -494,31 +494,31 @@ void driestQtrSouthAdjMonYears(int month, int *adjustedYearZero, int *adjustedYe @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 `allHist`. - If missing, set values to `SW_MISSING`. + 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 + @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 \ref cloudcov to daily time series - @param[in] use_humidityMonthly A boolean; if `swTRUE`, function will interpolate mean - monthly values provided by \ref r_humidity to daily time series - @param[in] use_windSpeedMonthly A boolean; if `swTRUE`, function will interpolate mean - monthly values provided by \ref windspeed to daily time series + @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 + @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 + @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 + @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 + @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 + @param[in] r_humidity Array of size #MAX_MONTHS holding monthly relative humidity values to be interpolated */ From aa15e2791e88ded5d920dd80ee0caf2bfa6f4387 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 27 Jan 2023 00:11:49 -0700 Subject: [PATCH 302/326] Rest of calculated weather values cannot use SW_MISSING - If a value of relative humidity, actual vapor pressure, and/or wind speed is calculated, we now prevent all cases from using SW_MISSING - On the SOILWAT2 side, this is mainly for the future and we cannot hold the chance of using SW_MISSING for any weather variables aside from max/min temperature and precipitation - On rSOILWAT2, this is needed to not calculate any given SW_MISSING values that come with the dataset of "daymet" does not have December 31st on leap years --- src/SW_Weather.c | 56 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 6ae2f202d..eabcbe9d5 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -1652,6 +1652,7 @@ void _read_weather_hist( 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 @@ -1710,17 +1711,30 @@ void _read_weather_hist( if(dailyInputFlags[WIND_SPEED]) { yearWeather->windspeed_daily[doy] = weathInput[dailyInputIndices[WIND_SPEED]]; - } else if(dailyInputFlags[WIND_EAST] && dailyInputFlags[WIND_NORTH]) { + } else if(hasEastNorthWind) { - yearWeather->windspeed_daily[doy] = sqrt(squared(weathInput[dailyInputIndices[WIND_EAST]]) + - squared(weathInput[dailyInputIndices[WIND_NORTH]])); + // 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) { - yearWeather->r_humidity_daily[doy] = (weathInput[dailyInputIndices[REL_HUMID_MAX]] + - weathInput[dailyInputIndices[REL_HUMID_MIN]]) / 2; + + // 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]]; @@ -1728,8 +1742,10 @@ void _read_weather_hist( } else if(dailyInputFlags[SPEC_HUMID]) { // Make sure the calculation of relative humidity will not be - // executed while average temperature is holding the value "SW_MISSING" - if(!missing(yearWeather->temp_avg[doy])) { + // 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]) / @@ -1755,16 +1771,21 @@ void _read_weather_hist( yearWeather->actualVaporPressure[doy] = weathInput[dailyInputIndices[ACTUAL_VP]]; - } else if(dailyInputFlags[TEMP_DEWPOINT]) { + } 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 min temperature are holding the value "SW_MISSING" + // 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(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]], @@ -1777,9 +1798,18 @@ void _read_weather_hist( } } else if(dailyInputFlags[REL_HUMID] || dailyInputFlags[SPEC_HUMID]) { - yearWeather->actualVaporPressure[doy] = - actualVaporPressure1(yearWeather->r_humidity_daily[doy], - yearWeather->temp_avg[doy]); + + // 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 From 6f6c803390d1159ad4a08a9053b67aef5efb34f4 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 27 Jan 2023 00:15:23 -0700 Subject: [PATCH 303/326] Moved #include of `SW_Sky.h" from `SW_Weather.h` - On a previous commit, the #include for `SW_Sky.h` was added to `SW_Weather.h` - Since `SW_Weather.h` does not have any dependencies from the included file, the best place for the #include is within `SW_Weather.c` - Weather tests rely on "SW_Sky", so upon the mentioned change, `test_SW_Weather.cc` now includes `SW_Sky.h` --- include/SW_Weather.h | 1 - src/SW_Weather.c | 1 + tests/gtests/test_SW_Weather.cc | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/include/SW_Weather.h b/include/SW_Weather.h index bca721928..3935522d3 100644 --- a/include/SW_Weather.h +++ b/include/SW_Weather.h @@ -26,7 +26,6 @@ #include "include/SW_Times.h" #include "include/SW_Defines.h" -#include "include/SW_Sky.h" #ifdef __cplusplus extern "C" { diff --git a/src/SW_Weather.c b/src/SW_Weather.c index eabcbe9d5..49ebca058 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -51,6 +51,7 @@ #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" diff --git a/tests/gtests/test_SW_Weather.cc b/tests/gtests/test_SW_Weather.cc index 58e9798ad..4f5324e1f 100644 --- a/tests/gtests/test_SW_Weather.cc +++ b/tests/gtests/test_SW_Weather.cc @@ -20,6 +20,7 @@ #include "include/SW_Markov.h" #include "include/SW_Model.h" #include "include/SW_Flow_lib_PET.h" +#include "include/SW_Sky.h" namespace { From e6db7e789dcea57c4b02cc2f8aaed7c3bd9584df Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 27 Jan 2023 00:39:00 -0700 Subject: [PATCH 304/326] Reset SOILWAT2 in weath tests instead of weather directory - Previously, the newest weather test subroutines would reset the weather prefix path - This left room for different orders of testing to unexpectedly crash due to existing data - To solve that, we now call `Reset_SOILWAT2_UnitTest()` to reset everything and eliminate any possibility of using data from a previous test --- tests/gtests/test_SW_Weather.cc | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/gtests/test_SW_Weather.cc b/tests/gtests/test_SW_Weather.cc index 4f5324e1f..ef459c3ad 100644 --- a/tests/gtests/test_SW_Weather.cc +++ b/tests/gtests/test_SW_Weather.cc @@ -791,8 +791,8 @@ namespace { // Make sure calculations and set input values are within reasonable range checkAllWeather(w); - // Reset directory to "data_weather" - strcpy(w->name_prefix, "Input/data_weather/weath"); + // Reset SOILWAT2 for next test + Reset_SOILWAT2_after_UnitTest(); } TEST(DailyWeatherInputTest, DailyDayMet) { @@ -868,8 +868,8 @@ namespace { // Make sure calculations and set input values are within reasonable range checkAllWeather(w); - // Reset directory to "data_weather" - strcpy(w->name_prefix, "Input/data_weather/weath"); + // Reset SOILWAT2 for next test + Reset_SOILWAT2_after_UnitTest(); } TEST(DailyWeatherInputTest, DailyMACA) { @@ -931,8 +931,8 @@ namespace { // Make sure calculations and set input values are within reasonable range checkAllWeather(w); - // Reset directory to "data_weather" - strcpy(w->name_prefix, "Input/data_weather/weath"); + // Reset SOILWAT2 for next test + Reset_SOILWAT2_after_UnitTest(); } TEST(DailyInsteadOfMonthlyInputDeathTest, ReasonableValuesAndFlags) { @@ -1009,5 +1009,8 @@ namespace { checkAllWeather(w), "" ); + + // Reset SOILWAT2 for next test + Reset_SOILWAT2_after_UnitTest(); } } From e44035225c6e951331712a044abf1355f83c616c Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Fri, 27 Jan 2023 00:46:51 -0700 Subject: [PATCH 305/326] Daily instead of monthly value expectations in weath tests - Weather tests currently test the use of daily values instead of monthly and their calculations - Now, the subroutines for "daymet", "gridmet", and "maca" also explicitly expect daily calculated relative humidity and/or wind speed values instead of monthly - Adjusted comments for expected fail of `_read_weather_hist()` in "ReasonableValuesAndFlags" subroutine to correctly explain what will be on the next line(s) --- tests/gtests/test_SW_Weather.cc | 81 ++++++++++++++++++++++++++++----- 1 file changed, 70 insertions(+), 11 deletions(-) diff --git a/tests/gtests/test_SW_Weather.cc b/tests/gtests/test_SW_Weather.cc index ef459c3ad..11af06a0e 100644 --- a/tests/gtests/test_SW_Weather.cc +++ b/tests/gtests/test_SW_Weather.cc @@ -736,7 +736,7 @@ namespace { SW_WEATHER *w = &SW_Weather; double result, expectedResult; - int yearIndex = 0, year = 1980; + int yearIndex = 0, year = 1980, midJanDay = 14; /* Test correct priority is being given to input values from DAYMET */ SW_WTH_setup(); @@ -778,6 +778,19 @@ namespace { // 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 @@ -809,7 +822,7 @@ namespace { SW_WEATHER *w = &SW_Weather; double result, expectedResult, tempSlope; - int yearIndex = 0, year = 1980; + int yearIndex = 0, year = 1980, midJanDay = 14; /* Test correct priority is being given to input values from DAYMET */ SW_WTH_setup(); @@ -831,7 +844,10 @@ namespace { // Fill the first day of relative humidity with SW_MISSING so that // the desired calculation to be tested is able to trigger + // Along with the middle of January to test that monthly values + // are not used w->allHist[yearIndex]->r_humidity_daily[0] = SW_MISSING; + w->allHist[yearIndex]->r_humidity_daily[midJanDay] = SW_MISSING; // Using the new inputs folder, read in year = 1980 _read_weather_hist( @@ -865,6 +881,23 @@ namespace { // 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] + ); + // Make sure calculations and set input values are within reasonable range checkAllWeather(w); @@ -883,7 +916,7 @@ namespace { SW_WEATHER *w = &SW_Weather; double result, expectedResult; - int yearIndex = 0, year = 1980; + int yearIndex = 0, year = 1980, midJanDay = 14; /* Test correct priority is being given to input values from MACA */ @@ -928,6 +961,34 @@ namespace { // 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] + ); + // Make sure calculations and set input values are within reasonable range checkAllWeather(w); @@ -953,14 +1014,10 @@ namespace { /* Not the same number of flags as columns */ - // Set SW_WEATHER's n_input_forcings to a number that is - // not the columns being read in - - /* Check for value(s) that are not within reasonable range these - tests will make use of `checkAllWeather()` */ - 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 @@ -975,9 +1032,11 @@ namespace { ), "" ); - // Edit SW_WEATHER_HIST values from their original value + /* Check for value(s) that are not within reasonable range these + tests will make use of `checkAllWeather()` */ - // Make temperature unreasonable (not within [-100, 100] or max < min) + // Edit SW_WEATHER_HIST values from their original value + // Make temperature unreasonable (not within [-100, 100]) originVal = SW_Weather.allHist[0]->temp_max[0]; From 7c45d87a1b80355c0561bd3b28e86d76393a900a Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sat, 28 Jan 2023 15:06:38 -0700 Subject: [PATCH 306/326] New weather test: LOCF for new daily input variables - New test suite "DailyLOCF" - Tests that the function, `generateMissingWeather()`, performs LOCF on new daily weather input variables when they are SW_MISSING and the weather generator method is 1 - Specifically covers the first year cloud cover, actual vapor pressure, and wind speed --- tests/gtests/test_SW_Weather.cc | 51 +++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/gtests/test_SW_Weather.cc b/tests/gtests/test_SW_Weather.cc index 11af06a0e..b11260b35 100644 --- a/tests/gtests/test_SW_Weather.cc +++ b/tests/gtests/test_SW_Weather.cc @@ -996,6 +996,57 @@ namespace { 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()`). From 20e11e0134fa67e0def34499b9735a7fd7ffa849 Mon Sep 17 00:00:00 2001 From: Nicholas Persley Date: Sat, 28 Jan 2023 15:20:59 -0700 Subject: [PATCH 307/326] Fix `Window Cygwin` tests: Attempt 2 - Most weather tests now reset SOILWAT2 before they exit to make sure the next test that's called isn't using the modified globals the current test is using - "VegProd" tests in need, call `finalizeAllWeather()` Reasons for the additions of `finalizeAllWeather()` in VegProd tests: * `SW_WTH_new_day()` has previously gained the ability to test new daily weather input, so if any daily value in those variables is "SW_MISSING" the program crashes * When the monthly flag for humidity is set, we rely on actual vapor pressure being calculated in `finalizeAllWeather()` which is not called, thus actual vapor pressure is not calculated and `SW_WTH_new_day()` crashes --- tests/gtests/test_SW_VegProd.cc | 3 +++ tests/gtests/test_SW_Weather.cc | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/tests/gtests/test_SW_VegProd.cc b/tests/gtests/test_SW_VegProd.cc index 983d5e93a..3a6a57520 100644 --- a/tests/gtests/test_SW_VegProd.cc +++ b/tests/gtests/test_SW_VegProd.cc @@ -258,6 +258,7 @@ namespace { // Reset "SW_Weather.allHist" SW_WTH_read(); + finalizeAllWeather(&SW_Weather); // Allocate arrays needed for `calcSiteClimate()` and `averageClimateAcrossYears()` allocateClimateStructs(31, &climateOutput, &climateAverages); @@ -714,6 +715,7 @@ namespace { // Reset "SW_Weather.allHist" SW_WTH_read(); + finalizeAllWeather(&SW_Weather); // Allocate arrays needed for `calcSiteClimate()` and `averageClimateAcrossYears()` allocateClimateStructs(31, &climateOutput, &climateAverages); @@ -1173,6 +1175,7 @@ namespace { // Reset "SW_Weather.allHist" SW_WTH_read(); + finalizeAllWeather(&SW_Weather); // Allocate arrays needed for `calcSiteClimate()` and `averageClimateAcrossYears()` allocateClimateStructs(31, &climateOutput, &climateAverages); diff --git a/tests/gtests/test_SW_Weather.cc b/tests/gtests/test_SW_Weather.cc index b11260b35..da820e921 100644 --- a/tests/gtests/test_SW_Weather.cc +++ b/tests/gtests/test_SW_Weather.cc @@ -53,6 +53,8 @@ namespace { 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) { @@ -276,6 +278,8 @@ namespace { // ------ Reset and deallocate deallocateClimateStructs(&climateOutput, &climateAverages); + + Reset_SOILWAT2_after_UnitTest(); } @@ -395,6 +399,8 @@ namespace { // ------ Reset and deallocate deallocateClimateStructs(&climateOutput, &climateAverages); + Reset_SOILWAT2_after_UnitTest(); + } TEST(ClimateVariableTest, ClimateFromDefaultWeatherSouth) { @@ -515,6 +521,8 @@ namespace { // ------ Reset and deallocate deallocateClimateStructs(&climateOutput, &climateAverages); + + Reset_SOILWAT2_after_UnitTest(); } @@ -697,6 +705,8 @@ namespace { EXPECT_FLOAT_EQ(SW_Weather.allHist[0]->temp_max[0], -.52); + // Reset SOIWLAT2 + Reset_SOILWAT2_after_UnitTest(); } TEST(DailyInsteadOfMonthlyInputTest, MonthlyInputPrioritization) { @@ -722,6 +732,9 @@ namespace { 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) { From abf4f70597e8f10c4084c1cbf0358d34bf3d44ab Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 3 Feb 2023 12:44:43 -0700 Subject: [PATCH 308/326] `_read_weather_hist()` now also processes daily radiation inputs --- src/SW_Weather.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 49ebca058..691fd79b8 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -1834,6 +1834,11 @@ void _read_weather_hist( } + 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 From 12b123e81a2607e0692133a5295dd022efe320c9 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 3 Feb 2023 12:48:57 -0700 Subject: [PATCH 309/326] Test radiation inputs for DayMet, gridMET, and MACA examples - turn off monthly flags, i.e., don't use mean monthly inputs of cloud cover, wind speed, and humidity (and instead use daily inputs) - reset `allHist` data structure before reading daily values - expect that these datasets provide daily radiation values - expect that daily cloud cover values are missing - fix MACA example: 7th column of input files contain radiation (and not vapor pressure) inputs --- tests/gtests/test_SW_Weather.cc | 50 +++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/tests/gtests/test_SW_Weather.cc b/tests/gtests/test_SW_Weather.cc index da820e921..1304aeb6a 100644 --- a/tests/gtests/test_SW_Weather.cc +++ b/tests/gtests/test_SW_Weather.cc @@ -757,6 +757,11 @@ namespace { // 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: @@ -771,6 +776,9 @@ namespace { w->dailyInputFlags[SHORT_WR] = swTRUE; w->n_input_forcings = 7; + // Reset daily weather values + _clear_hist_weather(w->allHist[0]); + // Using the new inputs folder, read in year = 1980 _read_weather_hist( year, @@ -814,9 +822,16 @@ namespace { // 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(); } @@ -843,6 +858,11 @@ namespace { // 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 = 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: @@ -855,12 +875,8 @@ namespace { w->dailyInputFlags[REL_HUMID_MIN] = swFALSE; w->n_input_forcings = 5; - // Fill the first day of relative humidity with SW_MISSING so that - // the desired calculation to be tested is able to trigger - // Along with the middle of January to test that monthly values - // are not used - w->allHist[yearIndex]->r_humidity_daily[0] = SW_MISSING; - w->allHist[yearIndex]->r_humidity_daily[midJanDay] = SW_MISSING; + // Reset daily weather values + _clear_hist_weather(w->allHist[0]); // Using the new inputs folder, read in year = 1980 _read_weather_hist( @@ -911,6 +927,11 @@ namespace { 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); @@ -938,6 +959,11 @@ namespace { // 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: @@ -946,14 +972,17 @@ namespace { w->dailyInputIndices[WIND_NORTH] = 4; w->dailyInputIndices[REL_HUMID_MAX] = 5; w->dailyInputIndices[REL_HUMID_MIN] = 6; - w->dailyInputIndices[ACTUAL_VP] = 7; + 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[ACTUAL_VP] = swTRUE; + w->dailyInputFlags[SHORT_WR] = swTRUE; w->n_input_forcings = 8; + // Reset daily weather values + _clear_hist_weather(w->allHist[0]); + // Using the new inputs folder, read in year = 1980 _read_weather_hist( year, @@ -1002,6 +1031,11 @@ namespace { 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); From 4b8c0906d6a31fd1612ec3c4db7df4c0144a03bd Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 3 Feb 2023 14:39:33 -0700 Subject: [PATCH 310/326] New input: description of radiation inputs - new `desc_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 - input file "weathsetup.in" gains input for `"desc_rsds"` - struct "SW_WEATHER" gains element `desc_rsds` - `SW_WTH_setup()` now reads updated input file "weathsetup.in" and populates `desc_rsds` of `SW_Weather` - assign correct value for `SW_Weather.desc_rsds` in tests for DayMet, gridMET, and MACA data sets to complete specifications -- even if `SW_Weather.desc_rsds` is currently unused --- include/SW_Weather.h | 3 ++- src/SW_Weather.c | 7 ++++++- tests/example/Input/weathsetup.in | 12 +++++++++++- tests/gtests/test_SW_Weather.cc | 7 ++++--- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/include/SW_Weather.h b/include/SW_Weather.h index 3935522d3..6fd25695a 100644 --- a/include/SW_Weather.h +++ b/include/SW_Weather.h @@ -167,7 +167,8 @@ typedef struct { Bool dailyInputFlags[MAX_INPUT_COLUMNS]; unsigned int dailyInputIndices[MAX_INPUT_COLUMNS], - n_input_forcings; // Number of input columns found in weath.YYYY + 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 diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 691fd79b8..cf99f337a 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -1278,7 +1278,7 @@ void SW_WTH_new_day(void) { void SW_WTH_setup(void) { /* =================================================== */ SW_WEATHER *w = &SW_Weather; - const int nitems = 34; + const int nitems = 35; FILE *f; int lineno = 0, month, x, currFlag; Bool monthlyFlagPrioritized = swFALSE; @@ -1412,6 +1412,11 @@ void SW_WTH_setup(void) { w->dailyInputFlags[SHORT_WR] = itob(atoi(inbuf)); break; + case 22: + w->desc_rsds = atoi(inbuf); + break; + + default: if (lineno == 5 + MAX_MONTHS) break; diff --git a/tests/example/Input/weathsetup.in b/tests/example/Input/weathsetup.in index 21cd8591a..74e611c9d 100755 --- a/tests/example/Input/weathsetup.in +++ b/tests/example/Input/weathsetup.in @@ -41,7 +41,17 @@ 0 # Specific humidity [%] 0 # Dew point temperature [C] 0 # Actual vapor pressure [kPa] -0 # Downward surface shortwave radiation [W/m2], i.e., flux density for a (hypothetical) flat horizon averaged over the daylight period of the day +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. diff --git a/tests/gtests/test_SW_Weather.cc b/tests/gtests/test_SW_Weather.cc index 1304aeb6a..d51fcb14f 100644 --- a/tests/gtests/test_SW_Weather.cc +++ b/tests/gtests/test_SW_Weather.cc @@ -770,11 +770,12 @@ namespace { 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[WIND_SPEED] = 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]); @@ -871,9 +872,8 @@ namespace { w->dailyInputIndices[SHORT_WR] = 4; w->dailyInputFlags[ACTUAL_VP] = swTRUE; w->dailyInputFlags[SHORT_WR] = swTRUE; - w->dailyInputFlags[REL_HUMID_MAX] = swFALSE; - w->dailyInputFlags[REL_HUMID_MIN] = swFALSE; 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]); @@ -979,6 +979,7 @@ namespace { 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]); From ffe0482e0b49056e463b586234c0f7be498e5268 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 3 Feb 2023 15:32:42 -0700 Subject: [PATCH 311/326] `solar_radiation()` now handles observed radiation values and missing cloud cover - previous behavior * surface irradiation is estimated from extraterrestrial radiation (if observed downward surface shortwave radiation rsds is not available) * cloud effects are estimated as part of atmospheric attenuation using observed cloud cover - additional new behaviors: * cloud effects are ignored if surface irradiation is estimated from extraterrestrial radiation and if observed cloud cover is not available * surface irradiation is derived from observed downward surface shortwave radiation rsds, if available, based on Allen et al. 2006 * cloud cover is estimated from observed radiation and expected cloud-less radiation if observed radiation is available and if observed cloud_cover is not available -> cloud cover is required as argument for `petfunc()` - `solar_radiation()` gained arguments `rsds` (observed radiation) and `desc_rsds` (unit and definition descriptor of `rsds`) - `solar_radiation()` changed input argument `cloud_cover` to input and/or output argument `*cloud_cover` (output generated if cloud cover estimated from observed radiation) - new `actual_horizontal_transmissivityindex()` to calculate observed transmissivity index - `petfunc()` now fails if `cloudcov` is missing - `SW_WTH_new_day()` now requires that at least one cloud cover and radiation inputs is not missing (instead of requiring that cloud cover is never missing) - additional tests in suite "SW2SolarRadiationTest.global" * as previously: test without observed radiation: missing `rsds`; `H_gh` calculated * new: test with previously calculated `H_gh` and missing cloud cover -> returned cloud cover matches what was used to calculate input radation * new: test with observed radiation `rsds` and missing cloud cover - new test "WaterBalanceTest.WithGRIDMET" * uses gridMET example inputs (for two years) * reads daily inputs (and all mean monthly variables, i.e., humidity, cloud cover, wind speed, are turned off) * uses observed radiation to estimate missing cloud cover; estimated cloud cover is then used by `petfunc()` --- include/SW_Flow_lib_PET.h | 10 +- src/SW_Flow.c | 4 +- src/SW_Flow_lib_PET.c | 249 +++++++++++++++++++++++---- src/SW_Weather.c | 18 +- tests/gtests/test_SW_Flow_Lib_PET.cc | 152 +++++++++++++--- tests/gtests/test_WaterBalance.cc | 46 +++++ 6 files changed, 403 insertions(+), 76 deletions(-) diff --git a/include/SW_Flow_lib_PET.h b/include/SW_Flow_lib_PET.h index 0b248654f..915c16499 100644 --- a/include/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 e_a, - 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); diff --git a/src/SW_Flow.c b/src/SW_Flow.c index 3601cac27..5a398b0e6 100644 --- a/src/SW_Flow.c +++ b/src/SW_Flow.c @@ -406,8 +406,10 @@ void SW_Water_Flow(void) { SW_Site.slope, SW_Site.aspect, x, - w->now.cloudCover, + &w->now.cloudCover, w->now.actualVaporPressure, + w->now.shortWaveRad, + w->desc_rsds, &sw->H_oh, &sw->H_ot, &sw->H_gh diff --git a/src/SW_Flow_lib_PET.c b/src/SW_Flow_lib_PET.c index a801d58e1..0580a4224 100644 --- a/src/SW_Flow_lib_PET.c +++ b/src/SW_Flow_lib_PET.c @@ -16,6 +16,7 @@ #include #include "include/generic.h" #include "include/SW_Defines.h" +#include "include/filefuncs.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,26 +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,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 e_a, - 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, 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; @@ -826,56 +885,155 @@ double solar_radiation(unsigned int doy, // Calculate atmospheric pressure P = atmospheric_pressure(elev); - // Atmospheric attenuation: additional cloud effects - //k_c = overcast_attenuation_KastenCzeplak1980(cloud_cover / 100.); - - // 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.); - - + //--- 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 = 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 + 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 // Diffuse irradiation - K_dh = clearnessindex_diffuse(K_bh); - H_dh = K_dh * k_c * (*H_oh); // Allen et al. 2006: eq. 25 + k_c + K_dh_calc = clearnessindex_diffuse(K_bh_calc); + H_dh_calc = K_dh_calc * (*H_oh); // Allen et al. 2006: eq. 25 + - // Global horizontal irradiation: Allen et al. 2006: eq. 23 - *H_gh = H_bh + H_dh; + //--- Global horizontal irradiation + if (missing(rsds)) { + // Atmospheric attenuation: additional cloud effects + if (missing(*cloud_cover)) { + k_c = 1.; // ignore additional cloud cover effects + } else { + //k_c = overcast_attenuation_KastenCzeplak1980(*cloud_cover / 100.); + + // 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 + + // 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.); + } + // 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.0432 * dl; + break; + + default: + LogError( + logfp, + LOGFATAL, + "`desc_rsds` has an unrecognized value: %u", + desc_rsds + ); + } + + *H_gh = convert_rsds_to_H_gh * rsds; + + + //--- 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 - // Reflected irradiation; Allen et al. 2006: eq. 36 - H_rt = albedo * (1. - f_i) * (*H_gh); + } 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); - //--- Daily global titled irradiation: Allen et al. 2006: eq. 29 + // 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 + // 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 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 { @@ -884,6 +1042,16 @@ double solar_radiation(unsigned int doy, } + // Check for valid range of radiation [MJ/m2] + if (!(H_g >= 0. && H_g <= 45.)) { + LogError( + logfp, + LOGFATAL, + "\nSolar radiation (%f) out of valid range (0-45 MJ m-2)\n", + H_g + ); + } + return H_g; } @@ -1104,6 +1272,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/src/SW_Weather.c b/src/SW_Weather.c index cf99f337a..462e1ee0f 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -1225,20 +1225,22 @@ void SW_WTH_new_day(void) { */ /* get the daily weather from allHist */ - /* - Note: Shortwave radiation needs to check if it was input, this is due to the - fact that shortwave radiation is not necessary for any calculations in `_read_weather_hist()` - and does not get calculated within `_read_weather_hist()` and is thus expected to - hold "SW_MISSING" values if daily input is not provided. + + /* 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]->cloudcov_daily[day]) || missing(w->allHist[yearIndex]->windspeed_daily[day]) || missing(w->allHist[yearIndex]->r_humidity_daily[day]) || - (missing(w->allHist[yearIndex]->shortWaveRad[day]) && w->dailyInputFlags[SHORT_WR]) || - missing(w->allHist[yearIndex]->actualVaporPressure[day]) + missing(w->allHist[yearIndex]->actualVaporPressure[day]) || + ( + missing(w->allHist[yearIndex]->shortWaveRad[day]) && + missing(w->allHist[yearIndex]->cloudcov_daily[day]) + ) ) { LogError( logfp, diff --git a/tests/gtests/test_SW_Flow_Lib_PET.cc b/tests/gtests/test_SW_Flow_Lib_PET.cc index 12bc16aa0..6e5f4f218 100644 --- a/tests/gtests/test_SW_Flow_Lib_PET.cc +++ b/tests/gtests/test_SW_Flow_Lib_PET.cc @@ -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,43 +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.}, + 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) 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}, - // Actual vapor pressure (kPa) - actual_vap_pressure; + {-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 @@ -752,22 +766,88 @@ namespace 60 * deg_to_rad, // slope 0., // aspect albedo[k], - cloud_cover[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"; } @@ -815,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., @@ -849,7 +932,8 @@ namespace H_gt = solar_radiation( doy, lat, elev, slope0, aspect, reflec, - cloudcov, actual_vap_pressure, + &cloudcov, actual_vap_pressure, + rsds, desc_rsds, &H_oh, &H_ot, &H_gh ); @@ -873,7 +957,8 @@ namespace H_gt = solar_radiation( doy, lats[i] * deg_to_rad, elev, slope0, aspect, reflec, - cloudcov, e_a, + &cloudcov, e_a, + rsds, desc_rsds, &H_oh, &H_ot, &H_gh ); @@ -898,7 +983,8 @@ namespace H_gt = solar_radiation( doy, lat, elevs[i], slope0, aspect, reflec, - cloudcov, e_a, + &cloudcov, e_a, + rsds, desc_rsds, &H_oh, &H_ot, &H_gh ); @@ -920,7 +1006,8 @@ namespace H_gt = solar_radiation( doy, lat, elev, slopes[i] * deg_to_rad, aspect, reflec, - cloudcov, e_a, + &cloudcov, e_a, + rsds, desc_rsds, &H_oh, &H_ot, &H_gh ); @@ -947,7 +1034,8 @@ namespace H_gt = solar_radiation( doy, lat, elev, sloped, aspects[i] * deg_to_rad, reflec, - cloudcov, e_a, + &cloudcov, e_a, + rsds, desc_rsds, &H_oh, &H_ot, &H_gh ); @@ -971,7 +1059,8 @@ namespace H_gt = solar_radiation( doy, lat, elev, sloped, aspect, reflecs[i], - cloudcov, e_a, + &cloudcov, e_a, + rsds, desc_rsds, &H_oh, &H_ot, &H_gh ); @@ -995,7 +1084,8 @@ namespace H_gt = solar_radiation( doy, lat, elev, slope0, aspect, reflec, - cloudcov, actual_vap_pressure, + &cloudcov, actual_vap_pressure, + rsds, desc_rsds, &H_oh, &H_ot, &H_gh ); @@ -1015,7 +1105,8 @@ namespace H_gt = solar_radiation( doy, lat, elev, slope0, aspect, reflec, - cloudcov, e_a, + &cloudcov, e_a, + rsds, desc_rsds, &H_oh, &H_ot, &H_gh ); @@ -1040,7 +1131,8 @@ namespace H_gt = solar_radiation( doy, lat, elev, slope0, aspect, reflec, - cloudcovs[i], e_a, + &cloudcovs[i], e_a, + rsds, desc_rsds, &H_oh, &H_ot, &H_gh ); @@ -1068,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., @@ -1120,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/tests/gtests/test_WaterBalance.cc b/tests/gtests/test_WaterBalance.cc index a9bff44eb..f7f50921d 100644 --- a/tests/gtests/test_WaterBalance.cc +++ b/tests/gtests/test_WaterBalance.cc @@ -290,4 +290,50 @@ namespace { 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(); + } + + } // namespace From 8b290ac8c37ff47c647683c4fffe2810465993e6 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 3 Feb 2023 15:46:55 -0700 Subject: [PATCH 312/326] Correctly print variables of type `size_t` - close #334 - issue #334 was expressed if "SWDEBUG" was defined and if module "SW_Output_outarray" was compiled (i.e., for STEPWAT2 and rSOILWAT2) - use correct format identifier "%zu" for variables of type `size_t` -- instead of "%ld" (that may or may not work) --- src/SW_Output_outarray.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/SW_Output_outarray.c b/src/SW_Output_outarray.c index 049e59e17..c39ba5d3d 100644 --- a/src/SW_Output_outarray.c +++ b/src/SW_Output_outarray.c @@ -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 } From 3207d3f9d62d37b90340e207e1e4aa4e586d8060 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 3 Feb 2023 15:51:32 -0700 Subject: [PATCH 313/326] New make target to run tests repeatedly and randomly shuffled --- makefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/makefile b/makefile index 33cd8e09e..6732e9840 100644 --- a/makefile +++ b/makefile @@ -31,6 +31,8 @@ # 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 bin_debug similar to `make bin_run` with debug settings; # consider cleaning previous build artifacts beforehand, @@ -351,6 +353,10 @@ test_severe : test_leaks : ./tools/run_test_leaks.sh +.PHONY : test_reprnd +test_reprnd : test + $(bin_test) --gtest_shuffle --gtest_repeat=-1 + .PHONY : bin_debug bin_debug : ./tools/run_debug.sh From 821f4039cd48b79bbb61ab86212fdf7f7c995f19 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Tue, 7 Feb 2023 09:50:39 -0500 Subject: [PATCH 314/326] Close all files during tests - addressing #342 * this issue is a bug for SOILWAT2 tests but not for other setups ** SOILWAT2 simulations close the log-file `logfp`: `main()` calls `check_log()` at exit ** STEPWAT2 and rSOILWAT2 have their own `logfp` mechanisms: `SW_F_read()` does not assign a (new) value (based on "files.in") to `logfp` (if compiled as STEPWAT2 or rSOILWAT2) - new approach for SOILWAT2 tests to close log files * immediately close the `logfp` file connection that was created by `SW_F_read()` * set `logfp` to NULL to silence non-error messages during tests * update `LogError()` to behave as it already does for rSOILWAT2, i.e., if silenced, write only if error (message sent stderr) and otherwise write all message to `logfp` as previously Specific changes: - `check_log()` is now public, moved to module SW_Main_lib, and renamed to `sw_check_log()` to avoid a potential name conflict with STEPWAT2's `check_log()` - `sw_check_log()` is now closing a `logfp` file connection before potentially exiting SOILWAT2 with a call to `sw_error()` - `LogError()` now handles NULL values of `logfp` even if not rSOILWAT2, i.e., don't write messages to `logfp` if NULL -- unless it is an error, then write message to stderr - moved handling of `logfp` from `sw_maintest.cc::main()` to `Reset_SOILWAT2_after_UnitTest()` * immediately close the `logfp` file connection that was created by `SW_F_read()` * set `logfp` to NULL to silence non-error messages during tests - DeathTests now expect a specific message --- include/SW_Main_lib.h | 2 +- src/SW_Main.c | 15 +--------- src/SW_Main_lib.c | 16 +++++++++++ src/filefuncs.c | 39 ++++++++++++++++++-------- tests/gtests/sw_maintest.cc | 21 +------------- tests/gtests/sw_testhelpers.cc | 40 +++++++++++++++++---------- tests/gtests/test_SW_Flow_lib_temp.cc | 4 +-- tests/gtests/test_SW_Markov.cc | 2 +- tests/gtests/test_SW_Site.cc | 18 ++++++++---- tests/gtests/test_SW_SoilWater.cc | 14 +++++----- tests/gtests/test_SW_VegProd.cc | 2 +- tests/gtests/test_SW_Weather.cc | 2 +- 12 files changed, 97 insertions(+), 78 deletions(-) diff --git a/include/SW_Main_lib.h b/include/SW_Main_lib.h index a94465a77..149d93186 100644 --- a/include/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/src/SW_Main.c b/src/SW_Main.c index 538747bf4..a15d44e73 100644 --- a/src/SW_Main.c +++ b/src/SW_Main.c @@ -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); diff --git a/src/SW_Main_lib.c b/src/SW_Main_lib.c index 5b74d90cf..972f20030 100644 --- a/src/SW_Main_lib.c +++ b/src/SW_Main_lib.c @@ -207,3 +207,19 @@ void sw_init_args(int argc, char **argv) { } /* 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/src/filefuncs.c b/src/filefuncs.c index 5d4e689f4..06ec79a4e 100644 --- a/src/filefuncs.c +++ b/src/filefuncs.c @@ -149,6 +149,7 @@ void LogError(FILE *fp, const int mode, const char *fmt, ...) { */ char outfmt[MAX_ERROR] = {0}; /* to prepend err type str */ + char buf[MAX_ERROR]; va_list args; va_start(args, fmt); @@ -166,26 +167,42 @@ void LogError(FILE *fp, const int mode, const char *fmt, ...) { strcat(outfmt, "\n"); #ifdef RSOILWAT - char buf[1024]; + vsnprintf(buf, MAX_ERROR, outfmt, args); - vsnprintf(buf, sizeof buf, outfmt, args); - - if(LOGEXIT & mode) { + if (LOGEXIT & mode) { + // exit with error and always show message error(buf); + } else if(fp != NULL) { + // send warning message only if not silenced (fp is not NULL) warning(buf); } #else - int check_eof; - check_eof = (EOF == vfprintf(fp, outfmt, args)); + 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); + } - if (check_eof) - sw_error(0, "SYSTEM: Cannot write to FILE *fp in LogError()\n"); - fflush(fp); - if (LOGEXIT & mode) { - sw_error(-1, "@ generic.c LogError"); + } else { + // send message to fp + + 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"); + } + + fflush(fp); + + if (LOGEXIT & mode) { + // exit with error + sw_error(-1, "@ generic.c LogError"); + } } #endif diff --git a/tests/gtests/sw_maintest.cc b/tests/gtests/sw_maintest.cc index 8e0e0a603..5c42b6ed3 100644 --- a/tests/gtests/sw_maintest.cc +++ b/tests/gtests/sw_maintest.cc @@ -15,25 +15,9 @@ #include #include -#include "include/myMemory.h" // externs `*logfp`, `errstr`, `logged`, `QuietMode`, `EchoInits` #include "include/generic.h" #include "include/filefuncs.h" // externs `_firstfile`, `inbuf` -#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" @@ -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/tests/gtests/sw_testhelpers.cc b/tests/gtests/sw_testhelpers.cc index f9936a00e..839e8720f 100644 --- a/tests/gtests/sw_testhelpers.cc +++ b/tests/gtests/sw_testhelpers.cc @@ -15,25 +15,14 @@ #include #include -#include "include/myMemory.h" // externs `*logfp`, `errstr`, `logged`, `QuietMode`, `EchoInits` #include "include/generic.h" #include "include/filefuncs.h" // externs `_firstfile`, `inbuf` -#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 "include/SW_Main_lib.h" // for `sw_check_log()` #include "tests/gtests/sw_testhelpers.h" @@ -42,18 +31,41 @@ /** 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(); + // ... } diff --git a/tests/gtests/test_SW_Flow_lib_temp.cc b/tests/gtests/test_SW_Flow_lib_temp.cc index c6ca28e2d..519dc6ca8 100644 --- a/tests/gtests/test_SW_Flow_lib_temp.cc +++ b/tests/gtests/test_SW_Flow_lib_temp.cc @@ -192,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 @@ -709,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/tests/gtests/test_SW_Markov.cc b/tests/gtests/test_SW_Markov.cc index 084fd82a4..ccf82d900 100644 --- a/tests/gtests/test_SW_Markov.cc +++ b/tests/gtests/test_SW_Markov.cc @@ -182,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 index 6363ca59f..0c47c17ee 100644 --- a/tests/gtests/test_SW_Site.cc +++ b/tests/gtests/test_SW_Site.cc @@ -144,7 +144,7 @@ namespace { gravel, bdensity ), - "@ generic.c LogError" + "PTF is not implemented in SOILWAT2" ); } @@ -239,7 +239,7 @@ namespace { EXPECT_DEATH_IF_SUPPORTED( SWRC_check_parameters(swrc_type, swrcp), - "@ generic.c LogError" + "is not implemented" ); } @@ -444,12 +444,18 @@ namespace { // 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"); + 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(), "@ generic.c LogError"); + EXPECT_DEATH_IF_SUPPORTED( + SW_SIT_init_run(), + "'transpiration coefficient' has an invalid value" + ); // Reset to previous global states Reset_SOILWAT2_after_UnitTest(); @@ -617,7 +623,7 @@ namespace { // Check error if bulk density too low for coarse fragments EXPECT_DEATH_IF_SUPPORTED( calculate_soilMatricDensity(1.65, 0.7), - "@ generic.c LogError" + "is lower than expected" ); @@ -626,7 +632,7 @@ namespace { EXPECT_DEATH_IF_SUPPORTED( SW_SIT_init_run(), - "@ generic.c LogError" + "Soil density type not recognized" ); diff --git a/tests/gtests/test_SW_SoilWater.cc b/tests/gtests/test_SW_SoilWater.cc index 94225368d..7abc79f75 100644 --- a/tests/gtests/test_SW_SoilWater.cc +++ b/tests/gtests/test_SW_SoilWater.cc @@ -275,7 +275,7 @@ namespace{ swrc_type = N_SWRCs + 1; EXPECT_DEATH_IF_SUPPORTED( SWRC_SWCtoSWP(1., swrc_type, swrcp, gravel, width, LOGFATAL), - "@ generic.c LogError" + "is not implemented" ); EXPECT_DOUBLE_EQ( SWRC_SWCtoSWP(1., swrc_type, swrcp, gravel, width, LOGWARN), @@ -287,7 +287,7 @@ namespace{ for (swrc_type = 0; swrc_type < N_SWRCs; swrc_type++) { EXPECT_DEATH_IF_SUPPORTED( SWRC_SWCtoSWP(-1., swrc_type, swrcp, gravel, width, LOGFATAL), - "@ generic.c LogError" + "invalid SWC" ); EXPECT_DOUBLE_EQ( SWRC_SWCtoSWP(-1., swrc_type, swrcp, gravel, width, LOGWARN), @@ -296,7 +296,7 @@ namespace{ EXPECT_DEATH_IF_SUPPORTED( SWRC_SWCtoSWP(1., swrc_type, swrcp, 1., width, LOGFATAL), - "@ generic.c LogError" + "invalid SWC" ); EXPECT_DOUBLE_EQ( SWRC_SWCtoSWP(1., swrc_type, swrcp, 1., width, LOGWARN), @@ -305,7 +305,7 @@ namespace{ EXPECT_DEATH_IF_SUPPORTED( SWRC_SWCtoSWP(1., swrc_type, swrcp, gravel, 0., LOGFATAL), - "@ generic.c LogError" + "invalid SWC" ); EXPECT_DOUBLE_EQ( SWRC_SWCtoSWP(1., swrc_type, swrcp, gravel, 0., LOGWARN), @@ -325,7 +325,7 @@ namespace{ EXPECT_DEATH_IF_SUPPORTED( SWRC_SWCtoSWP(0.99 * swrcp[0], swrc_type, swrcp, gravel, width, LOGFATAL), - "@ generic.c LogError" + "invalid value" ); EXPECT_DOUBLE_EQ( SWRC_SWCtoSWP(0.99 * swrcp[0], swrc_type, swrcp, gravel, width, LOGWARN), @@ -352,7 +352,7 @@ namespace{ swrc_type = N_SWRCs + 1; EXPECT_DEATH_IF_SUPPORTED( SWRC_SWPtoSWC(15., swrc_type, swrcp, gravel, width, LOGFATAL), - "@ generic.c LogError" + "is not implemented" ); EXPECT_DOUBLE_EQ( SWRC_SWPtoSWC(15., swrc_type, swrcp, gravel, width, LOGWARN), @@ -363,7 +363,7 @@ namespace{ for (swrc_type = 0; swrc_type < N_SWRCs; swrc_type++) { EXPECT_DEATH_IF_SUPPORTED( SWRC_SWPtoSWC(-1., swrc_type, swrcp, gravel, width, LOGFATAL), - "@ generic.c LogError" + "invalid SWP" ); EXPECT_DOUBLE_EQ( SWRC_SWPtoSWC(-1., swrc_type, swrcp, gravel, width, LOGWARN), diff --git a/tests/gtests/test_SW_VegProd.cc b/tests/gtests/test_SW_VegProd.cc index 983d5e93a..eb797d276 100644 --- a/tests/gtests/test_SW_VegProd.cc +++ b/tests/gtests/test_SW_VegProd.cc @@ -1193,7 +1193,7 @@ namespace { 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" ); /* =============================================================== diff --git a/tests/gtests/test_SW_Weather.cc b/tests/gtests/test_SW_Weather.cc index 8606b6b87..a5784fed0 100644 --- a/tests/gtests/test_SW_Weather.cc +++ b/tests/gtests/test_SW_Weather.cc @@ -154,7 +154,7 @@ namespace { // 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(); From b628d1687b9fa1f49a8665a5f855038a22ab6819 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Tue, 7 Feb 2023 10:22:46 -0500 Subject: [PATCH 315/326] GHA: shuffle and repeat tests 3x - new make target "test_rep3rnd" - shuffling and repeating tests checks that unit tests are self-sufficient and don't have undesired side-effects compromising other tests - see also `make test_reprnd` target where tests are rep[eated] r[a]nd[omly] until user interruption --- .github/workflows/main_nix.yml | 4 ++-- .github/workflows/main_win.yml | 4 ++-- makefile | 6 ++++++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main_nix.yml b/.github/workflows/main_nix.yml index 0bf2e3867..4e6c43890 100644 --- a/.github/workflows/main_nix.yml +++ b/.github/workflows/main_nix.yml @@ -32,8 +32,8 @@ jobs: make clean bin_run make clean bin_debug_severe - - name: Unit tests - run: make clean test_run + - name: Unit tests (shuffle and repeat 3x) + run: make clean test_rep3rnd - name: Severe unit tests (without sanitizers) if: ${{ runner.os == 'macOS' }} diff --git a/.github/workflows/main_win.yml b/.github/workflows/main_win.yml index a6f13f8ef..67974db47 100644 --- a/.github/workflows/main_win.yml +++ b/.github/workflows/main_win.yml @@ -42,5 +42,5 @@ jobs: - name: Run binary run: make clean bin_run - - name: Unit tests - run: make clean test_run + - name: Unit tests (shuffle and repeat 3x) + run: make clean test_rep3rnd diff --git a/makefile b/makefile index 6732e9840..7d7e3b283 100644 --- a/makefile +++ b/makefile @@ -33,6 +33,8 @@ # 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 similar to `make bin_run` with debug settings; # consider cleaning previous build artifacts beforehand, @@ -357,6 +359,10 @@ test_leaks : 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 From 1cba98f864b8b96ed1a76df831b0639ffdf75460 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Mon, 20 Feb 2023 08:39:20 -0500 Subject: [PATCH 316/326] All death tests now expect informative message patterns - provided more informative message patterns for new death tests -- utilizing updated `logError()` functionality from commit 8f33a5480a8d3e73e981ce2bed9466bf46af0f35 - fixed error message typo in `checkAllWeather()`: "minumum" -> "minimum" --- src/SW_Weather.c | 2 +- tests/gtests/test_SW_VegProd.cc | 2 +- tests/gtests/test_SW_Weather.cc | 9 +++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index 49ebca058..f06a87a35 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -978,7 +978,7 @@ void checkAllWeather(SW_WEATHER *weather) { dailyMinTemp > dailyMaxTemp) { // Fail - LogError(logfp, LOGFATAL, "Daily input value for minumum temperature" + 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); } diff --git a/tests/gtests/test_SW_VegProd.cc b/tests/gtests/test_SW_VegProd.cc index e576fc66a..c10f25630 100644 --- a/tests/gtests/test_SW_VegProd.cc +++ b/tests/gtests/test_SW_VegProd.cc @@ -1224,7 +1224,7 @@ namespace { 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 diff --git a/tests/gtests/test_SW_Weather.cc b/tests/gtests/test_SW_Weather.cc index 803660a2f..3975aa9bb 100644 --- a/tests/gtests/test_SW_Weather.cc +++ b/tests/gtests/test_SW_Weather.cc @@ -1093,7 +1093,8 @@ namespace { w->n_input_forcings, w->dailyInputIndices, w->dailyInputFlags - ), "" + ), + "Incomplete record 1" ); /* Check for value(s) that are not within reasonable range these @@ -1108,7 +1109,7 @@ namespace { 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) @@ -1120,7 +1121,7 @@ namespace { EXPECT_DEATH_IF_SUPPORTED( checkAllWeather(w), - "" + "Invalid daily precipitation value" ); // Make relative humidity unreasonable (< 0%) @@ -1130,7 +1131,7 @@ namespace { EXPECT_DEATH_IF_SUPPORTED( checkAllWeather(w), - "" + "relative humidity value did not fall in the range" ); // Reset SOILWAT2 for next test From d211ccf8de9d96d9d3d53edb3e5a670c18b8a88f Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Fri, 26 May 2023 15:48:33 -0400 Subject: [PATCH 317/326] New `check_and_update_dailyInputFlags()` - moved code chunk that was checking and updating daily (meteorological) input flags from `SW_WTH_setup()` to dedicated new `check_and_update_dailyInputFlags()` -> rSOILWAT2 is utilizing the same code from its own `onSet_SW_WTH_setup()` equivalence to `SW_WTH_setup() --- include/SW_Weather.h | 6 ++++ src/SW_Weather.c | 74 ++++++++++++++++++++++++++------------------ 2 files changed, 50 insertions(+), 30 deletions(-) diff --git a/include/SW_Weather.h b/include/SW_Weather.h index 6fd25695a..be31502e1 100644 --- a/include/SW_Weather.h +++ b/include/SW_Weather.h @@ -197,6 +197,12 @@ extern SW_WEATHER SW_Weather; /* Global Function Declarations */ /* --------------------------------------------------- */ void SW_WTH_setup(void); +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); diff --git a/src/SW_Weather.c b/src/SW_Weather.c index ee62e980f..d5dfc6e14 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -1283,7 +1283,6 @@ void SW_WTH_setup(void) { const int nitems = 35; FILE *f; int lineno = 0, month, x, currFlag; - Bool monthlyFlagPrioritized = swFALSE; RealF sppt, stmax, stmin; RealF sky, wind, rH, actVP, shortWaveRad; @@ -1451,23 +1450,6 @@ void SW_WTH_setup(void) { SW_WeatherPrefix(w->name_prefix); CloseFile(&f); - // 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 (lineno < nitems) { LogError(logfp, LOGFATAL, "%s : Too few input lines.", MyFileName); } @@ -1495,19 +1477,51 @@ void SW_WTH_setup(void) { } } - /* - * 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_and_update_dailyInputFlags( + w->use_windSpeedMonthly, + w->use_humidityMonthly, + w->use_windSpeedMonthly, + dailyInputFlags + ); +} - * 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. - */ +/* + * 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(w->use_windSpeedMonthly && (dailyInputFlags[WIND_SPEED] || + if(use_windSpeedMonthly && (dailyInputFlags[WIND_SPEED] || dailyInputFlags[WIND_EAST] || dailyInputFlags[WIND_NORTH])) { @@ -1517,7 +1531,7 @@ void SW_WTH_setup(void) { monthlyFlagPrioritized = swTRUE; } - if(w->use_humidityMonthly) { + if(use_humidityMonthly) { if(dailyInputFlags[REL_HUMID] || dailyInputFlags[REL_HUMID_MAX] || dailyInputFlags[REL_HUMID_MIN] || dailyInputFlags[SPEC_HUMID] || dailyInputFlags[ACTUAL_VP]) { @@ -1531,7 +1545,7 @@ void SW_WTH_setup(void) { } } - if(w->use_cloudCoverMonthly && dailyInputFlags[CLOUD_COV]) { + if(use_cloudCoverMonthly && dailyInputFlags[CLOUD_COV]) { dailyInputFlags[CLOUD_COV] = swFALSE; monthlyFlagPrioritized = swTRUE; } From 9c8276f25f9164ced5d21adc08661d22ac7d315d Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 31 May 2023 10:09:00 -0400 Subject: [PATCH 318/326] Fix call to `check_and_update_dailyInputFlags()` - previous commit d211ccf8de9d96d9d3d53edb3e5a670c18b8a88f called `check_and_update_dailyInputFlags()` with incorrect arguments ... --- src/SW_Weather.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index d5dfc6e14..a2ec6e250 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -1478,7 +1478,7 @@ void SW_WTH_setup(void) { } check_and_update_dailyInputFlags( - w->use_windSpeedMonthly, + w->use_cloudCoverMonthly, w->use_humidityMonthly, w->use_windSpeedMonthly, dailyInputFlags From 52adf3afa3b8b4e37790627b4a623ec63ebe9925 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Wed, 31 May 2023 10:20:52 -0400 Subject: [PATCH 319/326] New `set_dailyInputIndices()` - moved code chunk that was setting column indices for and counting of daily (meteorological) input flags from `SW_WTH_setup()` to dedicated new `set_dailyInputIndices()` -> rSOILWAT2 is utilizing the same code from its own `rSW2_readAllWeatherFromDisk()` equivalence to `SW_WTH_read() --- include/SW_Weather.h | 5 +++++ src/SW_Weather.c | 49 ++++++++++++++++++++++++++++++++------------ 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/include/SW_Weather.h b/include/SW_Weather.h index be31502e1..05d5f55ba 100644 --- a/include/SW_Weather.h +++ b/include/SW_Weather.h @@ -197,6 +197,11 @@ 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, diff --git a/src/SW_Weather.c b/src/SW_Weather.c index a2ec6e250..af23fd8ce 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -1282,7 +1282,7 @@ void SW_WTH_setup(void) { SW_WEATHER *w = &SW_Weather; const int nitems = 35; FILE *f; - int lineno = 0, month, x, currFlag; + int lineno = 0, month, x; RealF sppt, stmax, stmin; RealF sky, wind, rH, actVP, shortWaveRad; @@ -1455,34 +1455,57 @@ void SW_WTH_setup(void) { } // 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 - w->n_input_forcings = 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 - w->dailyInputIndices[currFlag] = 0; + dailyInputIndices[currFlag] = 0; // Check if current flag is set - if(dailyInputFlags[currFlag]) { + if (dailyInputFlags[currFlag]) { // Set current index to current number of "n_input_forcings" // which is the current number of flags found - w->dailyInputIndices[currFlag] = w->n_input_forcings; + dailyInputIndices[currFlag] = *n_input_forcings; // Increment "n_input_forcings" - w->n_input_forcings++; + (*n_input_forcings)++; } } - - check_and_update_dailyInputFlags( - w->use_cloudCoverMonthly, - w->use_humidityMonthly, - w->use_windSpeedMonthly, - dailyInputFlags - ); } From 9a4fa8dda68f6f809facfca6d75b222860f573f5 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Thu, 1 Jun 2023 19:25:03 -0400 Subject: [PATCH 320/326] Fix `SW_WTH_read()`: use correct monthly use flags - previously, the call to `readAllWeather()` mixed up monthly use flags for wind speed and humidity -> also fix unit tests for "DailyWeatherInputTest.DailyDayMet" which incorrectly had monthly wind speed use turned off --- src/SW_Weather.c | 2 +- tests/gtests/test_SW_Weather.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index af23fd8ce..fda43c6e8 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -1633,8 +1633,8 @@ void SW_WTH_read(void) { SW_Weather.use_weathergenerator_only, SW_Weather.name_prefix, SW_Weather.use_cloudCoverMonthly, - SW_Weather.use_windSpeedMonthly, SW_Weather.use_humidityMonthly, + SW_Weather.use_windSpeedMonthly, SW_Weather.n_input_forcings, SW_Weather.dailyInputIndices, SW_Weather.dailyInputFlags, diff --git a/tests/gtests/test_SW_Weather.cc b/tests/gtests/test_SW_Weather.cc index 2e0fad1f2..9590b720f 100644 --- a/tests/gtests/test_SW_Weather.cc +++ b/tests/gtests/test_SW_Weather.cc @@ -861,7 +861,7 @@ namespace { // Turn off monthly flags w->use_cloudCoverMonthly = swFALSE; - w->use_windSpeedMonthly = swFALSE; + w->use_windSpeedMonthly = swTRUE; w->use_humidityMonthly = swFALSE; // Manually edit index/flag arrays in SW_WEATHER to make test as From eaa648422964767d7d073c1ecfad9feffd0785f6 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Thu, 1 Jun 2023 19:26:45 -0400 Subject: [PATCH 321/326] New unit tests to run simulation with Daymet and with MACA inputs - previously, unit tests run a simulation only with "standard" inputs and with "gridMET" inputs -> now run tests all supported datasets -> Daymet unit tests currently fail due to radiation values being out of reasonable range --- tests/gtests/test_WaterBalance.cc | 88 +++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/tests/gtests/test_WaterBalance.cc b/tests/gtests/test_WaterBalance.cc index f7f50921d..69869210f 100644 --- a/tests/gtests/test_WaterBalance.cc +++ b/tests/gtests/test_WaterBalance.cc @@ -291,6 +291,47 @@ namespace { } + 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; @@ -336,4 +377,51 @@ namespace { } + 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 From 35bbc96c7951fcd4c8660b76a4998947a04324b1 Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Thu, 1 Jun 2023 19:32:30 -0400 Subject: [PATCH 322/326] Fix `solar_radiation()` for desc_rsds = 2 (flux density during daylight) - previously, `solar_radiation()` converted radiation inputs as flux density during daylight with a factor that was too large by the factor pi - previously, the documentation wrote out the correct factor but the implemented value was incorrect - additional and better documented checks to make sure that input radiation and calculated radiation have reasonable values -> simulation run unit tests with Daymet inputs are now passing (see previous commit eaa648422964767d7d073c1ecfad9feffd0785f6) --- src/SW_Flow_lib_PET.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/SW_Flow_lib_PET.c b/src/SW_Flow_lib_PET.c index 0580a4224..e051e46c1 100644 --- a/src/SW_Flow_lib_PET.c +++ b/src/SW_Flow_lib_PET.c @@ -945,7 +945,7 @@ double solar_radiation( // 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.0432 * dl; + convert_rsds_to_H_gh = 0.01375099 * dl; break; default: @@ -960,6 +960,21 @@ double solar_radiation( *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 @@ -1042,12 +1057,14 @@ double solar_radiation( } - // Check for valid range of radiation [MJ/m2] - if (!(H_g >= 0. && H_g <= 45.)) { + // 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-45 MJ m-2)\n", + "\nSolar radiation (%f) out of valid range (0-50 MJ m-2)\n", H_g ); } From 014686d3c395570d0f96c8648a2aff76dba3755b Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Thu, 1 Jun 2023 19:46:15 -0400 Subject: [PATCH 323/326] Improve `generateMissingWeather()` handling of new daily inputs - previously, `generateMissingWeather()` with method = 2, i.e., weather generator, was replacing tmax, tmin, and ppt (with generated values) if any of the eight variables (including the five new ones) are missing -> now, `generateMissingWeather()` with method = 2, i.e., weather generator, is replacing tmax, tmin, and ppt (with generated values) if any of tmax, tmin, and ppt are missing (ignoring the ones not considered by the weather generator) - clarify function documentation --- src/SW_Weather.c | 60 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/src/SW_Weather.c b/src/SW_Weather.c index fda43c6e8..7ccfe6637 100644 --- a/src/SW_Weather.c +++ b/src/SW_Weather.c @@ -779,22 +779,32 @@ void scaleAllWeather( 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) + 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) - - for minimum and maximum temperature - - cloud cover - - wind speed - - relative humidity - - downard surface shortwave radiation - - actual vapor pressure - - precipitation is set to 0 + - 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 @@ -827,8 +837,12 @@ void generateMissingWeather( yesterdayCloudCov = 0., yesterdayWindSpeed = 0., yesterdayRelHum = 0., yesterdayShortWR = 0., yesterdayActVP = 0.; - Bool any_missing, missing_Tmax, missing_Tmin, missing_PPT, missing_CloudCov, - missing_WindSpeed, missing_RelHum, missing_ShortWR, missing_ActVP; + 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 @@ -856,11 +870,15 @@ void generateMissingWeather( 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]); - 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]); + + 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 || @@ -1245,9 +1263,17 @@ void SW_WTH_new_day(void) { LogError( logfp, LOGFATAL, - "Missing weather data (day %u - %u) during simulation.", + "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 + 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] ); } From e67cbc60cc6152dbd444c98d1579cd16c6b91fef Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Thu, 1 Jun 2023 20:03:53 -0400 Subject: [PATCH 324/326] Update NEWS to reflect updated daily weather inputs --- NEWS.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/NEWS.md b/NEWS.md index edb5f60c8..b53fc7501 100644 --- a/NEWS.md +++ b/NEWS.md @@ -32,6 +32,26 @@ 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). @@ -71,6 +91,12 @@ ## 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 (optiona) 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 From 62521583f21b8bd829b3b868fef20da7a7973eda Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Sat, 3 Jun 2023 15:43:49 -0400 Subject: [PATCH 325/326] Fix `mvnorm()`: avoid NaN as tmin output - previously, `mvnorm()` was producing at times a NaN value for minimum air temperature - for instance, "rSOILWAT2" was failing on some (but not all computers) with > https://github.com/DrylandEcology/rSOILWAT2/actions/runs/5157599324/jobs/9290219836 > ('test_WeatherGenerator_functionality.R:125:7'): Weather generator any(is_missing_weather(wdf)) is not FALSE -> this was caused by a value pair of `s` and `wTmin_var` of 99.264050000 and 99.264050000 that resulted in both `GT(s, wTmin_var)` and `EQ(wTmin_var, s)` being FALSE and `vc11` was calculated as the square root of a negative number -> the fix implemented with this commit sets `vc11` to 0 if `LE(wTmin_var, s)` --- src/SW_Markov.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/SW_Markov.c b/src/SW_Markov.c index e27e6e49d..97299a619 100644 --- a/src/SW_Markov.c +++ b/src/SW_Markov.c @@ -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; From ed6afe3657acee7619c41599294f7d30b8dc6c9e Mon Sep 17 00:00:00 2001 From: Daniel Schlaepfer Date: Mon, 5 Jun 2023 11:18:46 -0400 Subject: [PATCH 326/326] Finalize NEWS for upcoming release of v7.0.0 --- NEWS.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index b53fc7501..e395c2a2c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,6 @@ # NEWS -# SOILWAT2 v7.0.0-9000 +# 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 @@ -44,7 +44,7 @@ * 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 + * 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) @@ -94,7 +94,8 @@ * 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 (optiona) input shortwave radiation. + 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`