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
Esempio n. 3
0
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