Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attempting to make PModelConst inherit CoreConst #201

Draft
wants to merge 1 commit into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ the Arrhenius equation ($h^{-1}$ in {cite}`mengoli:2022a`).
ha_vcmax25 = 65330
ha_jmax25 = 43900

tk_acclim = temp_acclim + pmodel_subdaily.env.core_const.k_CtoK
tk_acclim = temp_acclim + pmodel_subdaily.env.const.k_CtoK
vcmax25_acclim = pmodel_acclim.vcmax * (1 / calc_ftemp_arrh(tk_acclim, ha_vcmax25))
jmax25_acclim = pmodel_acclim.jmax * (1 / calc_ftemp_arrh(tk_acclim, ha_jmax25))
```
Expand Down Expand Up @@ -230,7 +230,7 @@ temperature at fast scales:
responses of $J_{max}$ and $V_{cmax}$.

```{code-cell} ipython3
tk_subdaily = subdaily_env.tc + pmodel_subdaily.env.core_const.k_CtoK
tk_subdaily = subdaily_env.tc + pmodel_subdaily.env.const.k_CtoK

# Fill the realised jmax and vcmax from subdaily to daily
vcmax25_subdaily = fsscaler.fill_daily_to_subdaily(vcmax25_real)
Expand Down Expand Up @@ -289,7 +289,7 @@ Aj_subdaily = (
)

# Calculate GPP and convert from micromols to micrograms
GPP_subdaily = np.minimum(Ac_subdaily, Aj_subdaily) * pmodel_subdaily.env.core_const.k_c_molmass
GPP_subdaily = np.minimum(Ac_subdaily, Aj_subdaily) * pmodel_subdaily.env.const.k_c_molmass

# Compare to the SubdailyPModel outputs
diff = GPP_subdaily - pmodel_subdaily.gpp
Expand Down
4 changes: 2 additions & 2 deletions pyrealm/constants/pmodel_const.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
import numpy as np
from numpy.typing import NDArray

from pyrealm.constants import ConstantsClass
from pyrealm.constants import CoreConst


@dataclass(frozen=True)
class PModelConst(ConstantsClass):
class PModelConst(CoreConst):
r"""Constants for the P Model.

This dataclass provides the following underlying constants used in calculating the
Expand Down
10 changes: 3 additions & 7 deletions pyrealm/pmodel/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,6 @@ def calc_gammastar(
tc: NDArray,
patm: NDArray,
pmodel_const: PModelConst = PModelConst(),
core_const: CoreConst = CoreConst(),
) -> NDArray:
r"""Calculate the photorespiratory CO2 compensation point.

