def stomatal_conductance(self, g0, g1, gb, m, A_net, CO2, RH, drb, gamma=U(10, 'umol/mol')): Cs = CO2 - (drb * A_net / gb) # surface CO2 in mole fraction Cs = clip(Cs, lower=gamma) a = m * g1 * A_net / Cs b = g0 + gb - a c = (-RH * gb) - g0 #hs = max(np.roots([a, b, c])) #hs = scipy.optimize.brentq(lambda x: np.polyval([a, b, c], x), 0, 1) #hs = scipy.optimize.fsolve(lambda x: np.polyval([a, b, c], x), 0) hs = quadratic_solve_upper(a, b, c) #hs = clip(hs, 0.1, 1.0) # preventing bifurcation: used to be (0.3, 1.0) for C4 maize #FIXME unused? #T_leaf = l.temperature #es = w.vp.saturation(T_leaf) #Ds = (1 - hs) * es # VPD at leaf surface #Ds = w.vp.deficit(T_leaf, hs) gs = g0 + (g1 * m * (A_net * hs / Cs)) gs = clip(gs, lower=g0) return gs
def temperature_dependence_rate(self, Ea, T, Tb=U(25, 'degC')): R = U(8.314, 'J/K/mol') # universal gas constant (J K-1 mol-1) #HACK handle too low temperature values during optimization Tk = clip(T, lower=0, unit='degK') Tbk = clip(Tb, lower=0, unit='degK') try: return np.exp(Ea * (T - Tb) / (Tbk * R * Tk)) except ZeroDivisionError: return 0
def Vp(self, Vpmax, Cm, Kp): # PEP carboxylation rate, that is the rate of C4 acid generation Vp = (Cm * Vpmax) / (Cm + Kp / U(1, 'atm')) Vpr = U(80, 'umol/m^2/s CO2' ) # PEP regeneration limited Vp, value adopted from vC book Vp = clip(Vp, 0, Vpr) return Vp
def optical_air_mass_number(self, elevation_angle): t_s = clip(elevation_angle, lower=0, unit='rad') #FIXME need to do max(0.0001, sin(t_s))? try: #FIXME check 101.3 is indeed in kPa return self.atmospheric_pressure / (U(101.3, 'kPa') * sin(t_s)) except: return 0
def evapotranspiration(self, vp='weather.vp'): gv = self.stomata.total_conductance_h2o ea = vp.ambient(self.weather.T_air, self.weather.RH) es_leaf = vp.saturation(self.temperature) ET = gv * ((es_leaf - ea) / self.weather.P_air) / (1 - (es_leaf + ea) / self.weather.P_air) return clip( ET, lower=0) # 04/27/2011 dt took out the 1000 everything is moles now
def _temperature_effect(self, T_grow, T_peak, T_base): # T_peak is the optimal growth temperature at which the potential leaf size determined in calc_mophology achieved. # Similar concept to fig 3 of Fournier and Andreiu (1998) # phyllochron corresponds to PHY in Lizaso (2003) # phyllochron needed for next leaf appearance in degree days (GDD8) - 08/16/11, SK. #phyllochron = (dv->get_T_Opt()- Tb)/(dv->get_Rmax_LTAR()); T_ratio = (T_grow - T_base) / (T_peak - T_base) # final leaf size is adjusted by growth temperature determining cell size during elongation return clip(T_ratio * exp(1 - T_ratio), lower=0)
def _water_potential_effect(self, psi_predawn, threshold): #psi_predawn = self.p.soil.WP_leaf_predawn psi_th = threshold # threshold wp below which stress effect shows up # DT Oct 10, 2012 changed this so it was not as sensitive to stress near -0.5 lwp # SK Sept 16, 2014 recalibrated/rescaled parameter estimates in Yang's paper. The scale of Boyer data wasn't set correctly # sensitivity = 1.92, LeafWPhalf = -1.86, the sensitivity parameter may be raised by 0.3 to 0.5 to make it less sensitivy at high LWP, SK s_f = 0.4258 # 0.5 psi_f = -1.4251 # -1.0 e = (1 + exp(s_f * psi_f)) / (1 + exp(s_f * (psi_f - (psi_predawn - psi_th)))) return clip(e, upper=1.0)
def senescence_ratio(self): # for MAIZSIM # t = self.senescence_age # t_e = self.senescence_duration # if t >= t_e: # return 1 # else: # t_m = t_e / 2 # r = (1 + (t_e - t) / (t_e - t_m)) * (t / t_e)**(t_e / (t_e - t_m)) # return clip(r, 0., 1.) # for garlic #HACK prevents nan if self.length == 0: r = 0. else: r = self.aging_rate * self.senescence_age / self.length return clip(r, 0., 1.)
def potential_expansion_rate(self): t = self.elongation_age t_e = self.growth_duration # 1.5 * w_max / c_m t = clip(t, upper=t_e) #FIXME can we introduce new w_max here when w_max in t_e (growth duration) supposed to be potential length? w_max = self.potential_area # c_m from Eq. 9, r (= dw/dt / c_m) from Eq. 7 of Yin (2003) #HACK can be more simplified #c_m = 1.5 / t_e * w_max #r = 4 * t * (t_e - t) / t_e**2 t_m = t_e / 2 c_m = (2 * t_e - t_m) / (t_e * (t_e - t_m)) * (t_m / t_e)**(t_m / (t_e - t_m)) * w_max r = (t_e - t) / (t_e - t_m) * (t / t_m)**(t_m / (t_e - t_m)) #FIXME dt here is physiological time, whereas timestep multiplied in potential_area_increase is chronological time return c_m * r # dw/dt
def maximum_electron_transport_rate(self, T, T_dep, N_dep, Jm25=U(300, 'umol/m^2/s Electron'), Eaj=U(32800, 'J/mol'), Sj=U(702.6, 'J/mol/degK'), Hj=U(220000, 'J/mol')): R = U(8.314, 'J/K/mol') Tb = U(25, 'degC') Tk = T.to('degK') Tbk = Tb.to('degK') r = Jm25 * N_dep \ * T_dep(Eaj) \ * (1 + np.exp((Sj*Tbk - Hj) / (R*Tbk))) \ / (1 + np.exp((Sj*Tk - Hj) / (R*Tk))) return clip(r, lower=0)
def boundary_layer_conductance(self, lw='leaf.width', ww='leaf.weather.wind'): # maize is an amphistomatous species, assume 1:1 (adaxial:abaxial) ratio. #sr = 1.0 # switchgrass adaxial : abaxial (Awada 2002) # https://doi.org/10.4141/P01-031 sr = 1.28 ratio = (sr + 1)**2 / (sr**2 + 1) # characteristic dimension of a leaf, leaf width in m d = lw * 0.72 #return 1.42 # total BLC (both sides) for LI6400 leaf chamber gb = 1.4 * 0.147 * (clip(ww, lower=0.1) / d)**0.5 * ratio #gb = (1.4 * 1.1 * 6.62 * (wind / d)**0.5 * (P_air / (R * (273.15 + T_air)))) # this is an alternative form including a multiplier for conversion from mm s-1 to mol m-2 s-1 # 1.1 is the factor to convert from heat conductance to water vapor conductance, an avarage between still air and laminar flow (see Table 3.2, HG Jones 2014) # 6.62 is for laminar forced convection of air over flat plates on projected area basis # when all conversion is done for each surface it becomes close to 0.147 as given in Norman and Campbell # multiply by 1.4 for outdoor condition, Campbell and Norman (1998), p109, also see Jones 2014, pg 59 which suggest using 1.5 as this factor. # multiply by ratio to get the effective blc (per projected area basis), licor 6400 manual p 1-9 return gb
def senescence_duration(self): # end of growth period, time to maturity return clip(self.growth_duration - self.senescence_water_stress_duration, lower=0)
def stay_green_duration(self): # SK 8/20/10: as in Sinclair and Horie, 1989 Crop sciences, N availability index scaled between 0 and 1 based on #nitrogen_index = max(0, (2 / (1 + exp(-2.9 * (self.g_content - 0.25))) - 1)) return clip(self.stay_green * self.growth_duration - self.stay_green_water_stress_duration, lower=0)
def light(self): I2 = self.leaf.light return clip(I2, lower=0)
def gross_photosynthesis(self): return clip( self.A_net + self.Rd, lower=0 ) # gets negative when PFD = 0, Rd needs to be examined, 10/25/04, SK
def co2_mesophyll(self): Cm = self.leaf.co2_mesophyll return clip(Cm, lower=0)
def co2_mesophyll(self, A_net, w='weather', rvc='stomata.rvc'): P = w.P_air / U(100, 'kPa') Ca = w.CO2 * P # conversion to partial pressure Cm = Ca - A_net * rvc * P #print(f"+ Cm = {Cm}, Ca = {Ca}, A_net = {A_net}, gs = {self.stomata.gs}, gb = {self.stomata.gb}, rvc = {rvc}, P = {P}") return clip(Cm, 0, 2 * Ca)
def leaf_number_effect(self, potential_leaves): # Fig 4 of Birch et al. (1998) return clip(exp(-1.17 + 0.047 * potential_leaves), 0.5, 1.0)
def solar_radiation(self, elevation_angle, day, SC): t_s = clip(elevation_angle, lower=0, unit='rad') g = 2 * pi * (day - 10) / 365 return SC * sin(t_s) * (1 + 0.033 * cos(g))
def phase1_delay(self, rank): # not used in MAIZSIM because LTAR is used to initiate leaf growth. # Fournier's value : -5.16+1.94*rank;equa 11 Fournier and Andrieu(1998) YY, This is in plastochron unit return clip(-5.16 + 1.94 * rank, lower=0)