def calculate_soil_water_fac(self): """ Estimate a relative water availability factor [0..1] A drying soil results in physiological stress that can induce stomatal closure and reduce transpiration. Further N mineralisation depends on top soil moisture. References: ----------- * Pepper et al. (2008) Functional Change Biology, 35, 493-508 But similarly see: * van Genuchten (1981) Soil Sci. Soc. Am. J, 44, 892--898. * Wang and Leuning (1998) Ag Forest Met, 91, 89-111. Returns: -------- wtfac_tsoil : float water availability factor for the top soil [0,1] wtfac_root : float water availability factor for the root zone [0,1] """ # turn into fraction... smc_root = self.state.pawater_root / self.params.wcapac_root smc_topsoil = self.state.pawater_tsoil / self.params.wcapac_topsoil # Calculate a soil moisture availability factor, used to adjust # ci/ca ratio in the face of limited water supply. arg = self.params.fwpmax - self.params.fwpmin wtfac_tsoil = (smc_topsoil - self.params.fwpmin) / arg wtfac_root = (smc_root - self.params.fwpmin) / arg return (clip(wtfac_tsoil, min=0.0, max=1.0), clip(wtfac_root, min=0.0, max=1.0))
def update_water_storage(self, tolerance=1E-08): """ Calculate root and top soil plant available water and runoff. Soil drainage is estimated using a "leaky-bucket" approach with two soil layers. In reality this is a combined drainage and runoff calculation, i.e. "outflow". There is no drainage out of the "bucket" soil. Returns: -------- outflow : float outflow [mm d-1] """ # reduce transpiration from the top soil if it is dry trans_frac = (self.params.fractup_soil * self.state.wtfac_topsoil) # Total soil layer self.state.pawater_topsoil += (self.fluxes.erain - (self.fluxes.transpiration * trans_frac) - self.fluxes.soil_evap) self.state.pawater_topsoil = clip(self.state.pawater_topsoil, min=0.0, max=self.params.wcapac_topsoil) # Total root zone previous = self.state.pawater_root self.state.pawater_root += (self.fluxes.erain - self.fluxes.transpiration - self.fluxes.soil_evap) # calculate runoff and remove any excess from rootzone if self.state.pawater_root > self.params.wcapac_root: runoff = self.state.pawater_root - self.params.wcapac_root self.state.pawater_root -= runoff else: runoff = 0.0 if float_le(self.state.pawater_root, 0.0): self.fluxes.transpiration = 0.0 self.fluxes.soil_evap = 0.0 self.fluxes.et = self.fluxes.interception self.state.pawater_root = clip(self.state.pawater_root, min=0.0, max=self.params.wcapac_root) self.state.delta_sw_store = self.state.pawater_root - previous return runoff
def calc_infiltration(self, rain): """ Estimate "effective" rain, or infiltration I guess. Simple assumption that infiltration relates to leaf area and therefore canopy storage capacity (wetloss). Interception is likely to be ("more") erroneous if a canopy is subject to frequent daily rainfall I would suggest. Parameters: ------- rain : float rainfall [mm d-1] """ if float_gt(rain, 0.0): self.fluxes.interception = self.state.lai * self.params.wetloss self.fluxes.interception = clip(self.fluxes.interception, min=self.fluxes.interception, max=rain) self.fluxes.erain = (rain * self.params.rfmult - self.fluxes.interception) else: self.fluxes.interception = 0.0 self.fluxes.erain = 0.0
def update_water_storage(self, tolerance=1E-08): """ Calculate root and top soil plant available water and runoff. Soil drainage is estimated using a "leaky-bucket" approach with two soil layers. In reality this is a combined drainage and runoff calculation, i.e. "outflow". There is no drainage out of the "bucket" soil. Returns: -------- outflow : float outflow [mm d-1] """ # reduce transpiration from the top soil if it is dry trans_frac = (self.params.fractup_soil * self.state.wtfac_topsoil) # Total soil layer self.state.pawater_topsoil += (self.fluxes.erain - (self.fluxes.transpiration * trans_frac) - self.fluxes.soil_evap) self.state.pawater_topsoil = clip(self.state.pawater_topsoil, min=0.0, max=self.params.wcapac_topsoil) # Total root zone previous = self.state.pawater_root self.state.pawater_root += (self.fluxes.erain - self.fluxes.transpiration - self.fluxes.soil_evap) # calculate runoff and remove any excess from rootzone if self.state.pawater_root > self.params.wcapac_root: runoff = self.state.pawater_root - self.params.wcapac_root self.state.pawater_root -= runoff else: runoff = 0.0 self.state.pawater_root = clip(self.state.pawater_root, min=0.0, max=self.params.wcapac_root) self.state.delta_sw_store = self.state.pawater_root - previous return runoff
def update_water_storage(self): """ Calculate root and top soil plant available water and runoff. Soil drainage is estimated using a "leaky-bucket" approach with two soil layers. In reality this is a combined drainage and runoff calculation, i.e. "outflow". There is no drainage out of the "bucket" soil. Returns: -------- outflow : float outflow [mm d-1] """ # Total root zone prev = self.state.pawater_root self.state.pawater_root += (self.fluxes.erain - self.fluxes.transpiration - self.fluxes.soil_evap) if self.state.pawater_root > self.params.wcapac_root: runoff = self.state.pawater_root - self.params.wcapac_root else: runoff = 0.0 self.state.pawater_root = clip(self.state.pawater_root, min=0.0, max=self.params.wcapac_root) self.delta_store = self.state.pawater_root - prev # Total soil layer self.state.pawater_tsoil += (self.fluxes.erain - self.fluxes.transpiration * self.params.fractup_soil - self.fluxes.soil_evap) self.state.pawater_tsoil = clip(self.state.pawater_tsoil, min=0.0, max=self.params.wcapac_topsoil) return runoff
def calc_soil_evaporation(self, avg_temp, net_rad, press): """ Use Penman eqn to calculate top soil evaporation flux at the potential rate. Soil evaporation is dependent upon soil wetness and plant cover. The net radiation term is scaled for the canopy cover passed to this func and the impact of soil wetness is accounted for in the wtfac term. As the soil dries the evaporation component reduces significantly. Key assumptions from Ritchie... * When plant provides shade for the soil surface, evaporation will not be the same as bare soil evaporation. Wind speed, net radiation and VPD will all belowered in proportion to the canopy density. Following Ritchie role ofwind, VPD are assumed to be negligible and are therefore ignored. These assumptions are based on work with crops and whether this holds for tree shading where the height from the soil to the base of the crown is larger is questionable. units = (mm/day) References: ----------- * Ritchie, 1972, Water Resources Research, 8, 1204-1213. Parameters: ----------- avg_temp : float average daytime temp [degC] net_rad : float net radiation [mj m-2 day-1] press : float average daytime pressure [kPa] Returns: -------- soil_evap : float soil evaporation [mm d-1] """ P = Penman() soil_evap = P.calc_evaporation(net_rad, avg_temp, press) # if the available soil moisture is low the soil evaporation needs to # be reduced as well wtfac = (((self.state.pawater_tsoil / self.params.wcapac_topsoil) - self.params.fwpmin) / (self.params.fwpmax - self.params.fwpmin)) soil_evap *= clip(wtfac, min=0.0, max=1.0) return soil_evap
def update_water_storage(self): """ Calculate root and top soil plant available water Soil drainage is estimated using a "leaky-bucket" approach with two soil layers. My visualisation is of 2 seperate buckets and the plant available water encompasses the top soil as well. """ # Total root zone self.state.pawater_root += (self.fluxes.erain - self.fluxes.transpiration - self.fluxes.soil_evap) self.state.pawater_root = clip(self.state.pawater_root, min=0.0, max=self.params.wcapac_root) # Total soil layer self.state.pawater_tsoil += (self.fluxes.erain - self.fluxes.transpiration * self.params.fractup_soil - self.fluxes.soil_evap) self.state.pawater_tsoil = clip(self.state.pawater_tsoil, min=0.0, max=self.params.wcapac_topsoil)
def iterationClip(cand, minLimit, maxLimit, limitless): """ Adayın limitless değerine göre clip edilip edilmeyeceğine karar verir ve gerekliyse clip edip döndürür. Gerekli değil ise clip edilmemiş halini döndürür. Zorunlu Argümanlar: cand: Adayın kendisi (list) minLimit: İstenen minimum değer (int or float) maxLimit: İstenen maximum değer (int or float) limitless: Adayın alabileceği değerlerin sınırsız olduğunu söyler (bool) """ if limitless: return cand else: return ut.clip(cand, minLimit, maxLimit)
def calc_carbon_allocation_fracs(self, nitfac, yr_index, project_day): """Carbon allocation fractions to move photosynthate through the plant. Parameters: ----------- nitfac : float leaf N:C as a fraction of 'Ncmaxfyoung' (max 1.0) Returns: -------- alleaf : float allocation fraction for shoot alroot : float allocation fraction for fine roots albranch : float allocation fraction for branches alstem : float allocation fraction for stem References: ----------- Corbeels, M. et al (2005) Ecological Modelling, 187, 449-474. McMurtrie, R. E. et al (2000) Plant and Soil, 224, 135-152. """ if self.control.alloc_model == "FIXED": self.state.alleaf = ( self.params.c_alloc_fmax + nitfac * (self.params.c_alloc_fmax - self.params.c_alloc_fmin)) self.state.alroot = ( self.params.c_alloc_rmax + nitfac * (self.params.c_alloc_rmax - self.params.c_alloc_rmin)) self.state.albranch = ( self.params.c_alloc_bmax + nitfac * (self.params.c_alloc_bmax - self.params.c_alloc_bmin)) # allocate remainder to stem self.state.alstem = (1.0 - self.state.alleaf - self.state.alroot - self.state.albranch) #print self.state.alleaf, self.state.alstem, self.state.albranch, self.state.alroot elif self.control.alloc_model == "GRASSES": # calculate the N limitation based on available canopy N # this logic appears counter intuitive, but it works out when # applied with the perhaps backwards logic below nf = self.state.shootnc # case - completely limited by N availability if nf < self.params.nf_min: nlim = 0.0 elif nf < self.params.nf_crit: nlim = ((nf - self.params.nf_min) / (self.params.nf_crit - self.params.nf_min)) # case - no N limitation else: nlim = 1.0 # no constraint on water uptake via root mass, so makes no sense #limitation = self.sma(min(nlim, self.state.wtfac_root)) # to increase allocation if water stressed. # dependent on lifespan of the roots... limitation = self.sma(nlim) self.state.prev_sma = limitation # figure out root allocation given available water & nutrients # hyperbola shape to allocation self.state.alroot = ( self.params.c_alloc_rmax * self.params.c_alloc_rmin / (self.params.c_alloc_rmin + (self.params.c_alloc_rmax - self.params.c_alloc_rmin) * limitation)) self.state.alstem = 0.0 self.state.albranch = 0.0 self.state.alleaf = (1.0 - self.state.alroot) #print nlim, limitation, self.state.alleaf, self.state.alroot elif self.control.alloc_model == "ALLOMETRIC": # calculate the N limitation based on available canopy N # this logic appears counter intuitive, but it works out when # applied with the perhaps backwards logic below nf = self.state.shootnc # case - completely limited by N availability if nf < self.params.nf_min: nlim = 0.0 elif nf < self.params.nf_crit: nlim = ((nf - self.params.nf_min) / (self.params.nf_crit - self.params.nf_min)) # case - no N limitation else: nlim = 1.0 # no constraint on water uptake via root mass, so makes no sense #limitation = self.sma(min(nlim, self.state.wtfac_root)) # to increase allocation if water stressed. # dependent on lifespan of the roots... limitation = self.sma(nlim) self.state.prev_sma = limitation # figure out root allocation given available water & nutrients # hyperbola shape to allocation self.state.alroot = ( self.params.c_alloc_rmax * self.params.c_alloc_rmin / (self.params.c_alloc_rmin + (self.params.c_alloc_rmax - self.params.c_alloc_rmin) * limitation)) #self.state.alroot = (self.params.c_alloc_rmin + # (self.params.c_alloc_rmax - # self.params.c_alloc_rmin) * limitation) # Calculate tree height: allometric reln using the power function # (Causton, 1985) self.state.canht = (self.params.heighto * self.state.stem**self.params.htpower) # LAI to stem sapwood cross-sectional area (As m-2 m-2) # (dimensionless) # Assume it varies between LS0 and LS1 as a linear function of tree # height (m) sap_cross_sec_area = ( ((self.state.sapwood * const.TONNES_AS_KG * const.M2_AS_HA) / self.params.cfracts) / self.state.canht / self.params.density) leaf2sap = self.state.lai / sap_cross_sec_area # Allocation to leaves dependant on height. Modification of pipe # theory, leaf-to-sapwood ratio is not constant above a certain # height, due to hydraulic constraints (Magnani et al 2000; Deckmyn # et al. 2006). if self.params.leafsap0 < self.params.leafsap1: min_target = self.params.leafsap0 else: min_target = self.params.leafsap1 if self.params.leafsap0 > self.params.leafsap1: max_target = self.params.leafsap0 else: max_target = self.params.leafsap1 leaf2sa_target = (self.params.leafsap0 + (self.params.leafsap1 - self.params.leafsap0) * (self.state.canht - self.params.height0) / (self.params.height1 - self.params.height0)) leaf2sa_target = clip(leaf2sa_target, min=min_target, max=max_target) self.state.alleaf = self.alloc_goal_seek(leaf2sap, leaf2sa_target, self.params.c_alloc_fmax, self.params.targ_sens) # Allocation to branch dependent on relationship between the stem # and branch target_branch = (self.params.branch0 * self.state.stem**self.params.branch1) self.state.albranch = self.alloc_goal_seek( self.state.branch, target_branch, self.params.c_alloc_bmax, self.params.targ_sens) #target_coarse_roots = 0.34 * self.state.stem**0.84 #self.state.alcroot = self.alloc_goal_seek(self.state.croot, # target_coarse_roots, # self.params.c_alloc_crmax, # self.params.targ_sens) # allocation to stem is the residual self.state.alstem = (1.0 - self.state.alroot - self.state.albranch - self.state.alleaf) #print self.state.alleaf, self.state.albranch, self.state.alstem, self.state.alroot else: raise AttributeError('Unknown C allocation model') # Total allocation should be one, if not print warning: total_alloc = (self.state.alroot + self.state.alleaf + self.state.albranch + self.state.alstem) if float_gt(total_alloc, 1.0): raise RuntimeError, "Allocation fracs > 1"
def calc_carbon_allocation_fracs(self, nitfac, yr_index, project_day): """Carbon allocation fractions to move photosynthate through the plant. Parameters: ----------- nitfac : float leaf N:C as a fraction of 'Ncmaxfyoung' (max 1.0) Returns: -------- alleaf : float allocation fraction for shoot alroot : float allocation fraction for fine roots albranch : float allocation fraction for branches alstem : float allocation fraction for stem References: ----------- Corbeels, M. et al (2005) Ecological Modelling, 187, 449-474. McMurtrie, R. E. et al (2000) Plant and Soil, 224, 135-152. """ if self.control.alloc_model == "FIXED": self.state.alleaf = (self.params.c_alloc_fmax + nitfac * (self.params.c_alloc_fmax - self.params.c_alloc_fmin)) self.state.alroot = (self.params.c_alloc_rmax + nitfac * (self.params.c_alloc_rmax - self.params.c_alloc_rmin)) self.state.albranch = (self.params.c_alloc_bmax + nitfac * (self.params.c_alloc_bmax - self.params.c_alloc_bmin)) # allocate remainder to stem self.state.alstem = (1.0 - self.state.alleaf - self.state.alroot - self.state.albranch) #print self.state.alleaf, self.state.alstem, self.state.albranch, self.state.alroot elif self.control.alloc_model == "GRASSES": # calculate the N limitation based on available canopy N # this logic appears counter intuitive, but it works out when # applied with the perhaps backwards logic below nf = self.state.shootnc # case - completely limited by N availability if nf < self.params.nf_min: nlim = 0.0 elif nf < self.params.nf_crit: nlim = ((nf - self.params.nf_min) / (self.params.nf_crit - self.params.nf_min)) # case - no N limitation else: nlim = 1.0 # no constraint on water uptake via root mass, so makes no sense #limitation = self.sma(min(nlim, self.state.wtfac_root)) # to increase allocation if water stressed. # dependent on lifespan of the roots... limitation = self.sma(nlim) self.state.prev_sma = limitation # figure out root allocation given available water & nutrients # hyperbola shape to allocation self.state.alroot = (self.params.c_alloc_rmax * self.params.c_alloc_rmin / (self.params.c_alloc_rmin + (self.params.c_alloc_rmax - self.params.c_alloc_rmin) * limitation)) self.state.alstem = 0.0 self.state.albranch = 0.0 self.state.alleaf = (1.0 - self.state.alroot) #print nlim, limitation, self.state.alleaf, self.state.alroot elif self.control.alloc_model == "ALLOMETRIC": # calculate the N limitation based on available canopy N # this logic appears counter intuitive, but it works out when # applied with the perhaps backwards logic below nf = self.state.shootnc # case - completely limited by N availability if nf < self.params.nf_min: nlim = 0.0 elif nf < self.params.nf_crit: nlim = ((nf - self.params.nf_min) / (self.params.nf_crit - self.params.nf_min)) # case - no N limitation else: nlim = 1.0 # no constraint on water uptake via root mass, so makes no sense #limitation = self.sma(min(nlim, self.state.wtfac_root)) # to increase allocation if water stressed. # dependent on lifespan of the roots... limitation = self.sma(nlim) self.state.prev_sma = limitation # figure out root allocation given available water & nutrients # hyperbola shape to allocation self.state.alroot = (self.params.c_alloc_rmax * self.params.c_alloc_rmin / (self.params.c_alloc_rmin + (self.params.c_alloc_rmax - self.params.c_alloc_rmin) * limitation)) #self.state.alroot = (self.params.c_alloc_rmin + # (self.params.c_alloc_rmax - # self.params.c_alloc_rmin) * limitation) # Calculate tree height: allometric reln using the power function # (Causton, 1985) self.state.canht = (self.params.heighto * self.state.stem**self.params.htpower) # LAI to stem sapwood cross-sectional area (As m-2 m-2) # (dimensionless) # Assume it varies between LS0 and LS1 as a linear function of tree # height (m) sap_cross_sec_area = (((self.state.sapwood * const.TONNES_AS_KG * const.M2_AS_HA) / self.params.cfracts) / self.state.canht / self.params.density) leaf2sap = self.state.lai / sap_cross_sec_area # Allocation to leaves dependant on height. Modification of pipe # theory, leaf-to-sapwood ratio is not constant above a certain # height, due to hydraulic constraints (Magnani et al 2000; Deckmyn # et al. 2006). if self.params.leafsap0 < self.params.leafsap1: min_target = self.params.leafsap0 else: min_target = self.params.leafsap1 if self.params.leafsap0 > self.params.leafsap1: max_target = self.params.leafsap0 else: max_target = self.params.leafsap1 leaf2sa_target = (self.params.leafsap0 + (self.params.leafsap1 - self.params.leafsap0) * (self.state.canht - self.params.height0) / (self.params.height1 - self.params.height0)) leaf2sa_target = clip(leaf2sa_target, min=min_target, max=max_target) self.state.alleaf = self.alloc_goal_seek(leaf2sap, leaf2sa_target, self.params.c_alloc_fmax, self.params.targ_sens) # Allocation to branch dependent on relationship between the stem # and branch target_branch = (self.params.branch0 * self.state.stem**self.params.branch1) self.state.albranch = self.alloc_goal_seek(self.state.branch, target_branch, self.params.c_alloc_bmax, self.params.targ_sens) #target_coarse_roots = 0.34 * self.state.stem**0.84 #self.state.alcroot = self.alloc_goal_seek(self.state.croot, # target_coarse_roots, # self.params.c_alloc_crmax, # self.params.targ_sens) # allocation to stem is the residual self.state.alstem = (1.0 - self.state.alroot - self.state.albranch - self.state.alleaf) #print self.state.alleaf, self.state.albranch, self.state.alstem, self.state.alroot else: raise AttributeError('Unknown C allocation model') # Total allocation should be one, if not print warning: total_alloc = (self.state.alroot + self.state.alleaf + self.state.albranch + self.state.alstem) if float_gt(total_alloc, 1.0): raise RuntimeError, "Allocation fracs > 1"