Expand All @@ -278,7 +277,6 @@ def calc_gammastar(
tc: Temperature relevant for photosynthesis (:math:`T`, °C)
patm: Atmospheric pressure (:math:`p`, Pascals)
pmodel_const: Instance of :class:`~pyrealm.constants.pmodel_const.PModelConst`.
core_const: Instance of :class:`~pyrealm.constants.core_const.CoreConst`.

PModel Parameters:
To: the standard reference temperature (:math:`T_0`. ``k_To``)
Expand All @@ -303,8 +301,8 @@ def calc_gammastar(
return (
pmodel_const.bernacchi_gs25_0
* patm
/ core_const.k_Po
* calc_ftemp_arrh((tc + core_const.k_CtoK), ha=pmodel_const.bernacchi_dha)
/ pmodel_const.k_Po
* calc_ftemp_arrh((tc + pmodel_const.k_CtoK), ha=pmodel_const.bernacchi_dha)
)


Expand Down Expand Up @@ -432,7 +430,6 @@ def calc_kp_c4(
tc: NDArray,
patm: NDArray,
pmodel_const: PModelConst = PModelConst(),
core_const: CoreConst = CoreConst(),
) -> NDArray:
r"""Calculate the Michaelis Menten coefficient of PEPc.

Expand All @@ -444,7 +441,6 @@ def calc_kp_c4(
tc: Temperature, relevant for photosynthesis (:math:`T`, °C)
patm: Atmospheric pressure (:math:`p`, Pa)
pmodel_const: Instance of :class:`~pyrealm.constants.pmodel_const.PModelConst`.
core_const: Instance of :class:`~pyrealm.constants.core_const.CoreConst`.

PModel Parameters:
hac: activation energy for :math:`\ce{CO2}` (:math:`H_{kc}`,
Expand All @@ -467,7 +463,7 @@ def calc_kp_c4(
_ = check_input_shapes(tc, patm)

# conversion to Kelvin
tk = tc + core_const.k_CtoK
tk = tc + pmodel_const.k_CtoK
return pmodel_const.boyd_kp25_c4 * calc_ftemp_arrh(tk, ha=pmodel_const.boyd_dhac_c4)


Expand Down
14 changes: 7 additions & 7 deletions pyrealm/pmodel/jmax_limitation.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def __init__(
"""Details of the optimal chi calculation for the model"""
self.method: str = method
"""Records the method used to calculate Jmax limitation."""
self.pmodel_const: PModelConst = pmodel_const
self.const: PModelConst = pmodel_const
"""The PModelParams instance used for the calculation."""

# Attributes populated by alternative method - two should always be populated by
Expand Down Expand Up @@ -148,14 +148,14 @@ def wang17(self) -> None:

# Calculate √ {1 – (c*/m)^(2/3)} (see Eqn 2 of Wang et al 2017) and
# √ {(m/c*)^(2/3) - 1} safely, both are undefined where m <= c*.
vals_defined = np.greater(self.optchi.mj, self.pmodel_const.wang17_c)
vals_defined = np.greater(self.optchi.mj, self.const.wang17_c)

self.f_v = np.sqrt(
1 - (self.pmodel_const.wang17_c / self.optchi.mj) ** (2.0 / 3.0),
1 - (self.const.wang17_c / self.optchi.mj) ** (2.0 / 3.0),
where=vals_defined,
)
self.f_j = np.sqrt(
(self.optchi.mj / self.pmodel_const.wang17_c) ** (2.0 / 3.0) - 1,
(self.optchi.mj / self.const.wang17_c) ** (2.0 / 3.0) - 1,
where=vals_defined,
)

Expand Down Expand Up @@ -208,12 +208,12 @@ def smith19(self) -> None:

# Adopted from Nick Smith's code:
# Calculate omega, see Smith et al., 2019 Ecology Letters # Eq. S4
theta = self.pmodel_const.smith19_theta
c_cost = self.pmodel_const.smith19_c_cost
theta = self.const.smith19_theta
c_cost = self.const.smith19_c_cost

# simplification terms for omega calculation
cm = 4 * c_cost / self.optchi.mj
v = 1 / (cm * (1 - self.pmodel_const.smith19_theta * cm)) - 4 * theta
v = 1 / (cm * (1 - self.const.smith19_theta * cm)) - 4 * theta

# account for non-linearities at low m values. This code finds
# the roots of a quadratic function that is defined purely from
Expand Down
22 changes: 11 additions & 11 deletions pyrealm/pmodel/optimal_chi.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def __init__(
self.shape: tuple[int, ...] = env.shape
"""The shape of the input environment data."""

self.pmodel_const: PModelConst = pmodel_const
self.const: PModelConst = pmodel_const
"""The PModelParams used for optimal chi estimation"""

# Declare attributes populated by methods. These are typed but not assigned a
Expand Down Expand Up @@ -249,7 +249,7 @@ class OptimalChiPrentice14(
def set_beta(self) -> None:
"""Set ``beta`` to a constant C3 specific value."""
# leaf-internal-to-ambient CO2 partial pressure (ci/ca) ratio
self.beta = self.pmodel_const.beta_cost_ratio_prentice14
self.beta = self.const.beta_cost_ratio_prentice14

def estimate_chi(self, xi_values: Optional[NDArray] = None) -> None:
"""Estimate ``chi`` for C3 plants."""
Expand Down Expand Up @@ -318,7 +318,7 @@ def set_beta(self) -> None:
)

# leaf-internal-to-ambient CO2 partial pressure (ci/ca) ratio
self.beta = self.pmodel_const.beta_cost_ratio_prentice14
self.beta = self.const.beta_cost_ratio_prentice14

def estimate_chi(self, xi_values: Optional[NDArray] = None) -> None:
"""Estimate ``chi`` for C3 plants."""
Expand Down Expand Up @@ -381,7 +381,7 @@ class OptimalChiC4(
def set_beta(self) -> None:
"""Set ``beta`` to a constant C4 specific value."""
# leaf-internal-to-ambient CO2 partial pressure (ci/ca) ratio
self.beta = self.pmodel_const.beta_cost_ratio_c4
self.beta = self.const.beta_cost_ratio_c4

def estimate_chi(self, xi_values: Optional[NDArray] = None) -> None:
"""Estimate ``chi`` for C4 plants, setting ``mj`` and ``mc`` to 1."""
Expand Down Expand Up @@ -448,7 +448,7 @@ class OptimalChiC4RootzoneStress(
def set_beta(self) -> None:
"""Set ``beta`` to a constant C4 specific value."""
# leaf-internal-to-ambient CO2 partial pressure (ci/ca) ratio
self.beta = self.pmodel_const.beta_cost_ratio_c4
self.beta = self.const.beta_cost_ratio_c4

def estimate_chi(self, xi_values: Optional[NDArray] = None) -> None:
"""Estimate ``chi`` for C4 plants, setting ``mj`` and ``mc`` to 1."""
Expand Down Expand Up @@ -531,8 +531,8 @@ def set_beta(self) -> None:
# Calculate beta as a function of theta, which is guaranteed not to be None by
# _check_requires so supress mypy here
self.beta = np.exp(
self.pmodel_const.lavergne_2020_b_c3 * self.env.theta # type: ignore[operator] # noqa: E501
+ self.pmodel_const.lavergne_2020_a_c3
self.const.lavergne_2020_b_c3 * self.env.theta # type: ignore[operator] # noqa: E501
+ self.const.lavergne_2020_a_c3
)

def estimate_chi(self, xi_values: Optional[NDArray] = None) -> None:
Expand Down Expand Up @@ -623,8 +623,8 @@ def set_beta(self) -> None:
# Calculate beta as a function of theta, which is guaranteed not to be None by
# _check_requires so supress mypy here
self.beta = np.exp(
self.pmodel_const.lavergne_2020_b_c4 * self.env.theta # type: ignore[operator] # noqa: E501
+ self.pmodel_const.lavergne_2020_a_c4
self.const.lavergne_2020_b_c4 * self.env.theta # type: ignore[operator] # noqa: E501
+ self.const.lavergne_2020_a_c4
)

def estimate_chi(self, xi_values: Optional[NDArray] = None) -> None:
Expand Down Expand Up @@ -702,7 +702,7 @@ def set_beta(self) -> None:
"""Set constant ``beta`` for C4 plants."""

# Calculate chi and xi as in Prentice 14 but removing gamma terms.
self.beta = self.pmodel_const.beta_cost_ratio_c4
self.beta = self.const.beta_cost_ratio_c4

def estimate_chi(self, xi_values: Optional[NDArray] = None) -> None:
"""Estimate ``chi`` for C4 plants excluding photorespiration."""
Expand Down Expand Up @@ -771,7 +771,7 @@ def set_beta(self) -> None:
"""Set constant ``beta`` for C4 plants."""

# Calculate chi and xi as in Prentice 14 but removing gamma terms.
self.beta = self.pmodel_const.beta_cost_ratio_c4
self.beta = self.const.beta_cost_ratio_c4

def estimate_chi(self, xi_values: Optional[NDArray] = None) -> None:
"""Estimate ``chi`` for C4 plants excluding photorespiration."""
Expand Down
26 changes: 10 additions & 16 deletions pyrealm/pmodel/pmodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import numpy as np
from numpy.typing import NDArray

from pyrealm.constants import CoreConst, PModelConst
from pyrealm.constants import PModelConst
from pyrealm.core.utilities import check_input_shapes, summarize_attrs
from pyrealm.pmodel.functions import (
calc_ftemp_inst_rd,
Expand Down Expand Up @@ -200,10 +200,8 @@ def __init__(
self.env: PModelEnvironment = env
"""The PModelEnvironment used to fit the P Model."""

self.pmodel_const: PModelConst = env.pmodel_const
self.const: PModelConst = env.const
"""The PModelConst instance used to create the model environment."""
self.core_const: CoreConst = env.core_const
"""The CoreConst instance used to create the model environment."""

# kphio calculation:
self.init_kphio: float
Expand Down Expand Up @@ -236,7 +234,7 @@ def __init__(

self.optchi: OptimalChiABC = opt_chi_class(
env=env,
pmodel_const=self.pmodel_const,
pmodel_const=self.const,
)
"""An subclass OptimalChi, implementing the requested chi calculation method"""

Expand All @@ -247,9 +245,7 @@ def __init__(
# Temperature dependence of quantum yield efficiency
# -----------------------------------------------------------------------
if self.do_ftemp_kphio:
ftemp_kphio = calc_ftemp_kphio(
env.tc, self.c4, pmodel_const=self.pmodel_const
)
ftemp_kphio = calc_ftemp_kphio(env.tc, self.c4, pmodel_const=self.const)
self.kphio = self.init_kphio * ftemp_kphio
else:
self.kphio = np.array([self.init_kphio])
Expand All @@ -261,7 +257,7 @@ def __init__(
"""Records the method used to calculate Jmax limitation."""

self.jmaxlim: JmaxLimitation = JmaxLimitation(
self.optchi, method=self.method_jmaxlim, pmodel_const=self.pmodel_const
self.optchi, method=self.method_jmaxlim, pmodel_const=self.const
)
"""Details of the Jmax limitation calculation for the model"""
# -----------------------------------------------------------------------
Expand All @@ -280,7 +276,7 @@ def __init__(
# The basic calculation of LUE = phi0 * M_c * m with an added penalty term
# for jmax limitation
self.lue: NDArray = (
self.kphio * self.optchi.mj * self.jmaxlim.f_v * self.core_const.k_c_molmass
self.kphio * self.optchi.mj * self.jmaxlim.f_v * self.const.k_c_molmass
)
"""Light use efficiency (LUE, g C mol-1)"""

Expand Down Expand Up @@ -404,14 +400,14 @@ def estimate_productivity(

# V_cmax25 (vcmax normalized to const.k_To)
ftemp25_inst_vcmax = calc_ftemp_inst_vcmax(
self.env.tc, core_const=self.core_const, pmodel_const=self.pmodel_const
self.env.tc, core_const=self.const, pmodel_const=self.const
)
self._vcmax25 = self._vcmax / ftemp25_inst_vcmax

# Dark respiration at growth temperature
ftemp_inst_rd = calc_ftemp_inst_rd(self.env.tc, pmodel_const=self.pmodel_const)
ftemp_inst_rd = calc_ftemp_inst_rd(self.env.tc, pmodel_const=self.const)
self._rd = (
self.pmodel_const.atkin_rd_to_vcmax
self.const.atkin_rd_to_vcmax
* (ftemp_inst_rd / ftemp25_inst_vcmax)
* self._vcmax
)
Expand All @@ -425,9 +421,7 @@ def estimate_productivity(

assim = np.minimum(a_j, a_c)

if not np.allclose(
assim, self._gpp / self.core_const.k_c_molmass, equal_nan=True
):
if not np.allclose(assim, self._gpp / self.const.k_c_molmass, equal_nan=True):
warn("Assimilation and GPP are not identical")

# Stomatal conductance - do not estimate when VPD = 0 or when floating point
Expand Down
16 changes: 6 additions & 10 deletions pyrealm/pmodel/pmodel_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import numpy as np
from numpy.typing import NDArray

from pyrealm.constants import CoreConst, PModelConst
from pyrealm.constants import PModelConst
from pyrealm.core.utilities import bounds_checker, check_input_shapes, summarize_attrs
from pyrealm.pmodel.functions import (
calc_co2_to_ca,
Expand Down Expand Up @@ -84,7 +84,7 @@ def __init__(
theta: Optional[NDArray] = None,
rootzonestress: Optional[NDArray] = None,
pmodel_const: PModelConst = PModelConst(),
core_const: CoreConst = CoreConst(),
# core_const: CoreConst = CoreConst(),
):
self.shape: tuple = check_input_shapes(tc, vpd, co2, patm)

Expand Down Expand Up @@ -115,12 +115,10 @@ def __init__(
self.ca: NDArray = calc_co2_to_ca(self.co2, self.patm)
"""Ambient CO2 partial pressure, Pa"""

self.gammastar = calc_gammastar(
tc, patm, pmodel_const=pmodel_const, core_const=core_const
)
self.gammastar = calc_gammastar(tc, patm, pmodel_const=pmodel_const)
r"""Photorespiratory compensation point (:math:`\Gamma^\ast`, Pa)"""

self.kmm = calc_kmm(tc, patm, pmodel_const=pmodel_const, core_const=core_const)
self.kmm = calc_kmm(tc, patm, pmodel_const=pmodel_const)
"""Michaelis Menten coefficient, Pa"""

# # Michaelis-Menten coef. C4 plants (Pa) NOT CHECKED. Need to think
Expand All @@ -129,7 +127,7 @@ def __init__(
# # has not yet been implemented.
# self.kp_c4 = calc_kp_c4(tc, patm, const=const)

self.ns_star = calc_ns_star(tc, patm, core_const=core_const)
self.ns_star = calc_ns_star(tc, patm, core_const=pmodel_const)
"""Viscosity correction factor realtive to standard
temperature and pressure, unitless"""

Expand All @@ -152,10 +150,8 @@ def __init__(
)

# Store constant settings
self.pmodel_const = pmodel_const
self.const = pmodel_const
"""PModel constants used to calculate environment"""
self.core_const = core_const
"""Core constants used to calculate environment"""

def __repr__(self) -> str:
"""Generates a string representation of PModelEnvironment instance."""
Expand Down
Loading
Loading