Pleaf_pd = p.Ps_pd - p.height * cst.rho * cst.g0 * conv.MEGA P, E = hydraulics(p, res=res, kmax=p.kmaxS1) # iter on the solution until it is stable enough iter = 0 while True: if case == 1: # assuming colimitation Cicol = calc_colim_Ci(p, Cs, Tleaf, photo) dCi = Cs - Cicol # Pa # calculate dA, μmol m-2 s-1 As, __, __ = calc_photosynthesis(p, 0., Cs, photo, Tleaf=Tleaf, gsc=0.) Acol, __, __ = calc_photosynthesis(p, 0., Cicol, photo, Tleaf=Tleaf, gsc=0.) dA = As - Acol # ambient - colimitation # dAdCi (in mol H2O) is needed to calculate gs, mmol m-2 s-1 dAdCi = dA * conv.GwvGc * p.Patm / dCi # kcost, unitless cost_pd = kcost(p, Pleaf_pd, Pleaf_pd)
def profit_psi(p, photo='Farquhar', res='low', inf_gb=False, deriv=False): """ Finds the instateneous profit maximization, following the optmization criterion for which, at each instant in time, the stomata regulate canopy gas exchange and pressure to achieve the maximum profit, which is the maximum difference between the normalized photosynthetic gain (gain) and the hydraulic cost function (cost). That is when d(gain)/dP = d(cost)/dP. Arguments: ---------- p: recarray object or pandas series or class containing the data time step's met data & params photo: string either the Farquhar model for photosynthesis, or the Collatz model res: string either 'low' (default), 'med', or 'high' to run the optimising solver onopt: boolean if True, the optimisation is performed. If Fall, fall back on previously performed optimisation for the value of the maximum profit. inf_gb: bool if True, gb is prescrived and very large Returns: -------- E_can: float transpiration [mmol m-2 s-1] at maximum profit across leaves gs_can: float stomatal conductance [mol m-2 s-1] at maximum profit across leaves An_can: float net photosynthetic assimilation rate [umol m-2 s-1] at maximum profit across leaves Ci_can: float intercellular CO2 concentration [Pa] at maximum profit across leaves rublim_can: string 'True' if the C assimilation is rubisco limited, 'False' otherwise """ # hydraulics P, trans = hydraulics(p, res=res) cost, __ = hydraulic_cost(p, P) # look for the most net profit gain, Ci, mask = photo_gain(p, trans, photo, res, inf_gb=inf_gb) expr = gain - cost[mask] if deriv: expr = np.abs(np.gradient(expr, P[mask])) # deal with edge cases by rebounding the solution gc, gs, gb, __ = leaf_energy_balance(p, trans[mask], inf_gb=inf_gb) try: if inf_gb: # check on valid range check = expr[gc > cst.zero] else: # further constrain the realm of possible gs check = expr[np.logical_and(gc > cst.zero, gs < 1.5 * gb)] idx = np.isclose(expr, max(check)) if deriv: idx = np.isclose(expr, min(check)) idx = [list(idx).index(e) for e in idx if e] if inf_gb: # check for algo. "overshooting" due to inf. gb while Ci[idx[0]] < 2. * p.gamstar25: idx[0] -= 1 if idx[0] < 3: break # optimized where Ci for both photo models are close Ci = Ci[idx[0]] trans = trans[mask][idx[0]] # mol.m-2.s-1 gs = gs[idx[0]] Pleaf = P[mask][idx[0]] # rubisco- or electron transport-limitation? An, Aj, Ac = calc_photosynthesis(p, trans, Ci, photo, inf_gb=inf_gb) rublim = rubisco_limit(Aj, Ac) # leaf temperature? Tleaf, __ = leaf_temperature(p, trans, inf_gb=inf_gb) if (np.isclose(trans, cst.zero, rtol=cst.zero, atol=cst.zero) and (An > 0.)) or (idx[0] == len(P) - 1) or any( np.isnan([An, Ci, trans, gs, Tleaf, Pleaf])): An, Ci, trans, gs, gb, Tleaf, Pleaf = (9999., ) * 7 elif not np.isclose(trans, cst.zero, rtol=cst.zero, atol=cst.zero): trans *= conv.MILI # mmol.m-2.s-1 return An, Ci, rublim, trans, gs, gb, Tleaf, Pleaf except ValueError: # no opt return (9999., ) * 8
def gas_exchange(p, fw, photo='Farquhar', res='low', dynamic=True, inf_gb=False, iter_max=40, threshold_conv=0.1): # initial state Cs = p.CO2 # Pa Tleaf = p.Tair # deg C # hydraulics P, E = hydraulics(p, res=res, kmax=p.kmaxT) # initialise gs over A g0 = 1.e-9 # g0 ~ 0, removing it entirely introduces errors Cs_umol_mol = Cs * conv.MILI / p.Patm # umol mol-1 gsoA = g0 + p.g1T * fw / Cs_umol_mol # iter on the solution until it is stable enough iter = 0 while True: An, Aj, Ac, Ci = calc_photosynthesis(p, 0., Cs, photo, Tleaf=Tleaf, gs_over_A=gsoA) # stomatal conductance, with fwsoil effect gs = np.maximum(cst.zero, conv.GwvGc * gsoA * An) # calculate new trans, gw, gb, etc. trans, real_zero, gw, gb, __ = calc_trans(p, Tleaf, gs, inf_gb=inf_gb) new_Tleaf, __ = leaf_temperature(p, trans, Tleaf=Tleaf, inf_gb=inf_gb) Pleaf = P[bn.nanargmin(np.abs(E - trans))] # update Cs (Pa) boundary_CO2 = p.Patm * conv.FROM_MILI * An / (gb * conv.GbcvGb) Cs = np.maximum(cst.zero, np.minimum(p.CO2, p.CO2 - boundary_CO2)) Cs_umol_mol = Cs * conv.MILI / p.Patm # update gs over A gsoA = g0 + p.g1T * fw / Cs_umol_mol # force stop when atm. conditions yield E < 0. (non-physical) if (iter < 1) and (not real_zero): real_zero = None # check for convergence if ((real_zero is None) or (iter >= iter_max) or ((iter >= 2) and real_zero and (abs(Tleaf - new_Tleaf) <= threshold_conv) and not np.isclose(gs, cst.zero, rtol=cst.zero, atol=cst.zero))): break # no convergence, iterate on leaf temperature Tleaf = new_Tleaf iter += 1 if ((np.isclose(trans, cst.zero, rtol=cst.zero, atol=cst.zero) and (An > 0.)) or np.isclose(Ci, 0., rtol=cst.zero, atol=cst.zero) or (Ci < 0.) or np.isclose(Ci, p.CO2, rtol=cst.zero, atol=cst.zero) or (Ci > p.CO2) or (real_zero is None) or (not real_zero) or any(np.isnan([An, Ci, trans, gs, Tleaf, Pleaf]))): An, Ci, trans, gs, gb, Tleaf, Pleaf = (9999.,) * 7 return An, Aj, Ac, Ci, trans, gs, gb, new_Tleaf, Pleaf
def Cmax_gs(p, photo='Farquhar', res='low', inf_gb=False): """ Finds the instantaneous optimal C gain for a given C cost. First, the C gain equation is derived for gs, beta, Ci unknown. Then, the derived form of the equation is solved for Ci over a range of possible betas, gs, all of which are directly or indirectly leaf water potential P dependent. A check (check_solve) is performed to verify that the optimization satisfies the zero equality criteria and, finally, results are bound via a range of physically possible Ci values. N.B.: there can be several possible optimizations Arguments: ---------- p: recarray object or pandas series or class containing the data time step's met data & params photo: string either the Farquhar model for photosynthesis, or the Collatz model inf_gb: bool if True, gb is prescrived and very large Returns: -------- gsOPT: float stomatal conductance [mol.m-2.s-1] for which the A(gs) is maximized AnOPT: float maximum C assimilation rate [μmol.m-2.s-1] given by the diffusive supply of CO2 transOPT: float transpiration rate [mmol.m-2.s-1] for which the A(gs) is maximized CiOPT: float intercellular CO2 concentration [Pa] for which the A(gs) is maximized """ # energy balance P, trans = hydraulics(p, res=res, kmax=p.kmaxCM) # expression of optimization Ci, mask = Ci_sup_dem(p, trans, photo=photo, res=res, inf_gb=inf_gb) gc, gs, gb, __ = leaf_energy_balance(p, trans[mask], inf_gb=inf_gb) expr = np.abs( np.gradient(A_trans(p, trans[mask], Ci, inf_gb=inf_gb), P[mask]) - dcost_dpsi(p, P[mask], gs)) try: if inf_gb: # check on valid range check = expr[gc > cst.zero] else: # further constrain the realm of possible gs check = expr[np.logical_and(gc > cst.zero, gs < 1.5 * gb)] idx = np.isclose(expr, min(check)) idx = [list(idx).index(e) for e in idx if e] if inf_gb: # check for algo. "overshooting" due to inf. gb while Ci[idx[0]] < 2. * p.gamstar25: idx[0] -= 1 if idx[0] < 3: break # optimized where Ci for both photo models are close Ci = Ci[idx[0]] trans = trans[mask][idx[0]] # mol.m-2.s-1 gs = gs[idx[0]] Pleaf = P[mask][idx[0]] # rubisco limitation or electron transport-limitation? An, Aj, Ac = calc_photosynthesis(p, trans, Ci, photo=photo, inf_gb=inf_gb) rublim = rubisco_limit(Aj, Ac) # leaf temperature? Tleaf, __ = leaf_temperature(p, trans, inf_gb=inf_gb) if (np.isclose(trans, cst.zero, rtol=cst.zero, atol=cst.zero) and (An > 0.)) or (idx[0] == len(P) - 1) or any( np.isnan([An, Ci, trans, gs, Tleaf, Pleaf])): An, Ci, trans, gs, gb, Tleaf, Pleaf = (9999., ) * 7 elif not np.isclose(trans, cst.zero, rtol=cst.zero, atol=cst.zero): trans *= conv.MILI # mmol.m-2.s-1 return An, Ci, rublim, trans, gs, gb, Tleaf, Pleaf except ValueError: # no opt return (9999., ) * 8
def floop(p, model, photo='Farquhar', inf_gb=True): # initialize the system Dleaf = p.VPD # kPa Cs = p.CO2 # Pa if model == 'Tuzet': iter_min = 2 # needed to update the fw with the LWP else: iter_min = 1 # hydraulics P, trans = hydraulics(p, kmax=p.kmaxT) try: # is Tleaf one of the input fields? Tleaf = p.Tleaf iter_max = 0 if model == 'Tuzet': iter_max = 2 # needed to update the fw with the LWP except (IndexError, AttributeError, ValueError): # calc. Tleaf Tleaf = p.Tair # deg C iter_max = 40 if model != 'Tuzet': # energy balance requirements Pleaf_pd = p.Ps_pd - p.height * cst.rho * cst.g0 * conv.MEGA if model == 'Medlyn': Dleaf = np.maximum(0.05, Dleaf) # gs model not valid < 0.05 if p.height > 0 and sw >= p.fc: fw = 1. # no moisture stress else: fw = fwWP(p, p.Ps) # moisture stress function else: # Tuzet model fw = fLWP(p, p.LWP_ini) # stress factor if (model == 'Medlyn') or (model == 'Tuzet'): # init. gs over A g0 = 1.e-9 # g0 ~ 0, removing it entirely introduces errors Cs_umol_mol = Cs * conv.MILI / p.Patm # umol mol-1 if model == 'Medlyn': gsoA = g0 + (1. + p.g1 * fw / (Dleaf**0.5)) / Cs_umol_mol else: # Tuzet gsoA = g0 + p.g1T * fw * Cs_umol_mol # iter on the solution until it is stable enough iter = 0 while True: if model == 'Eller': Cicol = calc_colim_Ci(p, Cs, Tleaf, photo) dCi = Cs - Cicol # Pa # calculate dA, μmol m-2 s-1 As, __, __ = calc_photosynthesis(p, 0., Cs, photo, Tleaf=Tleaf, gsc=0.) Acol, __, __ = calc_photosynthesis(p, 0., Cicol, photo, Tleaf=Tleaf, gsc=0.) dA = As - Acol # ambient - colimitation # dAdCi (in mol H2O) is needed to calculate gs, mmol m-2 s-1 dAdCi = dA * conv.GwvGc * p.Patm / dCi # kcost, unitless cost_pd = kcost(p, Pleaf_pd, Pleaf_pd) cost_mid = kcost(p, -p.P50, Pleaf_pd) dkcost = cost_pd - cost_mid # dP is needed to calculate gs dP = 0.5 * (Pleaf_pd + p.P50) # MPa, /!\ sign of P50 # xi, the loss of xylem cost of stomatal opening, mmol m-2 s-1 dq = Dleaf / p.Patm # mol mol-1, equivalent to D / Patm Xi = 2. * p.kmaxS1 * (cost_pd**2.) * dP / (dq * dkcost) # calculate gs at the co-limitation point, mmol m-2 s-1 gscol = Acol * conv.GwvGc * p.Patm / dCi # calculate gs, mol m-2 s-1 if dAdCi <= 0.: # cp from SOX code, ??? it should never happen! gs = gscol * conv.FROM_MILI else: gs = 0.5 * dAdCi * conv.FROM_MILI * (( (1. + 4. * Xi / dAdCi)**0.5) - 1.) elif model == 'SOX-OPT': # retrieve all potential Ci values Cis = Ci_stream(p, Cs, Tleaf, 'low') # rate of photosynthesis, μmol m-2 s-1 A, __, __ = calc_photosynthesis(p, 0., Cis, photo, Tleaf=Tleaf) # gb? __, gb = leaf_temperature(p, 0., Tleaf=Tleaf, inf_gb=inf_gb) if inf_gb or (iter < 1): # gas-exchange trans, mmol m-2 s-1 E = A * conv.GwvGc * Dleaf / (p.CO2 - Cis) else: E = (A * (gb * conv.GwvGc + gs * conv.GbvGbc) / (gs + gb) * Dleaf / (p.CO2 - Cis)) # cost, Pleaf mask = np.logical_and(Pleaf_pd - E / p.ksc_prev <= Pleaf_pd, Pleaf_pd - E / p.ksc_prev >= P[-1]) P = (Pleaf_pd - E / p.ksc_prev)[mask] cost = kcost(p, P, Pleaf_pd) try: # optimal point iopt = np.argmax(cost * A[mask]) Ci = Cis[mask][iopt] except Exception: return 9999. * 1000., p.ksc_prev # get net rate of photosynthesis at optimum, μmol m-2 s-1 An, __, __ = calc_photosynthesis(p, 0., Ci, photo, Tleaf=Tleaf) # get associated gc and gs gc = p.Patm * conv.FROM_MILI * An / (p.CO2 - Ci) if inf_gb: gs = gc * conv.GwvGc else: gs = np.maximum(cst.zero, gc * gb * conv.GwvGc / (gb - conv.GbvGbc * gc)) else: An, __, __, __ = calc_photosynthesis(p, 0., Cs, photo, Tleaf=Tleaf, gs_over_A=gsoA) gs = np.maximum(cst.zero, conv.GwvGc * gsoA * An) # calculate new trans, gw, gb, Tleaf E, real_zero, gw, gb, Dleaf = calc_trans(p, Tleaf, gs, inf_gb=inf_gb) new_Tleaf, __ = leaf_temperature(p, E, Tleaf=Tleaf, inf_gb=inf_gb) if model == 'Eller': # calculate An An, __, __ = calc_photosynthesis(p, 0., Cs, photo, Tleaf=Tleaf, gsc=conv.U * conv.GcvGw * gs) # update Cs (Pa) boundary_CO2 = p.Patm * conv.FROM_MILI * An / (gb * conv.GbcvGb) Cs = np.maximum(cst.zero, np.minimum(p.CO2, p.CO2 - boundary_CO2)) Cs_umol_mol = Cs * conv.MILI / p.Patm if model == 'Tuzet': # update Pleaf and fw Pleaf = P[np.nanargmin(np.abs(trans - E))] # Tuzet model if np.abs(fw - fLWP(p, Pleaf)) < 0.5: # is fw stable? fw = fLWP(p, Pleaf) # update # update gsoA gsoA = g0 + p.g1T * fw / Cs_umol_mol else: # update the leaf-to-air VPD if (np.isclose(E, cst.zero, rtol=cst.zero, atol=cst.zero) or np.isclose(gw, cst.zero, rtol=cst.zero, atol=cst.zero) or np.isclose(gs, cst.zero, rtol=cst.zero, atol=cst.zero)): Dleaf = p.VPD # kPa if model == 'Medlyn': Dleaf = np.maximum(0.05, Dleaf) # gs model not valid < 0.05 # update gs over A gsoA = g0 + (1. + p.g1 * fw / (Dleaf**0.5)) / Cs_umol_mol # force stop when atm. conditions yield E < 0. (non-physical) if (iter < 1) and (not real_zero): real_zero = None # check for convergence if ((real_zero is None) or (iter >= iter_max) or ((iter > iter_min) and real_zero and (abs(Tleaf - new_Tleaf) <= 0.1) and not np.isclose(gs, cst.zero, rtol=cst.zero, atol=cst.zero))): break # no convergence, iterate Tleaf = new_Tleaf iter += 1 if iter_max < 5: # no "real" iteration if Tleaf is prescribed Cs = p.CO2 Tleaf = p.Tleaf if model == 'Tuzet': return gs * 1000., Pleaf elif model == 'SOX-OPT': return gs * 1000., p.kmaxS2 * cost[iopt] else: return gs * 1000. # mmol/m2/s
def fmtx(p, model, photo='Farquhar', inf_gb=True): # hydraulics if (model == 'CAP') or (model == 'MES'): if model == 'CAP': P, trans = hydraulics(p, kmax=p.krlC, Pcrit=p.PcritC) else: P, trans = hydraulics(p, kmax=p.krlM, Pcrit=p.PcritM) elif model == 'LeastCost': P, trans = hydraulics(p, kmax=p.kmaxLC) else: P, trans = hydraulics(p) # expressions of optimisation if model == 'ProfitMax': # look for the most net profit cost, __ = hydraulic_cost(p, P) gain, Ci, mask = photo_gain(p, trans, photo, 'low', inf_gb=inf_gb) expr = gain - cost[mask] elif (model != 'CAP') and (model != 'MES'): Ci, mask = Ci_sup_dem(p, trans, photo=photo, inf_gb=inf_gb) if model == 'ProfitMax2': expr = A_trans(p, trans[mask], Ci, inf_gb=inf_gb) * (1. - trans[mask] / trans[-1]) if model == 'CGain': expr = (A_trans(p, trans[mask], Ci, inf_gb=inf_gb) - p.Kappa * fPLC(p, P[mask])) if model == 'LeastCost': expr = ((p.Eta * conv.MILI * trans[mask] + dVmaxoAdXi(p, trans[mask], Ci, inf_gb=inf_gb)) / A_trans(p, trans[mask], Ci, inf_gb=inf_gb)) # leaf energy balance gc, gs, gb, ww = leaf_energy_balance(p, trans, inf_gb=inf_gb) if model == 'CMax': try: expr = np.abs( np.gradient(A_trans(p, trans[mask], Ci, inf_gb=inf_gb), P[mask]) - dcost_dpsi(p, P[mask], gs)) except Exception: return 9999. * 1000. # returning an actual NaN causes trouble if model == 'WUE-LWP': expr = (A_trans(p, trans[mask], Ci, inf_gb=inf_gb) - p.Lambda * conv.MILI * trans[mask]) if model == 'CAP': cost = phiLWP(P, p.PcritC) Ci, mask = Ci_sup_dem(p, trans, photo=photo, Vmax25=p.Vmax25 * cost, inf_gb=inf_gb) An, __, __ = calc_photosynthesis(p, trans[mask], Ci, photo, Vmax25=p.Vmax25 * cost[mask], inf_gb=inf_gb) try: expr = An except Exception: return 9999. * 1000. # returning an actual NaN causes trouble if model == 'MES': # Ci is Cc cost = phiLWP(P, p.PcritM) Cc, mask = Ci_sup_dem(p, trans, photo=photo, phi=cost, inf_gb=inf_gb) An, __, __ = calc_photosynthesis(p, trans[mask], Cc, photo, inf_gb=inf_gb) try: expr = An except Exception: return 9999. * 1000. # returning an actual NaN causes trouble if inf_gb: # deal with edge cases by rebounding the solution check = expr[gc[mask] > cst.zero] else: # accounting for gb check = expr[np.logical_and(gc[mask] > cst.zero, gs[mask] < 1.5 * gb)] try: if (model == 'LeastCost') or (model == 'CMax'): idx = np.isclose(expr, min(check)) else: idx = np.isclose(expr, max(check)) idx = [list(idx).index(e) for e in idx if e] if len(idx) >= 1: # opt values return gs[mask][idx[0]] * 1000. # mmol/m2/s else: return 9999. * 1000. # returning an actual NaN causes trouble except ValueError: # expr function is empty return 9999. * 1000.
def solve_std(p, sw, photo='Farquhar', res='low', iter_max=40, threshold_conv=0.1, inf_gb=False): """ Checks the energy balance by looking for convergence of the new leaf temperature with the leaf temperature predicted by the previous iteration. Then returns the corresponding An, E, Ci, etc. Arguments: ---------- p: recarray object or pandas series or class containing the data time step's met data & params sw: float mean volumetric soil moisture content [m3 m-3] photo: string either the Farquhar model for photosynthesis, or the Collatz model threshold_conv: float convergence threshold for the new leaf temperature to be in energy balance iter_max: int maximum number of iterations allowed on the leaf temperature before reaching the conclusion that the system is not energy balanced inf_gb: bool if True, gb is prescrived and very large Returns: -------- trans_can: float transpiration rate of canopy [mmol m-2 s-1] across leaves gs_can: float stomatal conductance of canopy [mol m-2 s-1] across leaves An_can: float C assimilation rate of canopy [umol m-2 s-1] across leaves Ci_can: float average intercellular CO2 concentration of canopy [Pa] across leaves rublim_can: string 'True' if the C assimilation is rubisco limited, 'False' otherwise. """ # initial state Cs = p.CO2 # Pa Tleaf = p.Tair # deg C Dleaf = np.maximum(0.05, p.VPD) # gs model not valid < 0.05 # hydraulics P, E = hydraulics(p, res=res) if sw >= p.fc: g1 = p.g1 else: g1 = p.g1 * fwWP(p, p.Ps) # initialise gs over A g0 = 1.e-9 # g0 ~ 0, removing it entirely introduces errors Cs_umol_mol = Cs * conv.MILI / p.Patm # umol mol-1 gsoA = g0 + (1. + g1 / (Dleaf ** 0.5)) / Cs_umol_mol # iter on the solution until it is stable enough iter = 0 while True: An, Aj, Ac, Ci = calc_photosynthesis(p, 0., Cs, photo, Tleaf=Tleaf, gs_over_A=gsoA) # stomatal conductance, with moisture stress effect gs = np.maximum(cst.zero, conv.GwvGc * gsoA * An) # calculate new trans, gw, gb, mol.m-2.s-1 trans, real_zero, gw, gb, Dleaf = calc_trans(p, Tleaf, gs, inf_gb=inf_gb) new_Tleaf, __ = leaf_temperature(p, trans, Tleaf=Tleaf, inf_gb=inf_gb) # update Cs (Pa) boundary_CO2 = p.Patm * conv.FROM_MILI * An / (gb * conv.GbcvGb) Cs = np.maximum(cst.zero, np.minimum(p.CO2, p.CO2 - boundary_CO2)) Cs_umol_mol = Cs * conv.MILI / p.Patm # new leaf-air vpd, kPa if (np.isclose(trans, cst.zero, rtol=cst.zero, atol=cst.zero) or np.isclose(gw, cst.zero, rtol=cst.zero, atol=cst.zero) or np.isclose(gs, cst.zero, rtol=cst.zero, atol=cst.zero)): Dleaf = np.maximum(0.05, p.VPD) # kPa # update gs over A gsoA = g0 + (1. + g1 / (Dleaf ** 0.5)) / Cs_umol_mol # force stop when atm. conditions yield E < 0. (non-physical) if (iter < 1) and (not real_zero): real_zero = None # check for convergence if ((real_zero is None) or (iter >= iter_max) or ((iter >= 1) and real_zero and (abs(Tleaf - new_Tleaf) <= threshold_conv) and not np.isclose(gs, cst.zero, rtol=cst.zero, atol=cst.zero))): break # no convergence, iterate on leaf temperature Tleaf = new_Tleaf iter += 1 Pleaf = P[bn.nanargmin(np.abs(trans - E))] rublim = rubisco_limit(Aj, Ac) # lim? if ((np.isclose(trans, cst.zero, rtol=cst.zero, atol=cst.zero) and (An > 0.)) or np.isclose(Ci, 0., rtol=cst.zero, atol=cst.zero) or (Ci < 0.) or np.isclose(Ci, p.CO2, rtol=cst.zero, atol=cst.zero) or (Ci > p.CO2) or (real_zero is None) or (not real_zero) or any(np.isnan([An, Ci, trans, gs, new_Tleaf, Pleaf]))): An, Ci, trans, gs, gb, new_Tleaf, Pleaf = (9999.,) * 7 elif not np.isclose(trans, cst.zero, rtol=cst.zero, atol=cst.zero): trans *= conv.MILI # mmol.m-2.s-1 return An, Ci, rublim, trans, gs, gb, new_Tleaf, Pleaf
def Ci_sup_dem(p, trans, photo='Farquhar', res='low', Vmax25=None, phi=None, inf_gb=False): # ref. photosynthesis A_ref, __, __ = calc_photosynthesis(p, trans, p.CO2, photo, Vmax25=Vmax25, inf_gb=inf_gb) # Cs < Ca, used to ensure physical solutions __, __, gb, __ = leaf_energy_balance(p, trans, inf_gb=inf_gb) boundary_CO2 = p.Patm * conv.FROM_MILI * A_ref / (gb * conv.GbcvGb) Cs = np.maximum(cst.zero, np.minimum(p.CO2, p.CO2 - boundary_CO2)) # Pa # potential Ci values over the full range of transpirations if res == 'low': NCis = 500 if res == 'med': NCis = 2000 if res == 'high': NCis = 8000 # retrieve the appropriate Cis from the supply-demand Cis = np.asarray( [np.linspace(0.1, Cs[e], NCis) for e in range(len(trans))]) if (Vmax25 is None) and (phi is not None): # account for gm Tref = p.Tref + conv.C_2_K # degk, Tref set to 25 degC try: # is Tleaf one of the input fields? Tleaf = p.Tleaf except (IndexError, AttributeError, ValueError): # calc. Tleaf Tleaf, __ = leaf_temperature(p, trans, inf_gb=inf_gb) # CO2 compensation point gamstar = arrhen(p.gamstar25, p.Egamstar, Tref, Tleaf) try: # now getting the Cc Ccs = np.asarray([ phi[e] * (Cis[e] - gamstar[e]) + gamstar[e] for e in range(len(trans)) ]) except IndexError: # only one Tleaf Ccs = np.asarray([ phi[e] * (Cis[e] - gamstar) + gamstar for e in range(len(trans)) ]) if phi is None: Ccs = None Ci = mtx_minimise(p, trans, Cis, photo, Vmax25=Vmax25, all_Ccs=Ccs, inf_gb=inf_gb) mask = ~Ci.mask try: if len(mask) > 0: pass except TypeError: mask = ([False] + [ True, ] * len(Ci))[:-1] return Ci[mask], mask
def mtx_minimise(p, trans, all_Cis, photo, Vmax25=None, all_Ccs=None, inf_gb=False): """ Uses matrices to find each value of Ci for which An(supply) ~ An(demand) on the transpiration stream. Arguments: ---------- p: recarray object or pandas series or class containing the data time step's met data & params trans: array transpiration [mol m-2 s-1], values depending on the possible leaf water potentials (P) and the Weibull parameters b, c all_Cis: array all potential Ci values over the transpiration stream (for each water potential, Ci values can be anywhere between a lower bound and Cs) photo: string either the Farquhar model for photosynthesis, or the Collatz model inf_gb: bool if True, gb is prescrived and very large Returns: -------- The value of Ci for which An(supply) is the closest to An(demand) (e.g. An(supply) - An(demand) closest to zero). """ if Vmax25 is not None: demand, __, __ = calc_photosynthesis(p, np.expand_dims(trans, axis=1), all_Cis, photo, Vmax25=np.expand_dims(Vmax25, axis=1), inf_gb=inf_gb) elif all_Ccs is not None: demand, __, __ = calc_photosynthesis(p, np.expand_dims(trans, axis=1), all_Ccs, photo, inf_gb=inf_gb) else: demand, __, __ = calc_photosynthesis(p, np.expand_dims(trans, axis=1), all_Cis, photo, inf_gb=inf_gb) supply = A_trans(p, np.expand_dims(trans, axis=1), all_Cis, inf_gb=inf_gb) # find the meeting point between demand and supply idx = bn.nanargmin(np.abs(supply - demand), axis=1) # closest ~0 if all_Ccs is not None: all_Cis = all_Ccs # each Ci on the transpiration stream Ci = np.asarray([all_Cis[e, idx[e]] for e in range(len(trans))]) Ci = np.ma.masked_where(idx == 0, Ci) Ci = np.ma.masked_where(idx == all_Cis.shape[1] - 1, Ci) return Ci