class StateVariables(StatesTemplate): TAGP = Float(-99.) GASST = Float(-99.) MREST = Float(-99.) CTRAT = Float(-99.) CEVST = Float(-99.) HI = Float(-99.) DOF = Instance(datetime.date) FINISH_TYPE = Unicode(allow_none=True)
class StateVariables(StatesTemplate): PTa = Float(-99.) #Soil water balance TPE = Float(-99.) TPT = Float(-99.) TPREC = Float(-99.) TINTERC = Float(-99.) TRUNOFF = Float(-99.) PERC = Float(-99.) TE = Float(-99.) Ta = Float(-99.) WC = Instance(np.ndarray) WCv = Instance(np.ndarray) #To chech WB closed TT = Float(-99.) WB_close = Float(-99.) Diff_WC = Float(-99.) TWC = Float(-99.) W_Stress = Float(-99.)
class StateVariables(StatesTemplate): DVS = Float(-99.) # Development stage TSUM = Float(-99.) # Temperature sum state TSUME = Float(-99.) # Temperature sum for emergence state # States which register phenological events DOS = Instance(date) # Day of sowing DOE = Instance(date) # Day of emergence DOR1 = Instance(date) # Day of start of flowering DOR3 = Instance(date) # Day of pod development DOR5 = Instance(date) # Day of seed filling DOR8 = Instance(date) # Day of full ripeness DOH = Instance(date) # Day of harvest STAGE = Enum( [None, "emerging", "vegetative", "reproductive", "mature"])
class TestSimulationObject(SimulationObject): """This wraps the SimulationObject for testing to ensure that the computations are not carried out before crop emergence (e.g. DVS >= 0). The latter does not apply for the phenology simobject itself which simulates emergence. The phenology simobject is recognized because the variable DVS is not an external variable. """ test_class = None subsimobject = Instance(SimulationObject) def initialize(self, day, kiosk, parvalues): self.subsimobject = self.test_class(day, kiosk, parvalues) def calc_rates(self, day, drv): # some simobject do not provide a `calc_rates()` function but are directly callable # here we check for those cases. func = self.subsimobject if callable( self.subsimobject) else self.subsimobject.calc_rates if not self.kiosk.is_external_state("DVS"): func(day, drv) else: if self.kiosk.DVS >= 0: func(day, drv) else: self.subsimobject.zerofy() def integrate(self, day, delt=1.0): # If the simobject is callable, we do not need integration so we use the `nothing()` function. func = nothing if callable( self.subsimobject) else self.subsimobject.integrate if not self.kiosk.is_external_state("DVS"): func(day, delt) else: if self.kiosk.DVS >= 0: func(day, delt) else: self.subsimobject.touch()
class Wofost(SimulationObject): """Top level object organizing the different components of the WOFOST crop simulation. The CropSimulation object organizes the different processes of the crop simulation. Moreover, it contains the parameters, rate and state variables which are relevant at the level of the entire crop. The processes that are implemented as embedded simulation objects consist of: 1. Phenology (self.pheno) 2. Partitioning (self.part) 3. Assimilation (self.assim) 4. Maintenance respiration (self.mres) 5. Evapotranspiration (self.evtra) 6. Leaf dynamics (self.lv_dynamics) 7. Stem dynamics (self.st_dynamics) 8. Root dynamics (self.ro_dynamics) 9. Storage organ dynamics (self.so_dynamics) **Simulation parameters:** ======== =============================================== ======= ========== Name Description Type Unit ======== =============================================== ======= ========== CVL Conversion factor for assimilates to leaves SCr - CVO Conversion factor for assimilates to storage SCr - organs. CVR Conversion factor for assimilates to roots SCr - CVS Conversion factor for assimilates to stems SCr - ======== =============================================== ======= ========== **State variables:** =========== ================================================= ==== =============== Name Description Pbl Unit =========== ================================================= ==== =============== TAGP Total above-ground Production N |kg ha-1| GASST Total gross assimilation N |kg CH2O ha-1| MREST Total gross maintenance respiration N |kg CH2O ha-1| CTRAT Total crop transpiration accumulated over the crop cycle N cm CEVST Total soil evaporation accumulated over the crop cycle N cm HI Harvest Index (only calculated during N - `finalize()`) DOF Date representing the day of finish of the crop N - simulation. FINISH_TYPE String representing the reason for finishing the N - simulation: maturity, harvest, leave death, etc. =========== ================================================= ==== =============== **Rate variables:** ======= ================================================ ==== ============= Name Description Pbl Unit ======= ================================================ ==== ============= GASS Assimilation rate corrected for water stress N |kg CH2O ha-1 d-1| MRES Actual maintenance respiration rate, taking into account that MRES <= GASS. N |kg CH2O ha-1 d-1| ASRC Net available assimilates (GASS - MRES) N |kg CH2O ha-1 d-1| DMI Total dry matter increase, calculated as ASRC times a weighted conversion efficiency. Y |kg ha-1 d-1| ADMI Aboveground dry matter increase Y |kg ha-1 d-1| ======= ================================================ ==== ============= """ # sub-model components for crop simulation pheno = Instance(SimulationObject) part = Instance(SimulationObject) assim = Instance(SimulationObject) mres = Instance(SimulationObject) evtra = Instance(SimulationObject) lv_dynamics = Instance(SimulationObject) st_dynamics = Instance(SimulationObject) ro_dynamics = Instance(SimulationObject) so_dynamics = Instance(SimulationObject) # Parameters, rates and states which are relevant at the main crop # simulation level class Parameters(ParamTemplate): CVL = Float(-99.) CVO = Float(-99.) CVR = Float(-99.) CVS = Float(-99.) class StateVariables(StatesTemplate): TAGP = Float(-99.) GASST = Float(-99.) MREST = Float(-99.) CTRAT = Float(-99.) CEVST = Float(-99.) HI = Float(-99.) DOF = Instance(datetime.date) FINISH_TYPE = Unicode(allow_none=True) class RateVariables(RatesTemplate): GASS = Float(-99.) MRES = Float(-99.) ASRC = Float(-99.) DMI = Float(-99.) ADMI = Float(-99.) def initialize(self, day, kiosk, parvalues): """ :param day: start date of the simulation :param kiosk: variable kiosk of this PCSE instance :param parvalues: `ParameterProvider` object providing parameters as key/value pairs """ self.params = self.Parameters(parvalues) self.rates = self.RateVariables(kiosk, publish=["DMI", "ADMI"]) self.kiosk = kiosk # Initialize components of the crop self.pheno = Phenology(day, kiosk, parvalues) self.part = Partitioning(day, kiosk, parvalues) self.assim = Assimilation(day, kiosk, parvalues) self.mres = MaintenanceRespiration(day, kiosk, parvalues) self.evtra = Evapotranspiration(day, kiosk, parvalues) self.ro_dynamics = Root_Dynamics(day, kiosk, parvalues) self.st_dynamics = Stem_Dynamics(day, kiosk, parvalues) self.so_dynamics = Storage_Organ_Dynamics(day, kiosk, parvalues) self.lv_dynamics = Leaf_Dynamics(day, kiosk, parvalues) # Initial total (living+dead) above-ground biomass of the crop TAGP = self.kiosk.TWLV + self.kiosk.TWST + self.kiosk.TWSO self.states = self.StateVariables( kiosk, publish=["TAGP", "GASST", "MREST", "HI"], TAGP=TAGP, GASST=0.0, MREST=0.0, CTRAT=0.0, CEVST=0.0, HI=0.0, DOF=None, FINISH_TYPE=None) # Check partitioning of TDWI over plant organs checksum = parvalues["TDWI"] - self.states.TAGP - self.kiosk["TWRT"] if abs(checksum) > 0.0001: msg = "Error in partitioning of initial biomass (TDWI)!" raise exc.PartitioningError(msg) # assign handler for CROP_FINISH signal self._connect_signal(self._on_CROP_FINISH, signal=signals.crop_finish) @staticmethod def _check_carbon_balance(day, DMI, GASS, MRES, CVF, pf): (FR, FL, FS, FO) = pf checksum = (GASS - MRES - (FR + (FL + FS + FO) * (1. - FR)) * DMI / CVF) * 1. / (max(0.0001, GASS)) if abs(checksum) >= 0.0001: msg = "Carbon flows not balanced on day %s\n" % day msg += "Checksum: %f, GASS: %f, MRES: %f\n" % (checksum, GASS, MRES) msg += "FR,L,S,O: %5.3f,%5.3f,%5.3f,%5.3f, DMI: %f, CVF: %f\n" % \ (FR, FL, FS, FO, DMI, CVF) raise exc.CarbonBalanceError(msg) @prepare_rates def calc_rates(self, day, drv): p = self.params r = self.rates k = self.kiosk # Phenology self.pheno.calc_rates(day, drv) crop_stage = self.pheno.get_variable("STAGE") # if before emergence there is no need to continue # because only the phenology is running. if crop_stage == "emerging": return # Potential assimilation PGASS = self.assim(day, drv) # (evapo)transpiration rates self.evtra(day, drv) # water stress reduction r.GASS = PGASS * k.RFTRA # Respiration PMRES = self.mres(day, drv) r.MRES = min(r.GASS, PMRES) # Net available assimilates r.ASRC = r.GASS - r.MRES # DM partitioning factors (pf), conversion factor (CVF), # dry matter increase (DMI) and check on carbon balance pf = self.part.calc_rates(day, drv) CVF = 1. / ((pf.FL / p.CVL + pf.FS / p.CVS + pf.FO / p.CVO) * (1. - pf.FR) + pf.FR / p.CVR) r.DMI = CVF * r.ASRC self._check_carbon_balance(day, r.DMI, r.GASS, r.MRES, CVF, pf) # distribution over plant organ # Below-ground dry matter increase and root dynamics self.ro_dynamics.calc_rates(day, drv) # Aboveground dry matter increase and distribution over stems, # leaves, organs r.ADMI = (1. - pf.FR) * r.DMI self.st_dynamics.calc_rates(day, drv) self.so_dynamics.calc_rates(day, drv) self.lv_dynamics.calc_rates(day, drv) @prepare_states def integrate(self, day, delt=1.0): rates = self.rates states = self.states # crop stage before integration crop_stage = self.pheno.get_variable("STAGE") # Phenology self.pheno.integrate(day, delt) # if before emergence there is no need to continue # because only the phenology is running. # Just run a touch() to to ensure that all state variables are available # in the kiosk if crop_stage == "emerging": self.touch() return # Partitioning self.part.integrate(day, delt) # Integrate states on leaves, storage organs, stems and roots self.ro_dynamics.integrate(day, delt) self.so_dynamics.integrate(day, delt) self.st_dynamics.integrate(day, delt) self.lv_dynamics.integrate(day, delt) # Integrate total (living+dead) above-ground biomass of the crop states.TAGP = self.kiosk.TWLV + self.kiosk.TWST + self.kiosk.TWSO # total gross assimilation and maintenance respiration states.GASST += rates.GASS states.MREST += rates.MRES # total crop transpiration and soil evaporation states.CTRAT += self.kiosk.TRA states.CEVST += self.kiosk.EVS @prepare_states def finalize(self, day): # Calculate Harvest Index if self.states.TAGP > 0: self.states.HI = self.kiosk.TWSO / self.states.TAGP else: msg = "Cannot calculate Harvest Index because TAGP=0" self.logger.warning(msg) self.states.HI = -1. SimulationObject.finalize(self, day) def _on_CROP_FINISH(self, day, finish_type=None): """Handler for setting day of finish (DOF) and reason for crop finishing (FINISH). """ self._for_finalize["DOF"] = day self._for_finalize["FINISH_TYPE"] = finish_type
class Water_balance(SimulationObject): '''Parameters** ============ ================================================= ==== ======== Name Description Unit ============ ================================================= ==== ======== FCP Field capacity mH2O m Soil PWPP Permanent wilting point mH2O m Soi ADWCP Air dry water content mH2O m Soi TCK Thickness of soil layer m RUNOFF1 Parameter 1 for runoff function RUNOFF2 Parameter 2 for runoff function RMIN Root resistance J kg-1 PSIPWP Permanent wilting water potential J kg-1 PSIFC Field capacity water potential J kg-1 S Surface storage condition m RDMAX Maximun root depth m m ============ ================================================= ==== ======== Rates** ============ ================================================= ==== ======== Name Description Unit ============ ================================================= ==== ======== FC Parameter FCP in m of H2O per layer m PWP Parameter PWPP in m of H2O per layer m INTERC Precipitation intercepted by the canopy m RUNOFF Rate of runoff m INFIL Water available for infiltration m RW Rate of recharging water in each layer m NWC Rate of water in the 1st layer m EVS Rate of soil evaporation m RDr Rate of root growth m B Value for power equation of soil potential A Value for power equation of soil potential SPSI Soil potential per layer J kg-1 m-1 FROOT Root fraction per layer AVESPSI Soil potential weighten by rooting fraction J kg-1 m-1 RBAR Root resistance J kg-1 m-1 PSIX Xilem Potential J kg-1 m-1 LOSS Rate of water loss per layer mH2O m TL Transpiration per layer m AT Actual Evapotranspiration per layer m T Total Evapotranspiration m ============ ================================================= ==== ======== State variables** Name Description Unit ============ ================================================= ==== ======== TINTERC Total interception by the canopy m GPREC Ground precipitation m TRUNOFF Total runoff m PERC Total deep percolation m TWC Water content m-3 m-3 TEVS Total soil evaporation m TRD Root depth m TFR Root fraction in each layer ============ ================================================= ==== ======== **External dependencies:** ======= =================================== ================= ============ Name Description Provided by Unit ======= =================================== ================= ============ FR Fraction partitioned to roots. Y - FS Fraction partitioned to stems. Y - FL Fraction partitioned to leaves. Y - FO Fraction partitioned to storage orgains Y - DVS Development stage ======= =================================== ================= ============ ''' pheno = Instance(SimulationObject) class Parameters(ParamTemplate): #Soil water balance FCP = Float(-99.) PWPP = Float(-99.) ADWCP = Float(-99.) TCK = Float(-99.) RUNOFF1 = Float(-99.) RUNOFF2 = Float(-99.) RMIN = Float(-99.) PSIPWP = Float(-99.) PSIFC = Float(-99.) S = Float(-99.) RDMAX = Float(-99.) class RateVariables(RatesTemplate): PE = Float(-99.) PT = Float(-99.) EPT = Float(-99.) PREC = Float(-99.) nl = Int FC = Float(-99.) PWP = Float(-99.) ADWC = Float(-99.) B = Float(-99.) A = Float(-99.) INTERC = Float(-99.) GPREC = Float(-99.) RUNOFF = Float(-99.) INFIL = Float(-99.) RW = Float(-99.) values_RW = [] RWATER = Instance(np.ndarray) NWC = Float(-99.) EVS = Float(-99.) SPSI = Float(-99.) values_SPSI = [] SP = Instance(np.ndarray) #root FcR = Instance(np.ndarray) values_FR = [] FROOT = Float(-99.) AVEPSI = Float(-99.) RBAR = Float(-99.) PSIX = Float(-99.) z = Float(-99.) LOSS = Instance(np.ndarray) W = Instance(np.ndarray) arr_bool = Instance(np.ndarray) arr_TRUE = Instance(np.ndarray) TL = Instance(np.ndarray) net_RW = Instance(np.ndarray) T = Float(-99.) arr_subt = Instance(np.ndarray) arr_mult = Instance(np.ndarray) AT = Instance(np.ndarray) check = Float(-99.) class StateVariables(StatesTemplate): PTa = Float(-99.) #Soil water balance TPE = Float(-99.) TPT = Float(-99.) TPREC = Float(-99.) TINTERC = Float(-99.) TRUNOFF = Float(-99.) PERC = Float(-99.) TE = Float(-99.) Ta = Float(-99.) WC = Instance(np.ndarray) WCv = Instance(np.ndarray) #To chech WB closed TT = Float(-99.) WB_close = Float(-99.) Diff_WC = Float(-99.) TWC = Float(-99.) W_Stress = Float(-99.) def initialize(self, day, kiosk, parametervalues): self.params = self.Parameters(parametervalues) self.rates = self.RateVariables(kiosk, publish=None) self.kiosk = kiosk layers = math.floor(self.params.RDMAX / self.params.TCK) self.states = self.StateVariables( kiosk, publish=["Ta", "W_Stress", "PTa"], PTa=0, TPE=0.0, TPT=0.0, WC=np.full(layers, self.params.FCP * self.params.TCK), WCv=np.full(layers, self.params.FCP * self.params.TCK), TT=0.0, Diff_WC=0., W_Stress=0, TINTERC=0, TRUNOFF=0, PERC=0, TE=0, Ta=0, WB_close=0, TPREC=0.0, TWC=0.) @prepare_rates def calc_rates(self, day, drv): p = self.params r = self.rates s = self.states k = self.kiosk if "FI" not in self.kiosk: k.FI = 0.0 else: k.FI = self.kiosk["FI"] if "TRD" not in self.kiosk: k.TRD = 0.0 else: k.TRD = self.kiosk["TRD"] r.PE = convert_cm_to_m(drv.ET0 * (1 - k.FI)) r.PT = convert_cm_to_m(drv.ET0 * k.FI) r.ETP = convert_cm_to_m(drv.ET0) * 100 * 10 # Convert fc and pwp to m of H2O r.nl = math.floor(p.RDMAX / p.TCK) r.FC = p.FCP * p.TCK r.PWP = p.PWPP * p.TCK r.ADWC = p.ADWCP * p.TCK # Parameters for eq os SPSI r.B = math.log(p.PSIPWP / p.PSIFC) / math.log(p.FCP / p.PWPP) r.A = math.exp((math.log(-p.PSIFC)) + r.B * math.log(max(p.FCP, sys.float_info.min))) # Interception r.PREC = drv.RAIN / 100 if drv.RAIN != 0.: r.INTERC = min(drv.RAIN / 100, 0.001 * k.FI) r.GPREC = ((drv.RAIN / 100) - r.INTERC) # Runoff calculation if r.GPREC <= 0.2 * p.S: r.RUNOFF = 0 else: r.RUNOFF = ( (r.GPREC - p.RUNOFF1 * p.S)**2) / (r.GPREC + p.RUNOFF2 * p.S) r.INFIL = ((drv.RAIN / 100) - r.INTERC - r.RUNOFF) # Recharge water per layer r.values_RW = [] r.values_FR = [] r.values_SPSI = [] for j in range(s.WC.shape[0]): if r.INFIL > 0: if r.INFIL <= (r.FC - s.WC[j]): r.RW = r.INFIL r.INFIL = 0. else: r.RW = r.FC - s.WC[j] r.INFIL = r.INFIL - (r.FC - s.WC[j]) else: r.RW = 0 r.values_RW.append(r.RW) r.RWATER = np.array(r.values_RW) # Evaporation calculation if j < 1: # if s.WC[0]>r.PWP: # r.EVS=r.PE if s.WC[0] < r.PWP: r.PE = r.PE * (((s.WC[0] / p.TCK) - p.ADWCP) / (p.PWPP - p.ADWCP))**2. r.NWC = s.WC[0] - r.PE r.EVS = r.PE if r.NWC < r.ADWC: r.NWC = r.ADWC r.EVS = r.EVS - r.NWC # Fraction root per layer if j >= 1: r.z += p.TCK if r.z <= k.TRD: r.FROOT = p.TCK * (2. * (k.TRD - r.z) + p.TCK) / (k.TRD * k.TRD) elif r.z > k.TRD and (r.z - p.TCK) < k.TRD: r.FROOT = ((k.TRD - r.z + p.TCK) / k.TRD)**2. else: r.FROOT = 0. r.SPSI = -r.A * mp.exp( -r.B * math.log(max( (s.WC[j] / p.TCK), sys.float_info.min))) r.AVEPSI += (r.FROOT * r.SPSI) r.values_FR.append(r.FROOT) r.FcR = np.array(r.values_FR) r.values_SPSI.append(r.SPSI) r.SP = np.array(r.values_SPSI) r.RBAR = p.RMIN / max(k.FI, 1e-70) r.PSIX = r.AVEPSI - (r.RBAR * r.PT) if r.PSIX < p.PSIPWP: r.PSIX = p.PSIPWP # Transpiration calculation r.arr_subt = np.subtract(r.SP, r.PSIX) r.arr_mult = np.multiply(r.FcR, r.arr_subt) r.LOSS = np.true_divide(r.arr_mult, (r.RBAR * p.TCK)) # check loss under water deficit r.W = np.true_divide(s.WC[1::], p.TCK) r.arr_bool = (np.subtract(r.W, r.LOSS)) < p.PWPP r.arr_TRUE = np.subtract(r.W, p.PWPP) # Actual evapotranspiration r.TL = np.multiply(np.where(r.arr_bool, r.arr_TRUE, r.LOSS), p.TCK) r.AT = np.append(r.EVS, r.TL) r.T = np.sum(r.TL) r.net_RW = np.subtract(r.RWATER, r.AT) @prepare_states def integrate(self, day, delt): p = self.params r = self.rates s = self.states k = self.kiosk s.PTa = r.PT # Soil water balance s.TPE += r.PE s.TPT = r.PT s.TPREC = r.PREC s.TINTERC += r.GPREC s.TRUNOFF += r.RUNOFF s.PERC += r.INFIL s.TE += r.EVS s.Ta = r.T s.WC = np.add(s.WC, r.net_RW) s.WCv = (s.WC / p.TCK) # check WB closed s.TT += r.T s.Diff_WC = np.sum(np.subtract(np.full(r.nl, r.FC), s.WC)) s.WB_close = s.TINTERC - s.TRUNOFF - s.PERC - s.TT - s.TE + s.Diff_WC s.TWC = (np.sum(np.subtract(s.WC, r.PWP))) * 1000 s.W_Stress = r.T / max(r.PT, 1e-70)
class RateVariables(RatesTemplate): PE = Float(-99.) PT = Float(-99.) EPT = Float(-99.) PREC = Float(-99.) nl = Int FC = Float(-99.) PWP = Float(-99.) ADWC = Float(-99.) B = Float(-99.) A = Float(-99.) INTERC = Float(-99.) GPREC = Float(-99.) RUNOFF = Float(-99.) INFIL = Float(-99.) RW = Float(-99.) values_RW = [] RWATER = Instance(np.ndarray) NWC = Float(-99.) EVS = Float(-99.) SPSI = Float(-99.) values_SPSI = [] SP = Instance(np.ndarray) #root FcR = Instance(np.ndarray) values_FR = [] FROOT = Float(-99.) AVEPSI = Float(-99.) RBAR = Float(-99.) PSIX = Float(-99.) z = Float(-99.) LOSS = Instance(np.ndarray) W = Instance(np.ndarray) arr_bool = Instance(np.ndarray) arr_TRUE = Instance(np.ndarray) TL = Instance(np.ndarray) net_RW = Instance(np.ndarray) T = Float(-99.) arr_subt = Instance(np.ndarray) arr_mult = Instance(np.ndarray) AT = Instance(np.ndarray) check = Float(-99.)
class Campbell(SimulationObject): """Parameters** ============ ================================================= ==== ======== Name Description Unit ============ ================================================= ==== ======== DVV1 Paramter 1 for vapor deficit equation DVV2 Paramter 2 for vapor deficit equation DVV3 Paramter 3 for vapor deficit equation NTR Nitrogen content in grain g.g-1 LNTR Nitrogen content in leaves g.g-1 FNTR Fraction of N translocated from leaves to seeds HD Factor to standardize humidity content at 13% K Light extinction coefficient (Kukal and Irmak, 2020) Ppar Proportion of PAR in the total radiation WUE Dry matter water ratio Pa DSLA Specific leaf area for dead leaves m-2 kg-1 RUE Radiation use efficiency (Kukal and Irmak, 2020) g Mj m2 GCC Conversion coefficient (CHO to soyeban seed) LAIC Critic leaf area index m-2 m-2 FTRANSL Fraction of TDM to be translocated RDRSHM Maximum relative death rate of leaves due to shading (LINTUL2) d-1 ============ ================================================= ==== ======== Rates** ============ ================================================= ==== ======== Name Description Unit ============ ================================================= ==== ======== FI Fractional interception PE Potential evaporation m PT Potential transpiration m VDD Correction by vapor deficit water KPa PARi Intercepted PAR Mj m-2 DM Rate of growth kg m-2 ROOT Growth rate root kg m-2 STEMS Growth rate stems kg m-2 LEAF Growth rate leaf kg m-2 SEED Growth rate storage organs kg m-2 TN Translocated nitrogen from leaves to grains kg m-2 DLEAF Senescence rate of leaf kg m-2 RDRSH Relative death rate of leaves due to shading (LINTUL2) d-1 RDRT Table of RDR as a function of temperature ============ ================================================= ==== ======== State variables** Name Description Unit ============ ================================================= ==== ======== TPE Total potential evaporation m TPT Total potential transpiration m TDM Total above-ground biomass kg m-2 TROOT Total weight of roots kg m-2 TSTEMS Total weight of stems kg m-2 TLEAF Total weight of leaves m-2 m-2 TSEED Total weight of storage organs kg m-2 LAI Leaf area index m-2 m-2 TDLEAF Total of dead leaves m-2 m-2 GLEAF Total of green leaves m-2 m-2 SLA Specific leaf area m-2 kg-1 ============ ================================================= ==== ======== **External dependencies:** ======= =================================== ================= ============ Name Description Provided by Unit ======= =================================== ================= ============ FR Fraction partitioned to roots. Y - FS Fraction partitioned to stems. Y - FL Fraction partitioned to leaves. Y - FO Fraction partitioned to storage orgains Y - DVS Development stage ======= =================================== ================= ============ """ # sub-model components for crop simulation pheno = Instance(SimulationObject) part = Instance(SimulationObject) soil = Instance(SimulationObject) class Parameters(ParamTemplate): RDRT = AfgenTrait() RDRSHM = Float(-99.) LAIC = Float(-99.) DVV1 = Float(-99.) DVV2 = Float(-99.) DVV3 = Float(-99.) initLAI = Float(-99.) K = Float(-99.) Ppar = Float(-99.) WUE = Float(-99.) DSLA = Float(-99.) NTR = Float(-99.) LNTR = Float(-99.) FNTR = Float(-99.) HD = Float(-99.) GCC = Float(-99.) FTRANSL = Float(-99.) SLATB = AfgenTrait() RUE = Float(-99.) RDMAX = Float(-99.) class RateVariables(RatesTemplate): RDRDV = Float(-99.) RDRSH = Float(-99.) RDR = Float(-99.) DLAI = Float(-99.) DM_W = Float(-99.) DM_R = Float(-99.) DM = Float(-99.) PDM = Float(-99.) VDD = Float(-99.) FI = Float(-99.) ROOT = Float(-99.) STEMS = Float(-99.) LEAF = Float(-99.) WLEAF = Float(-99.) SEED = Float(-99.) PSEED = Float(-99.) TN = Float(-99.) WDLEAF = Float(-99.) DLEAF = Float(-99.) GLEAF = Float(-99.) TRANSL = Float(-99.) #root RD = Float(-99.) WD = Float(-99.) class StateVariables(StatesTemplate): TDM = Float(-99.) TDMv = Float(-99.) TSTEM = Float(-99.) TLEAF = Float(-99.) TSEED = Float(-99.) YIELD = Float(-99.) LAI = Float(-99.) LAIFlowering = Float(-99.) TDLEAF = Float(-99.) SLA = Float(-99.) TDMFlowering = Float(-99.) TDMTRANSL = Float(-99.) POOLTRSL = Float(-99.) #root TRD = Float(-99.) da = Int CWDv = Float(-99.) CWDr = Float(-99.) def initialize(self, day, kiosk, parametervalues): self.params = self.Parameters(parametervalues) self.rates = self.RateVariables(kiosk, publish=["FI"]) self.kiosk = kiosk # Initialize components of the crop self.pheno = Phenology(day, kiosk, parametervalues) self.part = Partitioning(day, kiosk, parametervalues) DVS = self.kiosk["DVS"] SLA = self.params.SLATB(DVS) # ============================================================================= # # Initial total (living+dead) above-ground biomass of the crop # FR = self.kiosk["FR"] # FS = self.kiosk["FS"] # FL = self.kiosk["FL"] # FO = self.kiosk["FO"] # ============================================================================= self.states = self.StateVariables(kiosk, publish=["TRD"], TDM=0.00, TDMv=0, GLEAF=0.0, TSTEM=0.0, TLEAF=0.0, TSEED=0.0, YIELD=0.0, LAI=self.params.initLAI, TDLEAF=0, SLA=SLA, TRD=0.0, da=0, TDMFlowering=None, LAIFlowering=None, TDMTRANSL=0, POOLTRSL=0, CWDv=0., CWDr=0.) @prepare_rates def calc_rates(self, day, drv): p = self.params r = self.rates s = self.states k = self.kiosk self.pheno.calc_rates(day, drv) crop_stage = self.pheno.get_variable("STAGE") # if before emergence there is no need to continue # because only the phenology is running. if crop_stage == "emerging": return self.part.calc_rates(day, drv) r.VDD = convert_hPa_to_KPa(drv.TMAX - drv.TMIN) * ( (p.DVV1 * drv.TEMP + p.DVV2) * drv.TEMP + p.DVV3) print("VDD=", r.VDD) r.FI = 1. - mp.exp(-p.K * s.LAI) r.PARi = convert_j_Mj(drv.IRRAD) * p.Ppar * r.FI if k.DVS < 2: if "Ta" not in self.kiosk: k.Ta = 0.001 else: k.Ta = self.kiosk["Ta"] if "W_Stress" not in self.kiosk: k.W_Stress = 0.0 else: k.W_Stress = self.kiosk["W_Stress"] if "PTa" not in self.kiosk: k.PTa = 0.0 else: k.PTa = self.kiosk["PTa"] r.DM_W = k.Ta * (p.WUE / r.VDD) print("Ta=", k.Ta) print("DM=", r.DM_W) r.DM_R = convert_g_kg(r.PARi * p.RUE) r.DM = min(r.DM_W, r.DM_R) r.PDM = k.PTa * (p.WUE / r.VDD) r.STEMS = r.DM * k.FS r.WLEAF = r.DM * k.FL r.LEAF = r.DM * k.FL * convert_ha_m2(s.SLA) r.PSEED = r.PDM * k.FO # Biomass reallocated from vegetative to seed if s.TDMTRANSL > 0: r.TRANSL = r.PSEED - (r.DM * k.FO) r.SEED = (r.DM * k.FO) + r.TRANSL else: r.SEED = r.DM * k.FO # Senescence from N translocation r.TN = ((r.SEED * p.NTR) / p.FNTR) #senescence rate from LINTUL if k.DVS > 1.5: r.RDRDV = p.RDRT(drv.TEMP) r.RDRSH = p.RDRSHM * ((s.LAI - p.LAIC) / p.LAIC) if r.RDRSH > 0 or r.RDRSH < 0.03: r.RDRSH = r.RDRSH if r.RDRSH < 0: r.RDRSH = 0 if r.RDRSH > 0.03: r.RDRSH = 0.03 else: r.RDRDV = 0 r.RDR = max(r.RDRDV, r.RDRSH) r.DLAI = s.LAI * r.RDR r.WDLEAF = r.TN / p.LNTR r.DLEAF = r.WDLEAF * p.DSLA r.GLEAF = r.LEAF - max(r.DLAI, r.DLEAF) #Rooting growth r.RD = p.RDMAX * (1. / (1 + 44.2 * math.exp(-15 * (s.da) / (140)))) r.WD = k.PTa - k.Ta @prepare_states def integrate(self, day, delt): p = self.params r = self.rates s = self.states k = self.kiosk # crop stage before integration crop_stage = self.pheno.get_variable("STAGE") self.pheno.integrate(day, delt=1.0) # if before emergence there is no need to continue # because only the phenology is running. # Just run a touch() to to ensure that all state variables are available # in the kiosk if crop_stage == "emerging": self.touch() return self.part.integrate(day, delt=1.0) DVS = self.kiosk["DVS"] s.SLA = p.SLATB(DVS) s.TDM += r.DM print("TDM", s.TDM) s.TDMv += r.STEMS + r.WLEAF if s.TDMFlowering is None and k.DVS >= 1.: s.TDMFlowering = s.TDMv s.TDMTRANSL = s.TDMFlowering * p.FTRANSL s.TDMTRANSL -= r.TRANSL s.TSEED += r.SEED s.YIELD = s.TSEED * p.GCC * p.HD s.TSTEM += r.STEMS s.TLEAF += r.DM * k.FL s.LAI += r.GLEAF if s.LAIFlowering is None and k.DVS >= 1.3: s.LAIFlowering = s.LAI s.da += 1 s.TRD = r.RD if k.DVS < 1: s.CWDv += r.WD else: s.CWDr += r.WD
class SoybeanPhenology(SimulationObject): """Implements the algorithms for phenologic development in WOFOST specifically for soybean. Phenologic development in WOFOST is expresses using a unitless scale which takes the values 0 at emergence, 1 at Anthesis (flowering) and 2 at maturity. This type of phenological development is mainly representative for cereal crops. All other crops that are simulated with WOFOST are forced into this scheme as well, although this may not be appropriate for all crops. For example, for potatoes development stage 1 represents the start of tuber formation rather than flowering. **Simulation parameters** ======= ============================================= ======= ============ Name Description Type Unit ======= ============================================= ======= ============ TSUMEM Temperature sum from sowing to emergence SCr |C| day TBASEM Base temperature for emergence SCr |C| TEFFMX Maximum effective temperature for emergence SCr |C| DVRMAX1 Maximum development rate emergence to anthesis SCr |C| day DVRMAX2 Maximum develpment rate anthesis to maturity SCr |C| day DVSI Initial development stage at emergence. SCr - Usually this is zero, but it can be higher for crops that are transplanted (e.g. paddy rice) DVSEND Final development stage SCr - MG Maturity group rating for daylength sensivity SCr - Topt Optimum temperature for phenological dev. SCr |C| Tmin Temperature below which development is zero. SCr |C| Tmax Temperature above which development is zero. SCr |C| ======= ============================================= ======= ============ **State variables** ======= ================================================= ==== ============ Name Description Pbl Unit ======= ================================================= ==== ============ DVS Development stage Y - TSUM Temperature sum N |C| day TSUME Temperature sum for emergence N |C| day DOS Day of sowing N - DOE Day of emergence N - DOR1 Day of R1 stage (beginning of flowering) N - DOR3 Day of R3 stage (pod development) N - DOR5 Day of R5 stage (seed development) N - DOR8 Day of R8 stage (fully ripe N - STAGE Current phenological stage, can take the N - folowing values: `emerging|vegetative|reproductive|mature` ======= ================================================= ==== ============ **Rate variables** ======= ================================================= ==== ============ Name Description Pbl Unit ======= ================================================= ==== ============ DTSUME Increase in temperature sum for emergence N |C| DTSUM Increase in temperature sum for anthesis or N |C| maturity DVR Development rate Y |day-1| ======= ================================================= ==== ============ **External dependencies:** None **Signals sent or handled** `SoybeanPhenology` sends the `crop_finish` signal when maturity is reached and the `end_type` is 'maturity' or 'earliest'. """ photoperiod_reduction_factor = Instance(PhotoperiodReductionFactor) temperature_reduction_factor = Instance(TemperatureReductionFactor) class Parameters(ParamTemplate): TSUMEM = Float(-99.) # Temp. sum for emergence TBASEM = Float(-99.) # Base temp. for emergence TEFFMX = Float(-99.) # Max eff temperature for emergence DVRMAX1 = Float(-99.) # Max development rate towards anthesis DVRMAX2 = Float(-99.) # Max development rate towards maturity DVSI = Float(-99.) # Initial development stage DVSEND = Float(-99.) # Final development stage CROP_START_TYPE = Enum(["sowing", "emergence"]) CROP_END_TYPE = Enum(["maturity", "harvest", "earliest"]) #------------------------------------------------------------------------------- class RateVariables(RatesTemplate): DTSUME = Float(-99.) # increase in temperature sum for emergence DVR = Float(-99.) # development rate DAYL = Float(-99) PHOTORF = Float(-99) TEMPRF = Float(-99) #------------------------------------------------------------------------------- class StateVariables(StatesTemplate): DVS = Float(-99.) # Development stage TSUM = Float(-99.) # Temperature sum state TSUME = Float(-99.) # Temperature sum for emergence state # States which register phenological events DOS = Instance(date) # Day of sowing DOE = Instance(date) # Day of emergence DOR1 = Instance(date) # Day of start of flowering DOR3 = Instance(date) # Day of pod development DOR5 = Instance(date) # Day of seed filling DOR8 = Instance(date) # Day of full ripeness DOH = Instance(date) # Day of harvest STAGE = Enum( [None, "emerging", "vegetative", "reproductive", "mature"]) #--------------------------------------------------------------------------- def initialize(self, day, kiosk, parvalues): """ :param day: start date of the simulation :param kiosk: variable kiosk of this PCSE instance :param parvalues: `ParameterProvider` object providing parameters as key/value pairs """ self.photoperiod_reduction_factor = PhotoperiodReductionFactor( day, kiosk, parvalues) self.temperature_reduction_factor = TemperatureReductionFactor( day, kiosk, parvalues) self.params = self.Parameters(parvalues) self.rates = self.RateVariables(kiosk) self.kiosk = kiosk self._connect_signal(self._on_CROP_FINISH, signal=signals.crop_finish) # Define initial states DVS = self.params.DVSI DOS, DOE, STAGE = self._get_initial_stage(day) self.states = self.StateVariables(kiosk, publish="DVS", TSUM=0., TSUME=0., DVS=DVS, DOS=DOS, DOE=DOE, DOR1=None, DOR3=None, DOR5=None, DOR8=None, DOH=None, STAGE=STAGE) def _get_initial_stage(self, day): """""" p = self.params # Define initial stage type (emergence/sowing) and fill the # respective day of sowing/emergence (DOS/DOE) if p.CROP_START_TYPE == "emergence": STAGE = "vegetative" DOE = day DOS = None # send signal to indicate crop emergence self._send_signal(signals.crop_emerged) elif p.CROP_START_TYPE == "sowing": STAGE = "emerging" DOS = day DOE = None else: msg = "Unknown start type: %s" % p.CROP_START_TYPE raise exc.PCSEError(msg) return DOS, DOE, STAGE @prepare_rates def calc_rates(self, day, drv): """Calculates the rates for phenological development """ p = self.params r = self.rates s = self.states # Day length r.DAYL = daylength(day, drv.LAT, angle=-0.83) # temperature reduction factor r.TEMPRF = self.temperature_reduction_factor(drv.TEMP) # photoperiod r.PHOTORF = 1.0 if s.STAGE == "emerging": r.DTSUME = limit(0., (p.TEFFMX - p.TBASEM), (drv.TEMP - p.TBASEM)) elif s.STAGE == 'vegetative': r.DVR = p.DVRMAX1 * r.TEMPRF elif s.STAGE in ['reproductive', 'mature']: # photoperiod reduction factor r.PHOTORF = self.photoperiod_reduction_factor(r.DAYL) r.DVR = p.DVRMAX2 * r.PHOTORF * r.TEMPRF else: # Problem: no stage defined msg = "No STAGE defined in phenology submodule" raise exc.PCSEError(msg) msg = "Finished rate calculation for %s" self.logger.debug(msg % day) #--------------------------------------------------------------------------- @prepare_states def integrate(self, day, delt): """Updates the state variable and checks for phenologic stages """ p = self.params r = self.rates s = self.states if s.STAGE == "emerging": s.TSUME += r.DTSUME if s.TSUME >= p.TSUMEM: self._next_stage(day) elif s.STAGE == 'vegetative': s.DVS += r.DVR if s.DVS >= 1.0: self._next_stage(day) s.DVS = 1.0 elif s.STAGE == 'reproductive': s.DVS += r.DVR # Check of R5 stage is reached at DVS=1.15. if s.DVS >= 1.15 and s.DOR5 is None: s.DOR5 = day # Check if maturity is reached if s.DVS >= p.DVSEND: self._next_stage(day) elif s.STAGE == 'mature': s.DVS += r.DVR else: # Problem no stage defined msg = "No STAGE defined in phenology submodule" raise exc.PCSEError(msg) msg = "Finished state integration for %s" self.logger.debug(msg % day) #--------------------------------------------------------------------------- def _next_stage(self, day): """Moves states.STAGE to the next phenological stage""" s = self.states p = self.params current_STAGE = s.STAGE if s.STAGE == "emerging": s.STAGE = "vegetative" s.DOE = day # send signal to indicate crop emergence self._send_signal(signals.crop_emerged) elif s.STAGE == "vegetative": s.STAGE = "reproductive" s.DOR1 = day elif s.STAGE == "reproductive": s.STAGE = "mature" s.DOR8 = day if p.CROP_END_TYPE in ["maturity", "earliest"]: self._send_signal(signal=signals.crop_finish, day=day, finish_type="maturity") elif s.STAGE == "mature": msg = "Cannot move to next phenology stage: maturity already reached!" raise exc.PCSEError(msg) else: # Problem no stage defined msg = "No STAGE defined in phenology submodule." raise exc.PCSEError(msg) msg = "Changed phenological stage '%s' to '%s' on %s" self.logger.info(msg % (current_STAGE, s.STAGE, day)) #--------------------------------------------------------------------------- def _on_CROP_FINISH(self, day, finish_type=None): """Handler for setting day of harvest (DOH). Although DOH is not strictly related to phenology (but to management) this is the most logical place to put it. """ if finish_type == 'harvest': self._for_finalize["DOH"] = day
class StateVariables(StatesTemplate): FR = Float(-99.) FL = Float(-99.) FS = Float(-99.) FO = Float(-99.) PF = Instance(PartioningFactors)