def A_trans(p, trans, Ci, Tleaf=None, inf_gb=False): """ Calculates the assimilation rate given the supply function, gc. 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], an array of values depending on the possible leaf water potentials (P) and the Weibull parameters b, c Ci: float intercellular CO2 concentration [Pa], corresponding to a leaf water potential (P) for which the transpiration cost is minimal and the C assimilation gain is maximal Tleaf: float leaf temperature [degC] inf_gb: bool if True, gb is prescrived and very large Returns: -------- Calculates the photosynthetic gain A [umol m-2 s-1] for a given Ci(P) and over an array of Gc(P) values. """ try: # is Tleaf one of the input fields? Tleaf = p.Tleaf except (IndexError, AttributeError, ValueError): # calc. Tleaf pass # get CO2 diffusive conduct. gc, __, __, __ = leaf_energy_balance(p, trans, Tleaf=Tleaf, inf_gb=inf_gb) A_P = conv.MILI * gc * (p.CO2 - Ci) / p.Patm try: A_P[np.isclose(np.squeeze(gc), cst.zero, rtol=cst.zero, atol=cst.zero)] = cst.zero except TypeError: pass return A_P
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 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 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 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