//////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// \file cropphenology.cpp /// \brief Crop phenology including phu calculations /// \author Mats Lindeskog /// $Date: $ //////////////////////////////////////////////////////////////////////////////////////////////////////////////// #include "config.h" #include "landcover.h" #include "cropphenology.h" const double MAXHUTEMP = 30; // Degree limit for heat unit summation /// Calculation of down-scaling of lai during crop senescence /** Follows Bondeau et al. 2007. */ double senescence_curve(Pft& pft, double fphu) { if (pft.shapesenescencenorm) return pow(1-fphu,2) / pow(1 - pft.fphusen, 2) * (1 - pft.flaimaxharvest) + pft.flaimaxharvest; else return pow(1 - fphu, 0.5) / pow(1 - pft.fphusen, 0.5) * (1 - pft.flaimaxharvest) + pft.flaimaxharvest; } /// Initiation of potential heat unit calculation /** Calculates pvd (required vernalising days), tb (base temperature) * and phu (potential heat units) based on Bondeau et al. 2007. * Dynamic phu calculation based on Lindeskog et al. 2013. * Called on sowing date. */ void phu_init(cropphen_struct& ppftcrop, Gridcellpft& gridcellpft, Patch& patch) { Pft& pft = gridcellpft.pft; const Climate& climate = patch.get_climate(); double phu_last_year = ppftcrop.phu; ppftcrop.husum = 0.0; ppftcrop.vrf = 1.0; ppftcrop.vdsum = 0; ppftcrop.prf = 1.0; ppftcrop.pvd = pft.pvd; // default; kept for TrMi, TePu, TeSb, TrMa, TeSo, TrPe ppftcrop.phu = pft.phu; ppftcrop.tb = pft.tb; ppftcrop.vdsum_alloc=0.0; ppftcrop.vd=0.0; ppftcrop.dev_stage=0.0; // Calculation of phu and pvd according to Bondeau et al. 2007 if (pft.ifsdautumn) { // TeWW,TeRa // maximum pvd allowed const int max_pvd = 60; if (gridcellpft.wintertype) { // Autumn sowing // if neither spring or winter conditions for the past 20 years if ((gridcellpft.first_autumndate20 == climate.testday_temp || gridcellpft.first_autumndate20 == climate.coldestday) && gridcellpft.first_autumndate % date.year_length() == gridcellpft.first_autumndate20 && (gridcellpft.last_springdate20 == climate.testday_temp || gridcellpft.last_springdate20 == climate.coldestday) && gridcellpft.last_springdate == gridcellpft.last_springdate20) { ppftcrop.pvd = pft.pvd; ppftcrop.phu = pft.phu; } // not all past 20 years without vernendoccurred: too cold else if (!(gridcellpft.last_verndate20 == gridcellpft.last_springdate20 + def_verndate_ndays_after_last_springdate && gridcellpft.last_verndate == gridcellpft.last_verndate20)) { // pvd (required vernalising days): undocumented equations from Bondeau's code // vernalization (below 12 degrees) is supposed to occur directly at sowing (trg=tempautumn) for TeWW, for TeRa a 20-day lag (tempautumn=17) // first_autumndate20 occurred before last_verndate20 if ((ppftcrop.sdate < 180 || gridcellpft.last_verndate20 >= 180) && climate.lat >= 0.0 || climate.lat < 0.0) { ppftcrop.pvd = (int)max(0, min(max_pvd, gridcellpft.last_verndate20 - ppftcrop.sdate - pft.vern_lag)); } // first_autumndate20 occurred after last_verndate20 else { ppftcrop.pvd = (int)max(0, min(max_pvd, gridcellpft.last_verndate20 + date.year_length() - ppftcrop.sdate - pft.vern_lag)); } // phu (potential heat units): quadratic calculation according to sowing month; see Fig.2 in Bondeau et al.2007 const double k1 = -0.1081; const double k2 = 3.1633; if (ppftcrop.sdate < 184 + climate.adjustlat) { ppftcrop.phu = max(pft.phu_min, k1 * pow((double)(ppftcrop.sdate - climate.adjustlat),2) + k2 * ((double)(ppftcrop.sdate - climate.adjustlat)) + pft.phu_max); } else { ppftcrop.phu = max(pft.phu_min, k1 * pow((double)ppftcrop.sdate - date.year_length(),2) + k2 * ((double)ppftcrop.sdate - 365) + pft.phu_max); ppftcrop.phu *= pft.phu_red_spring_sow; // undocumented reduction in spring variants from Bondeau's code } } } else { // spring sowing // default pvd of spring varieties when vernalisation has not occurred during the past 20 years const int pvd_def_spring = 30; // phu of spring varieties when vernalisation has occurred during the past 20 years const double phu_spring_ifvern = 1300.0; // phu of spring varieties when vernalisation has not occurred during the past 20 years const double phu_spring_ifnvern = 1500.0; // If last_verndate has occurred during the past 20 years (or too warm): if (!(gridcellpft.last_verndate20 == ppftcrop.sdate + def_verndate_ndays_after_last_springdate && gridcellpft.last_verndate == gridcellpft.last_verndate20)) { ppftcrop.pvd = (int)min(max_pvd, gridcellpft.last_verndate20 - ppftcrop.sdate); ppftcrop.phu = phu_spring_ifvern; } // If no last_verndate occurred during the past 20 year (too cold): else { ppftcrop.pvd = pvd_def_spring; ppftcrop.phu = phu_spring_ifnvern; } } } else { // phu: Linear calculation according to sowing month; see Fig.2 in Bondeau et al.2007 if (pft.phu_calc_lin) { ppftcrop.phu = min(pft.phu_max, max(pft.phu_min, -(pft.phu_max - pft.phu_min) / pft.ndays_ramp_phu * (double)(ppftcrop.sdate - climate.adjustlat) + pft.phu_interc)); } } // Calculation of potential heat units according to local climate. if (ifcalcdynamic_phu) { const double min_phu = 900.0; // minimum allowed phu based on phu range in the literature const int min_phu_sample_period = 20; // minimum no. of years of the phu sample period // add last year's husum_max to running 10-year mean if (ppftcrop.husum_max) { ppftcrop.nyears_hu_sample++; int years = min(ppftcrop.nyears_hu_sample, 10); ppftcrop.husum_max_10 = (ppftcrop.husum_max_10 * (years - 1) + ppftcrop.husum_max) / years; } ppftcrop.husum_max = 0.0; ppftcrop.phu_old = ppftcrop.phu; // phu_old for printout // set phu according to running mean if (ppftcrop.nyears_hu_sample) ppftcrop.phu = max(min_phu, 0.9 * ppftcrop.husum_max_10); // ... unless past specified time period if (ifdyn_phu_limit && patch.stand.get_gridcell().getsimulationyear(date.year) >= patch.stand.first_year + min_phu_sample_period && patch.stand.get_gridcell().getsimulationyear(date.year) >= nyear_spinup + nyear_dyn_phu) ppftcrop.phu = phu_last_year; } } /// Calculation of harvest index /** Based on fphu. Restricted by water stress. * SWAT equations are from Neitsch et al. 2002. */ void harvest_index(Patch& patch, Pft& pft) { double wdf, fwdf, hi_save; Patchpft& patchpft = patch.pft[pft.id]; cropphen_struct& ppftcrop = *(patchpft.get_cropphen()); ppftcrop.hi = pft.hiopt * 100 * ppftcrop.fphu / (100 * ppftcrop.fphu + exp(11.1 - 10.0 * ppftcrop.fphu)); // SWAT 5:2.4.1 ppftcrop.fhi_phen = ppftcrop.hi / pft.hiopt; // Correction of HI according to water stress: ppftcrop.demandsum_crop += patch.wdemand; if (patchpft.wsupply > patch.wdemand) ppftcrop.supplysum_crop += patch.wdemand; else ppftcrop.supplysum_crop += patchpft.wsupply; if (ppftcrop.demandsum_crop > 0.0) wdf = 100.0 * ppftcrop.supplysum_crop / ppftcrop.demandsum_crop; // SWAT 5:3.3.2 : aetsum/petsum else wdf = 100.0; fwdf = wdf / (wdf + exp(6.13 - 0.0883 * wdf)); // (SWAT 5:3.3.1) hi_save = ppftcrop.hi; ppftcrop.hi = ppftcrop.fhi_phen * ((pft.hiopt - pft.himin) * fwdf + pft.himin); if (ppftcrop.hi > 0.0 && hi_save) ppftcrop.fhi_water = ppftcrop.hi / hi_save; ppftcrop.fhi = ppftcrop.fhi_phen * ppftcrop.fhi_water; } /// Calculation of accumulated of heat units /** Accumulation of heat units during sampling period used for calculation of * dynamic phu if ifcalcdynamic_phu = true. SWAT equation is from Neitsch et al. 2002 */ void heat_units(Patch& patch, Pft& pft) { Patchpft& patchpft = patch.pft[pft.id]; cropphen_struct& ppftcrop = *(patchpft.get_cropphen()); const Climate& climate = patch.get_climate(); // calculation av fphu: double hu = max(0.0, min(climate.temp, MAXHUTEMP) - ppftcrop.tb); // account for vernalization if needs for vernalization not yet satisfied //trg=tb for crops other than TeWW and TeRa and don't enter here if (climate.temp < pft.trg && ppftcrop.vdsum < ppftcrop.pvd) { //trg=12 for TeWW & TeRa ppftcrop.vdsum++; ppftcrop.vrf = min(1.0, (double)ppftcrop.vdsum / (double)ppftcrop.pvd); // vernalisation reduction factor has no effect once temp>trg even if needs are not satisfied // no effect as well if temp>trg at the beginning of the growing season... hu *= ppftcrop.vrf; } // account for response to photoperiod ppftcrop.prf = (1 - pft.psens) * min(1.0, max(0.0, (climate.daylength_save[date.day] - pft.pb) / (pft.ps - pft.pb))) + pft.psens; hu *= ppftcrop.prf; if (date.day == ppftcrop.sdate) { ppftcrop.husum = 0.0; } // Accumulate heat units during growing period if (ppftcrop.growingseason) { // daily effective temperature sum (degree-days) ppftcrop.husum += hu; // phenological scale (fraction of growing season) ppftcrop.fphu = min(1.0, ppftcrop.husum / ppftcrop.phu); // SWAT 5:2.1.11 } // Sample heat units for dynamic phu calculation if (ifcalcdynamic_phu) { if (date.day == ppftcrop.sdate) { ppftcrop.hu_samplingperiod = true; ppftcrop.hu_samplingdays = 0; ppftcrop.husum_sampled = 0; } if (ppftcrop.hu_samplingperiod) { ppftcrop.husum_sampled += hu; ppftcrop.hu_samplingdays++; if (date.day == ppftcrop.hucountend) { ppftcrop.husum_sampled -= hu; // Don't count the hu's on last day ppftcrop.husum_max = ppftcrop.husum_sampled; ppftcrop.hu_samplingperiod = false; } } } } /// Temperature factor used in development stage calculation inline double temperature_factor(double temp, double min, double opt, double max) { if (temp <= min || temp >= max) { return 0; } double _opt = opt - min; double alpha = log(2.) / log((max - min) / _opt); return (2 * pow(temp - min, alpha) * pow(_opt, alpha) - pow(temp - min, 2*alpha))/ pow(_opt, 2*alpha); } /// Calculation of development stage /** Accumulation of development during sampling period, based on Wang & Engel 1998. */ void development_stage(Patch& patch, Pft& pft) { Patchpft& patchpft = patch.pft[pft.id]; cropphen_struct& ppftcrop = *(patchpft.get_cropphen()); const Climate& climate = patch.get_climate(); // account for vernalization if needs for vernalization not yet satisfied //trg=tb for crops other than TeWW and TeRa and don't enter here if (ppftcrop.vdsum_alloc < 1 && climate.temp > pft.T_vn_min && climate.temp < pft.T_vn_max) { ppftcrop.vd += temperature_factor(climate.temp, pft.T_vn_min, pft.T_vn_opt, pft.T_vn_max); double vd5 = pow(ppftcrop.vd, 5.); ppftcrop.vdsum_alloc = min(1.0, vd5 / (pow(22.5, 5.0) + vd5)); } double daylength = max(0.0, climate.daylength_save[date.day] - pft.photo[0]); double e = exp(-pft.photo[1] * daylength); double fP = min(1.0, pft.photo[2] > 0 ? e : 1.0 - e); double T_min = pft.T_veg_min; double T_opt = pft.T_veg_opt; double T_max = pft.T_veg_max; if (ppftcrop.dev_stage >= 1) { T_min = pft.T_rep_min; T_opt = pft.T_rep_opt; T_max = pft.T_rep_max; } double fT = min(1.0, temperature_factor(climate.temp, T_min, T_opt, T_max)); double dev_rate = 0.0; if (ppftcrop.dev_stage < 1.0) dev_rate = pft.dev_rate_veg * ppftcrop.vdsum_alloc * fP * fT; else dev_rate = pft.dev_rate_rep * fT; ppftcrop.dev_stage = min(2.0, ppftcrop.dev_stage + dev_rate); } /// Handles heat unit and harvest index calculation and identifies harvest, senescence and intercrop events. /** Accumulation of heat units during sampling period used for calculation of dynamic phu if DYNAMIC_PHU defined. * Sets patchpft.cropphen variables growingseason, hdate, intercropseason and senescence */ void crop_phenology(Patch& patch) { patch.pft.firstobj(); while (patch.pft.isobj) { Patchpft& patchpft = patch.pft.getobj(); Pft& pft=patchpft.pft; Standpft& standpft = patch.stand.pft[pft.id]; Gridcell& gridcell = patch.stand.get_gridcell(); Climate& climate = gridcell.climate; Gridcellpft& gridcellpft = gridcell.pft[pft.id]; if (patch.stand.pft[pft.id].active) { if (pft.phenology == CROPGREEN) { cropphen_struct& ppftcrop = *(patchpft.get_cropphen()); ppftcrop.growingseason_ystd = ppftcrop.growingseason; // resets on first day of the year: if (date.day == 0) { ppftcrop.fphu_harv = -1.0; ppftcrop.fhi_harv = -1.0; ppftcrop.sdate_harv = -1; ppftcrop.nsow = 0; ppftcrop.sendate = -1; ppftcrop.nharv = 0; ppftcrop.sownlastyear=false; for(int i=0;i<2;i++) { ppftcrop.sdate_harvest[i] = -1; ppftcrop.hdate_harvest[i] = -1; ppftcrop.sdate_thisyear[i] = -1; } } // initiations on sowing day: if (date.day == ppftcrop.sdate) { ppftcrop.fphu = 0.0; ppftcrop.fhi = 0.0; ppftcrop.fhi_phen = 0.0; ppftcrop.fhi_water = 1.0; ppftcrop.hdate = -1; ppftcrop.bicdate = -1; ppftcrop.growingseason = true; ppftcrop.growingdays = 0; ppftcrop.nsow++; if (ppftcrop.nsow == 1) ppftcrop.sdate_thisyear[0] = ppftcrop.sdate; else if (ppftcrop.nsow == 2) ppftcrop.sdate_thisyear[1] = ppftcrop.sdate; // calculate pvd, phu & tb phu_init(ppftcrop, gridcellpft, patch); } // Calculation of accumulated heat units and harvest index from sowing to maturity if (ppftcrop.growingseason) { ppftcrop.senescence_ystd = ppftcrop.senescence; ppftcrop.intercropseason = false; ppftcrop.growingdays++; // check if harvest is prescribed bool force_harvest = date.day == standpft.hdate_force; // before maturity is reached bool pre_maturity = ifnlim ? ppftcrop.dev_stage < 2.0 : ppftcrop.husum < ppftcrop.phu; if (pre_maturity && dayinperiod(date.day, ppftcrop.sdate, stepfromdate(ppftcrop.hlimitdate, -1)) && !force_harvest) { // count accumulated heat units after sowing date heat_units(patch, pft); if (ifnlim) development_stage(patch, pft); // test for senescence if (ppftcrop.fphu >= pft.fphusen) { if (ppftcrop.senescence_ystd == false) ppftcrop.sendate = date.day; ppftcrop.senescence = true; } // calculated harvest index harvest_index(patch, pft); } else { // harvest // save today as harvest day ppftcrop.hdate = date.day; ppftcrop.growingseason = false; ppftcrop.intercropseason = false; ppftcrop.senescence = false; ppftcrop.fertilised[0] = false; ppftcrop.fertilised[1] = false; ppftcrop.fertilised[2] = false; // set start of intercrop grass growth ppftcrop.bicdate = stepfromdate(ppftcrop.hdate, 15); // count number of harvest events this year ppftcrop.nharv++; // Save phenological values and dates at harvest: ppftcrop.fphu_harv = ppftcrop.fphu; ppftcrop.fhi_harv = ppftcrop.fhi; ppftcrop.sdate_harv = ppftcrop.sdate; ppftcrop.lgp = ppftcrop.growingdays; // allowing saving at two harvests per year if (ppftcrop.nharv == 1) { ppftcrop.sdate_harvest[0] = ppftcrop.sdate; ppftcrop.hdate_harvest[0] = date.day; if (ppftcrop.sdate > date.day) ppftcrop.sownlastyear = true; } else if (ppftcrop.nharv == 2) { ppftcrop.sdate_harvest[1] = ppftcrop.sdate; ppftcrop.hdate_harvest[1] = date.day; } ppftcrop.demandsum_crop = 0.0; ppftcrop.supplysum_crop = 0.0; ppftcrop.sdate = -1; ppftcrop.eicdate = -1; } //end harvest } //from sowing has taken place until harvest day // continue sampling heat units from hdate until last sampling date if (ifcalcdynamic_phu && ppftcrop.growingseason == false && ppftcrop.hu_samplingperiod) { heat_units(patch, pft); } if (patch.stand.pftid == pft.id && stlist[patch.stand.stid].intercrop == NATURALGRASS) { if (!ppftcrop.intercropseason && date.day == ppftcrop.bicdate) { ppftcrop.intercropseason = true; } if (date.day == ppftcrop.eicdate) { ppftcrop.intercropseason = false; } } } else if (pft.phenology == ANY) { // crop grasses using standard guess phenology calculation if (patch.stand.pftid != pft.id) { cropphen_struct& ppftcrop = *(patchpft.get_cropphen()); if (date.day == 0) { ppftcrop.nharv = 0; } if (date.day == patch.pft[patch.stand.pftid].cropphen->bicdate) { ppftcrop.growingseason = true; ppftcrop.growingdays = 0; } else if (date.day == patch.pft[patch.stand.pftid].cropphen->eicdate) { ppftcrop.growingseason = false; ppftcrop.lgp = ppftcrop.growingdays; } if (ppftcrop.growingseason == true) { ppftcrop.growingdays++; } } } } patch.pft.nextobj(); } } /// Updates crop phen from yesterday's lai_daily /** True crops derive phen from yesterday's fpc_daily, assuming only one crop individual. * Intercrop grass and pasture grass grown in crop stands use gdd5 and is treated * similar to pasture grass. */ void leaf_phenology_crop(Pft& pft, Patch& patch) { Gridcell& gridcell = patch.stand.get_gridcell(); Climate& climate = gridcell.climate; Patchpft& patchpft = patch.pft[pft.id]; cropphen_struct& ppftcrop = *(patchpft.get_cropphen()); if (pft.phenology == CROPGREEN) { if (ppftcrop.growingseason) { Vegetation& vegetation = patch.vegetation; vegetation.firstobj(); while (vegetation.isobj) { Individual& indiv = vegetation.getobj(); if (indiv.pft.id == pft.id) { patchpft.phen = indiv.fpc ? indiv.fpc_daily / indiv.fpc : 0; } vegetation.nextobj(); } } else if (date.day == ppftcrop.hdate) { patchpft.phen = 0.0; } } else if (pft.phenology == ANY) { // crop grasses using standard guess phenology calculation if (patch.stand.pftid == pft.id // pasture grass in crop stand || patch.stand.hasgrassintercrop && (patch.pft[patch.stand.pftid].cropphen->intercropseason // intercrop grass || date.day == patch.pft[patch.stand.pftid].cropphen->eicdate)) { // intercrop grass if (patch.stand.pftid != pft.id) { if (date.day == patch.pft[patch.stand.pftid].cropphen->bicdate) { patch.stand.gdd0_intercrop = climate.gdd5; } if (date.day == patch.pft[patch.stand.pftid].cropphen->eicdate) { patch.stand.gdd0_intercrop = 0.0; patchpft.phen = 0.0; } } // reset stand.gdd0_intercrop same day as gdd5 if (climate.lat >= 0.0 && date.day == COLDEST_DAY_NHEMISPHERE || climate.lat < 0.0 && date.day == COLDEST_DAY_SHEMISPHERE || climate.gdd5 == 0.0) { patch.stand.gdd0_intercrop = 0.0; } if (ppftcrop.growingseason) { // includes bicdate, not eicdate if (patch.stand.pftid == pft.id || gridcell.pft[patch.stand.pftid].sowing_restriction) { // Normal grass growth: gives identical result to natural stands. patchpft.phen = min(1.0, climate.gdd5 / pft.phengdd5ramp); } else if (patch.stand.gdd0_intercrop > 0.0) { patchpft.phen = min(1.0, (climate.gdd5 - patch.stand.gdd0_intercrop) / (pft.phengdd5ramp * 0.9)); // intercrop grass } else { patchpft.phen = min(1.0, (climate.gdd5 - patch.stand.gdd0_intercrop) / pft.phengdd5ramp); // intercrop grass } if (patchpft.phen < 0.0) { patchpft.phen = 0.0; } // raingreen phenology if (patchpft.wscal < pft.wscal_min) { patchpft.phen = 0.0; patch.stand.gdd0_intercrop = climate.gdd5; } } } } } ////////////////////////////////////////////////////////////////////////////////////////// // REFERENCES // // Bondeau A, Smith PC, Zaehle S, Schaphoff S, Lucht W, Cramer W, Gerten D, Lotze-Campen H, // Müller C, Reichstein M & Smith B 2007. Modelling the role of agriculture for the // 20th century global terrestrial carbon balance. Global Change Biology, 13:679-706. // Lindeskog M, Arneth A, Bondeau A, Waha K, Seaquist J, Olin S, & Smith B 2013. // Implications of accounting for land use in simulations of ecosystem carbon cycling // in Africa. Earth Syst Dynam 4:385-407. // Neitsch SL, Arnold JG, Kiniry JR et al.2002 Soil and Water Assessment Tool, Theorethical // Documentation + User's Manual. USDA_ARS-SR Grassland, Soil and Water Research Laboratory. // Agricultural Reasearch Service, Temple,Tx, US. // Wang, E. and Engel, T. 1998 Simulation of phenological development // of wheat crops, Agr. Syst., 58, 1-24