# -*- coding: latin-1 -*-
"""
hydraulics.model
~~~~~~~~~~~~~~~~~~
The module :mod:`hydraulics.model` defines the equations of water flow, turgor pressure and growth.
"""
from __future__ import division # use "//" to do integer division
from math import exp
from openalea.cnwgrass.hydraulics import parameters
[docs]
class Population:
"""
The class :class:`Population`.
A :class:`population <Population>` must have at least one :class:`plant <Plant>`.
"""
PARAMETERS = parameters.POPULATION_PARAMETERS #: the internal parameters of the population
def __init__(self, plants=None):
"""
:param list [Plant] plants: the list of Plant objects
"""
if plants is None:
plants = []
self.plants = plants
[docs]
def calculate_aggregated_variables(self):
"""Calculate the integrative variables of the population recursively.
"""
for plant in self.plants:
plant.calculate_aggregated_variables()
[docs]
class Plant:
"""
The class :class:`Plant` defines the water flow at plant scale.
A :class:`plant <Plant>` must have at least one :class:`axis <Axis>`.
"""
PARAMETERS = parameters.PLANT_PARAMETERS #: the internal parameters of the plants
def __init__(self, index=None, axes=None):
"""
:param int index: plant index
:param list [Axis] axes: the list of Axis objects
"""
self.index = index
if axes is None:
axes = []
self.axes = axes #: the list of axes
self.cohorts = [] #: list of cohort values - Hack to treat tillering cases : TEMPORARY
[docs]
def calculate_aggregated_variables(self):
"""Calculate the integrative variables of the plant recursively.
"""
for axis in self.axes:
axis.calculate_aggregated_variables()
[docs]
class Axis:
"""
The class :class:`Axis`.
An :class:`axis <Axis>` must have:
* one :class:`root compartment <Roots>`,
* one :class:`xylem <Xylem>`,
* at least one :class:`phytomer<Phytomer>`.
"""
PARAMETERS = parameters.AXIS_PARAMETERS #: the internal parameters of the axes
INIT_COMPARTMENTS = parameters.AXIS_INIT_COMPARTMENTS #: the initial values of compartments and state parameters
def __init__(self, label=None, roots=None, xylem=None, phytomers=None, SAM_temperature=INIT_COMPARTMENTS.SAM_temperature):
"""
:param str label: the label of the axis
:param Root roots: Root object
:param Xylem xylem: Xylem object
:param list [Phytomer] phytomers: list of Phytomer objects
"""
self.label = label
self.roots = roots
self.xylem = xylem
if phytomers is None:
phytomers = []
self.phytomers = phytomers #: the list of phytomers
# state parameters
self.SAM_temperature = SAM_temperature
# integrative variables
self.water_influx = None #: water influx in non growing organs (g H2O)
self.Growth = None #: water influx in the hiddenzones related to growth (g H2O)
self.Total_Transpiration_turgor = None #: the total transpiration (mmol s-1)
self.plant_water_content = None #: plant water content (g H2O)
[docs]
def calculate_aggregated_variables(self):
"""Calculate the integrative variables of the axis recursively.
"""
self.water_influx = 0
self.Total_Transpiration_turgor = 0
self.Growth = 0
self.plant_water_content = 0
for phytomer in self.phytomers:
phytomer.calculate_aggregated_variables()
self.Total_Transpiration_turgor += phytomer.Total_Transpiration_turgor * phytomer.nb_replications
self.plant_water_content += phytomer.water_content * phytomer.nb_replications
if phytomer != phytomer.hiddenzone:
self.water_influx += phytomer.water_influx
if phytomer.hiddenzone is not None:
self.Growth += phytomer.hiddenzone.water_influx * phytomer.hiddenzone.nb_replications
[docs]
@staticmethod
def calculate_ratio_WC_mstruct(plant_water_content, mstruct):
"""
Ratio between water content and structural mass of the axis
:param float plant_water_content: g
:param float mstruct: g
:return: Water content : Structural mass ratio (%)
:rtype: float
"""
plant_WC_DM = plant_water_content / mstruct * 100
return plant_WC_DM
[docs]
class Phytomer:
"""
The class :class:`Phytomer`.
A :class:`phytomer <Phytomer>` must have at least:
* 1 photosynthetic organ: :class:`lamina <Lamina>`, :class:`internode <Internode>`,
or :class:`sheath <Sheath>`.
* or 1 :class:`hiddenzone <HiddenZone>`.
"""
PARAMETERS = parameters.PHYTOMER_PARAMETERS #: the internal parameters of the phytomers
INIT_COMPARTMENTS = parameters.PHYTOMER_INIT_COMPARTMENTS #: the initial values of compartments and state parameters
def __init__(self, index=None, lamina=None, internode=None, sheath=None, hiddenzone=None, cohorts=None, cohorts_replications=None):
"""
:param int index: index of the phytomer
:param Lamina lamina: Lamina object
:param Internode internode: Internode object
:param Sheath sheath: Sheath object
:param HiddenZone hiddenzone: HiddenZone object
"""
self.index = index #: the index of the phytomer
self.lamina = lamina #: the lamina
self.internode = internode #: the internode
self.sheath = sheath #: the sheath
self.hiddenzone = hiddenzone #: the hidden zone
if cohorts is None:
cohorts = []
self.cohorts = cohorts #: list of cohort values - Hack to treat tillering cases : TEMPORARY. Devrait être porté à l'échelle de la plante uniquement mais je ne vois pas comment faire mieux
self.cohorts_replications = cohorts_replications #: dictionary of number of replications per cohort rank
# integrative variables
# self.green_area = None #: m2
self.Total_Transpiration_turgor = None #: g H20
self.water_influx = None #: g H20
self.Growth = None #: g H20
self.total_water_influx = None #: g H20
self.water_content = None #: g H2O
[docs]
def calculate_aggregated_variables(self):
"""Calculate the integrative variables of the phytomer recursively.
"""
self.Total_Transpiration_turgor = 0
self.water_influx = 0
# self.green_area = 0
self.water_content = 0
for organ_ in (self.lamina, self.internode, self.sheath, self.hiddenzone):
if organ_ is not None:
organ_.calculate_aggregated_variables()
# self.green_area += organ_.green_area
self.water_content += organ_.water_content
self.Total_Transpiration_turgor += organ_.Total_Transpiration_turgor
self.water_influx += organ_.water_influx
@property
def nb_replications(self):
return sum(int(v <= self.index) * self.cohorts_replications.get(v, 0) for v in self.cohorts) + 1
[docs]
class Organ:
"""
The class :class:`Organ`.
:class:`Organ` is the base class of all organs. DO NOT INSTANTIATE IT.
"""
PARAMETERS = parameters.ORGAN_PARAMETERS #: the internal parameters of the organ
def __init__(self, label):
"""
:param str label: the label of the organ
"""
self.label = label
[docs]
def initialize(self):
"""Initialize the derived attributes of the organ.
"""
pass
[docs]
def calculate_aggregated_variables(self):
"""Calculate the integrative variables of the organ recursively.
"""
pass
[docs]
class Roots(Organ):
"""
The class :class:`Roots`.
"""
PARAMETERS = parameters.ROOTS_PARAMETERS #: the internal parameters of the roots
INIT_COMPARTMENTS = parameters.ROOTS_INIT_COMPARTMENTS #: the initial values of compartments and state parameters
def __init__(self, label='roots'):
"""
:param str label: root label
"""
super(Roots, self).__init__(label)
[docs]
class Xylem(Organ):
"""
The class :class:`Xylem` defines the water exchanges in a xylem.
"""
PARAMETERS = parameters.XYLEM_PARAMETERS #: the internal parameters of the xylem
INIT_COMPARTMENTS = parameters.XYLEM_INIT_COMPARTMENTS #: the initial values of compartments and state parameters
def __init__(self, label='xylem', water_potential=INIT_COMPARTMENTS.water_potential):
super(Xylem, self).__init__(label)
# state parameters
self.water_potential = water_potential #: MPa
# integrative variables
self.delta_t = 3600 #: the delta t of the simulation (in seconds)
#: Model equations for water flux
[docs]
@staticmethod
def calculate_xylem_water_potential(soil_water_potential, total_water_influx, Growth, delta_t):
"""Total water potential of the xylem
:param float soil_water_potential: MPa
:param float total_water_influx: g H2O
:param float Growth: g H2O
:param float delta_t: time step of the simulation (s)
:return: Total water potential (MPa)
:rtype: float
"""
#: Axial resistance between soil and xylem is a fixed parameter : R_soil
water_potential = soil_water_potential - ((Growth + total_water_influx) * Xylem.PARAMETERS.R_soil * delta_t)
return water_potential
[docs]
class HiddenZone(Organ):
"""
The class :class:`HiddenZone`.
"""
PARAMETERS = parameters.HIDDEN_ZONE_PARAMETERS #: the internal parameters of the hidden zone
INIT_COMPARTMENTS = parameters.HIDDEN_ZONE_INIT_COMPARTMENTS #: the initial values of compartments and state parameters
def __init__(self, label='hiddenzone', fructan=INIT_COMPARTMENTS.fructan, leaf_enclosed_mstruct=INIT_COMPARTMENTS.leaf_enclosed_mstruct, leaf_pseudo_age=INIT_COMPARTMENTS.leaf_pseudo_age, hiddenzone_age=INIT_COMPARTMENTS.hiddenzone_age, amino_acids=INIT_COMPARTMENTS.amino_acids, proteins=INIT_COMPARTMENTS.proteins, sucrose=INIT_COMPARTMENTS.sucrose,
length_hz_En=INIT_COMPARTMENTS.length_hz_En, lamina_Lmax=INIT_COMPARTMENTS.lamina_Lmax,
mstruct=INIT_COMPARTMENTS.mstruct, osmotic_water_potential=INIT_COMPARTMENTS.osmotic_water_potential,
water_potential=INIT_COMPARTMENTS.water_potential, leaf_pseudostem_length=INIT_COMPARTMENTS.leaf_pseudostem_length,
leaf_L=INIT_COMPARTMENTS.leaf_L, thickness=INIT_COMPARTMENTS.thickness, width=INIT_COMPARTMENTS.width,
turgor_water_potential=INIT_COMPARTMENTS.turgor_water_potential, water_content=INIT_COMPARTMENTS.water_content,
water_influx=INIT_COMPARTMENTS.water_influx, water_outflow=INIT_COMPARTMENTS.water_outflow, cohorts=None, cohorts_replications=None, leaf_Wmax = INIT_COMPARTMENTS.leaf_Wmax,
leaf_is_growing=INIT_COMPARTMENTS.leaf_is_growing, index=None):
super(HiddenZone, self).__init__(label)
if cohorts is None:
cohorts = []
self.cohorts = cohorts #: list of cohort values - Hack to treat tillering cases : TEMPORARY. Devrait être porté à l'échelle de la plante uniquement mais je ne vois pas comment faire mieux
self.cohorts_replications = cohorts_replications #: dictionary of number of replications per cohort rank
self.index = index
self.label = label
# state parameters
self.fructan = fructan #: :math:`:math:`\\mu mol C``
self.amino_acids = amino_acids #: :math:`:math:`\\mu mol N``
self.proteins = proteins #: :math:`:math:`\\mu mol N``
self.sucrose = sucrose #: :math:`:math:`\\mu mol C``
self.leaf_pseudo_age = leaf_pseudo_age #: °Cd
self.leaf_L = leaf_L #: m
self.leaf_is_growing = leaf_is_growing #: -
self.mstruct = mstruct #: g
self.leaf_enclosed_mstruct = leaf_enclosed_mstruct #: g
self.hiddenzone_age = hiddenzone_age #: °Cd
self.length = min(leaf_L, leaf_pseudostem_length) #: m
self.leaf_pseudostem_length = leaf_pseudostem_length #: m
self.lamina_Lmax = lamina_Lmax #: m
self.width = width #: m
self.thickness = thickness #: m
self.length_hz_En = length_hz_En #: m
self.water_content = water_content #: g H2O
# fluxes from xylem
self.water_influx = water_influx #: current flow of water from xylem to hiddenzone integrated over delta t (g H2O)
self.water_outflow = water_outflow #: current flow of water from hiddenzone to emerged lamina if any integrated over delta t (g H2O)
# other fluxes
self.initial_volume = None #: m3
self.osmotic_water_potential = osmotic_water_potential #: MPa
self.water_potential = water_potential #: MPa
self.resistance = None #: resistance of water flux between two organs (MPa s g-1)
self.extensibility = None #: MPa-1
self.turgor_water_potential = turgor_water_potential #: MPa
self.leaf_Lmax = None #: m
self.leaf_Wmax = leaf_Wmax #: m
# Integrated fluxes
self.Total_Transpiration_turgor = 0
self.Growth = 0
@property
def nb_replications(self):
return sum(int(v <= self.index) * self.cohorts_replications.get(v, 0) for v in self.cohorts) + 1
[docs]
def calculate_aggregated_variables(self):
"""
:return:
"""
self.Growth = self.calculate_delta_water_content(self.water_influx, self.water_outflow)
#: Model equations for water flux
[docs]
@staticmethod
def calculate_initial_volume(mstruct):
""" Hidden zone initial volume calculated from mstruct. This calculation is only performed at t = previous leaf emergence
:param float mstruct: (g)
:return: volume (m3), water content (g)
:rtype: (float, float)
"""
dry_mass = mstruct / HiddenZone.PARAMETERS.RATIO_MSTRUCT_DM #: total dry mass (g)
volume = dry_mass * HiddenZone.PARAMETERS.SLOPE_MASS_VOLUME + HiddenZone.PARAMETERS.OFFSET_MASS_VOLUME #: m3
water_content = volume * parameters.RHO_WATER #: g
return volume, water_content
[docs]
@staticmethod
def calculate_volume(water_content):
""" Hidden zone volume, assumed to be proportional to water content.
:param float water_content: g H2O
:return: volume (m3)
:rtype: float
"""
return water_content / parameters.RHO_WATER
[docs]
@staticmethod
def calculate_osmotic_water_potential(fructan, sucrose, amino_acids, volume, temperature):
""" Osmotic water potential of the organ calculated according to metabolites
:param float fructan: µmol C under the form of fructan
:param float sucrose: µmol C under the form of sucrose
:param float amino_acids: µmol N under the form of amino acids
:param float volume: (g H2O)
:param float temperature: hidden zone temperature, approximated by SAM temperature (°C)
:return: Osmotic water potential (MPa)
:rtype: float
"""
temperature_K = temperature + parameters.CELSIUS_2_KELVIN
#: Concentration of solutes
sucrose = (sucrose * 1E-6) / parameters.NB_C_SUCROSE
amino_acids = (amino_acids * 1E-6) / parameters.AMINO_ACIDS_N_RATIO
fructan = (fructan * 1E-6) / parameters.NB_C_SUCROSE
conc_solutes = (fructan + sucrose + amino_acids) / (volume * parameters.VSTORAGE)
#: Effective concentration of solutes
conc_solutes_eff = HiddenZone.PARAMETERS.Sa / (HiddenZone.PARAMETERS.Sb + exp(HiddenZone.PARAMETERS.Sc *
conc_solutes / HiddenZone.PARAMETERS.Sd))
osmotic_water_potential = - parameters.R * temperature_K * conc_solutes_eff / parameters.RHO_WATER
return osmotic_water_potential
[docs]
@staticmethod
def calculate_water_potential(turgor_water_potential, osmotic_water_potential):
""" Total water potential of the organ
:param float turgor_water_potential: MPa
:param float osmotic_water_potential: MPa
:return: Total water potential (MPa)
:rtype: float
"""
return turgor_water_potential + osmotic_water_potential
[docs]
@staticmethod
def calculate_hiddenzone_length(leaf_L, leaf_pseudostem_length):
""" Length of the hidden zone
:param float leaf_L: Total leaf length (m)
:param float leaf_pseudostem_length: Length of the pseudostem (m)
:return: Length of the hidden zone (m)
:rtype: float
"""
return min(leaf_L, leaf_pseudostem_length)
[docs]
@staticmethod
def calculate_resistance(hiddenzone_dimensions):
""" Resistance of water flow between the hiddenzone and the xylem.
Relations were set proportional to the length and inversely proportional to the area of organ's cross-section.
From Coussement et al. (2018)
:param dict hiddenzone_dimensions: dict of hidden zone dimensions at time t. Keys = ['length', 'thickness', 'width'] (m)
:return: resistance (MPa s g-1)
:rtype: float
"""
resistance = 0.5 * Xylem.PARAMETERS.R_xylem_hz * (hiddenzone_dimensions['length'] / (hiddenzone_dimensions['width'] * hiddenzone_dimensions['thickness']))
return resistance
[docs]
@staticmethod
def calculate_water_flux(organ_water_potential, xylem_water_potential, resistance, delta_t):
""" Water flow into the organ according to water potential gradient with the xylem.
:param float organ_water_potential: water potential of the current organ (MPa)
:param float xylem_water_potential: water potential of the xylem (MPa)
:param float resistance: transport resistance between organ and xylem (MPa s g-1)
:param float delta_t: time step of the simulation (s)
:return: Water influx into the current organ integrated over delta_t (g)
:rtype: float
"""
water_influx = ((xylem_water_potential - organ_water_potential) / resistance) * delta_t
return water_influx
[docs]
@staticmethod
def calculate_delta_water_content(water_influx, water_outflow):
""" delta of water flow for the hidden zone.
:param float water_influx: Water influx integrated over delta_t (g H2O)
:param float water_outflow: Water loss through the emerged lamina or sheath if any (g H2O)
:return: Delta of water flow into the organ (g)
:rtype: float
"""
return water_influx - water_outflow
[docs]
@staticmethod
def calculate_time_equivalent_Tref(temperature_hz, time):
""" Return the time equivalent to a reference temperature i.e. temperature-compensated time (Parent, 2010).
:param float temperature_hz: hiddenzone temperature, approximated by SAM temperature (degree Celsius)
:param float time: time duration (s)
:return: temperature-compensated time (s)
:rtype: float
"""
def modified_Arrhenius_equation(temperature):
""" Return value of equation from Johnson and Lewin (1946) for temperature. The equation is modified to return zero below zero degree.
:param float temperature: temperature (degree Celsius)
:return: Return value of Eyring equation from Johnson and Lewin (1946) for temperature. The equation is modified to return zero below zero degree.
:rtype: float
"""
def Arrhenius_equation(T):
return T * exp(-HiddenZone.PARAMETERS.Temp_Ea_R / T) / (
1 + exp(HiddenZone.PARAMETERS.Temp_DS_R - HiddenZone.PARAMETERS.Temp_DH_R / T))
temperature_K = temperature + 273.15
if temperature < 0:
res = 0
elif temperature < HiddenZone.PARAMETERS.Temp_Ttransition:
res = temperature * Arrhenius_equation(
HiddenZone.PARAMETERS.Temp_Ttransition + 273.15) / HiddenZone.PARAMETERS.Temp_Ttransition
else:
res = Arrhenius_equation(temperature_K)
return res
return time * modified_Arrhenius_equation(temperature_hz) / modified_Arrhenius_equation(HiddenZone.PARAMETERS.Temp_Tref)
[docs]
@staticmethod
def calculate_extensibility_temperature(age, delta_teq, delta_t):
""" Hidden zone extensibility in each dimension in relation to non-reversible dimensional changes.
From Coussement et al. (2018)
With temperature effect on leaf_pseudo_age and on maximum extensibility.
:param float age: hidden zone age (°Cd)
:param float delta_teq: temperature-compensated time (s)
:param float delta_t: time step of the simulation (s)
:return: Extensibility z, y and x (MPa-1): {'z': float, 'y': float, 'x': float}
:rtype: dict
"""
phi = {}
for phi_init_dimensions, phi_init_value in HiddenZone.PARAMETERS.phi_initial.items():
if age <= HiddenZone.PARAMETERS.tend:
beta_function_norm = (1 - (1 + (HiddenZone.PARAMETERS.tend - age) / (HiddenZone.PARAMETERS.tend - HiddenZone.PARAMETERS.tmax))
* ((age - HiddenZone.PARAMETERS.tbase) / (HiddenZone.PARAMETERS.tend - HiddenZone.PARAMETERS.tbase)) **
((HiddenZone.PARAMETERS.tend - HiddenZone.PARAMETERS.tbase) / (HiddenZone.PARAMETERS.tend - HiddenZone.PARAMETERS.tmax)))
else:
beta_function_norm = 0
phi[phi_init_dimensions] = phi_init_value * beta_function_norm * delta_t * (delta_teq / delta_t)
return phi
[docs]
@staticmethod
def calculate_organ_volume(hiddenzone_dimensions):
""" HiddenZone volume, assumed to be equal to a box dimensions.
:param dict hiddenzone_dimensions: dict of hidden zone dimensions at time t. Keys = ['length', 'thickness', 'width'] (m)
:return: volume (m3)
:rtype: float
"""
return hiddenzone_dimensions['length'] * hiddenzone_dimensions['width'] * hiddenzone_dimensions['thickness']
[docs]
@staticmethod
def calculate_delta_turgor_water_potential(phi, turgor_water_potential, organ_volume, delta_water_content):
""" Delta of turgor water potential of hidden zone.
:param dict [str, float] phi: float phi: dict of cell wall extensibility (MPa). Keys = ['x', 'y', 'z]
:param float turgor_water_potential: MPa
:param float organ_volume: m3
:param float delta_water_content: delta water content integrated over delta t (g)
:return: Delta of turgor water potential (MPa)
:rtype: float
"""
epsilon_x, epsilon_y, epsilon_z = HiddenZone.PARAMETERS.epsilon['x'], HiddenZone.PARAMETERS.epsilon['y'], HiddenZone.PARAMETERS.epsilon['z']
elastic_component = (epsilon_x * epsilon_y * epsilon_z) / (epsilon_z * epsilon_x + epsilon_z * epsilon_y + epsilon_x * epsilon_y) #: Elastic reversible growth (MPa)
plastic_component = (phi['x'] + phi['y'] + phi['z']) #: Plastic irreversible growth
delta_turgor_water_potential = ((1 / (
parameters.RHO_WATER * organ_volume * parameters.VSTORAGE)) * delta_water_content - plastic_component * (max(turgor_water_potential, HiddenZone.PARAMETERS.GAMMA) - HiddenZone.PARAMETERS.GAMMA)) * elastic_component #: (MPa)
return delta_turgor_water_potential
[docs]
@staticmethod
def calculate_delta_organ_dimensions_plastic(turgor_water_potential, phi, organ_dimensions):
""" Irreversible delta of organ dimensions according to turgor water potential, dimensions and plasticity.
Hidden zone geometry is supposed to be a rectangular prism.
:param float turgor_water_potential: MPa
:param dict phi: dict of cell wall extensibility (MPa). Keys = ['x', 'y', 'z]
:param dict organ_dimensions: dict of organ dimensions at time t. Keys = [length', 'width', 'thickness'] (m)
:return: Delta of organ specific-dimensions (m). Keys = ['Leaf_L'', 'width', 'thickness']
:rtype: dict
"""
delta_organ_dimensions_plastic = {}
epsilon_dict = HiddenZone.PARAMETERS.epsilon
mapping_dimensions = {'x': 'width', 'y': 'thickness', 'z': 'length'}
for epsilon_dimension, epsilon_value in epsilon_dict.items():
delta_organ_dimensions_plastic[mapping_dimensions[epsilon_dimension]] = (phi[epsilon_dimension] * (max(turgor_water_potential, HiddenZone.PARAMETERS.GAMMA) - HiddenZone.PARAMETERS.GAMMA)) *\
organ_dimensions[mapping_dimensions[epsilon_dimension]]
return delta_organ_dimensions_plastic
[docs]
@staticmethod
def calculate_delta_organ_dimensions_elastic(delta_turgor_water_potential, organ_dimensions):
""" Reversible delta of organ dimensions according to turgor water potential, dimensions and extensibility.
Hidden zone geometry is supposed to be a rectangular prism.
:param float delta_turgor_water_potential: delta of turgor water potential integrated over delta t (MPa)
:param dict organ_dimensions: dict of organ dimensions at time t. Keys = [length', 'width', 'thickness'] (m)
:return: Delta of organ specific-dimensions (m). Keys = ['leaf_L'', 'width', 'thickness']
:rtype: dict
"""
delta_organ_dimensions_elastic = {}
epsilon_dict = HiddenZone.PARAMETERS.epsilon
mapping_dimensions = {'x': 'width', 'y': 'thickness', 'z': 'length'}
for epsilon_dimension, epsilon_value in epsilon_dict.items():
delta_organ_dimensions_elastic[mapping_dimensions[epsilon_dimension]] = ((1 / epsilon_value) * delta_turgor_water_potential) *\
organ_dimensions[mapping_dimensions[epsilon_dimension]]
return delta_organ_dimensions_elastic
[docs]
class PhotosyntheticOrgan(Organ):
"""
The class :class:`PhotosyntheticOrgan` defines the water flow in a photosynthetic organ.
A :class:`photosynthetic organ <PhotosyntheticOrgan>` must have at least 1
:class:`photosynthetic organ element <PhotosyntheticOrganElement>`:
:class:`lamina element <LaminaElement>`, :class:`internode element <InternodeElement>`, or :class:`sheath element <SheathElement>`.
:class:`PhotosyntheticOrgan` is the base class of all photosynthetic organs. DO NOT INSTANTIATE IT.
"""
def __init__(self, label, exposed_element, enclosed_element):
"""
:param str label: Photosynthetic organ label
:param LaminaElement or InternodeElement or SheathElement exposed_element: the exposed element
:param LaminaElement or InternodeElement or SheathElement enclosed_element: the enclosed element
"""
super(PhotosyntheticOrgan, self).__init__(label)
self.exposed_element = exposed_element
self.enclosed_element = enclosed_element
self.green_area = None #: m2
self.water_content = None #: g H2O
self.Total_Transpiration_turgor = None #: g H2O
self.water_influx = None #: g H2O
[docs]
def calculate_aggregated_variables(self):
self.Total_Transpiration_turgor = 0
self.water_influx = 0
self.green_area = 0
self.water_content = 0
for element in (self.exposed_element, self.enclosed_element):
if element is not None:
element.calculate_aggregated_variables()
self.Total_Transpiration_turgor += element.Total_Transpiration_turgor
self.water_influx += element.water_influx
self.green_area += element.green_area
self.water_content += element.water_content
[docs]
class Lamina(PhotosyntheticOrgan):
"""
The class :class:`Lamina`.
"""
def __init__(self, label='lamina', exposed_element=None, enclosed_element=None):
"""
:param str label: lamina label
:param LaminaElement exposed_element: the exposed lamina object
:param LaminaElement enclosed_element: the enclosed lamina object
"""
super(Lamina, self).__init__(label, exposed_element, enclosed_element)
[docs]
class Internode(PhotosyntheticOrgan):
"""
The class :class:`Internode`.
"""
def __init__(self, label=None, exposed_element=None, enclosed_element=None):
"""
:param str label: Internode label
:param InternodeElement exposed_element: the exposed internode object
:param InternodeElement enclosed_element: the enclosed internode object
"""
super(Internode, self).__init__(label, exposed_element, enclosed_element)
[docs]
class Sheath(PhotosyntheticOrgan):
"""
The class :class:`Sheath`.
"""
def __init__(self, label=None, exposed_element=None, enclosed_element=None):
"""
:param str label: Sheath label
:param SheathElement exposed_element: the exposed sheath object
:param SheathElement enclosed_element: the enclosed sheath object
"""
super(Sheath, self).__init__(label, exposed_element, enclosed_element)
[docs]
class PhotosyntheticOrganElement:
"""
The class :class:`PhotosyntheticOrganElement` defines the water flow in a photosynthetic organ element.
An element must belong to an organ of the same type (e.g. a class:`LaminaElement` must belong to a class:`Lamina`).
A :class:`photosynthetic organ element <PhotosyntheticOrganElement>` must have at least 1
:class:`lamina element<LaminaElement>`,
:class:`internode element <InternodeElement>`,
or :class:`sheath element <SheathElement>`.
:class:`PhotosyntheticOrganElement` is the base class of all photosynthetic organ elements. DO NOT INSTANTIATE IT.
"""
PARAMETERS = parameters.PHOTOSYNTHETIC_ORGAN_ELEMENT_PARAMETERS #: the internal parameters of the photosynthetic organs elements
INIT_COMPARTMENTS = parameters.PHOTOSYNTHETIC_ORGAN_ELEMENT_INIT_COMPARTMENTS #: the initial values of compartments and state parameters
def __init__(self, label=None, is_growing=INIT_COMPARTMENTS.is_growing, temperature=INIT_COMPARTMENTS.temperature, age=INIT_COMPARTMENTS.age, green_area=INIT_COMPARTMENTS.green_area, mstruct=INIT_COMPARTMENTS.mstruct, Ts=INIT_COMPARTMENTS.Ts,
Tr=INIT_COMPARTMENTS.Tr, sucrose=INIT_COMPARTMENTS.sucrose, amino_acids=INIT_COMPARTMENTS.amino_acids, proteins=INIT_COMPARTMENTS.proteins, fructan=INIT_COMPARTMENTS.fructan,
osmotic_water_potential=INIT_COMPARTMENTS.osmotic_water_potential, water_potential=INIT_COMPARTMENTS.water_potential,
turgor_water_potential=INIT_COMPARTMENTS.turgor_water_potential, water_influx=INIT_COMPARTMENTS.water_influx, Wmax=INIT_COMPARTMENTS.Wmax,
length=INIT_COMPARTMENTS.length, thickness=INIT_COMPARTMENTS.thickness, width=INIT_COMPARTMENTS.width, water_content=INIT_COMPARTMENTS.water_content, cohorts=None, cohorts_replications=None, index=None):
self.label = label #: the label of the element
self.index = index
if cohorts is None: #: list of cohort values - Hack to treat tillering cases : TEMPORARY. Devrait être porté à l'échelle de la plante uniquement mais je ne vois pas comment faire mieux
cohorts = []
self.cohorts = cohorts #: list of cohort values - Hack to treat tillering cases : TEMPORARY. Devrait être porté à l'échelle de la plante uniquement mais je ne vois pas comment faire mieux
self.cohorts_replications = cohorts_replications #: dictionary of number of replications per cohort rank
# state parameters
self.is_growing = is_growing #: -
self.age = age #: °Cd
self.Wmax = Wmax #: m
self.amino_acids = amino_acids #: :math:`:math:`\\mu mol N``
self.green_area = green_area #: m2
self.mstruct = mstruct #: g
self.proteins = proteins #: :math:`:math:`\\mu mol N``
self.sucrose = sucrose #: :math:`:math:`\\mu mol C``
self.fructan = fructan #: :math:`:math:`\\mu mol C``
self.Ts = Ts #: °C
self.temperature = temperature #: °C
self.Tr = Tr #: mmol H20 m-2 s-1
self.thickness = thickness #: m
self.width = width #: m
# intermediate variables
self.turgor_water_potential = turgor_water_potential #: MPa
self.osmotic_water_potential = osmotic_water_potential #: MPa
self.water_potential = water_potential #: MPa
self.length = length #: m
# state variables
self.water_content = water_content #: g H2O
# fluxes to xylem
self.water_influx = water_influx #: current flow of water from xylem to organ integrated over delta t (g H2O)
# other fluxes
self.resistance = None #: resistance of water flux between two organs (MPa s g-1)
# Integrated variables
self.delta_t = 3600 #: the delta t of the simulation (in seconds)
self.Total_Transpiration_turgor = 0 #: (g H2O)
@property
def nb_replications(self):
return sum(int(v <= self.index) * self.cohorts_replications.get(v, 0) for v in self.cohorts) + 1
[docs]
def calculate_aggregated_variables(self):
"""Calculate the integrative variables of the element.
"""
self.Total_Transpiration_turgor = self.calculate_Total_Transpiration(self.Tr, self.green_area, self.delta_t)
# VARIABLES
[docs]
@staticmethod
def calculate_organ_volume(organ_dimensions):
""" Photosynthetic element volume, assumed to be equal to a box dimensions.
:param dict organ_dimensions: dict of organ dimensions at time t. Keys = ['length', 'thickness', 'width'] (m)
:return: volume (m3)
:rtype: float
"""
organ_volume = organ_dimensions['length'] * organ_dimensions['width'] * organ_dimensions['thickness']
return organ_volume
[docs]
@staticmethod
def calculate_resistance(organ_dimensions):
"""
Resistance of water flow between the lamina and xylem
Relations were set proportional to the length and inversely proportional to the area of organ's cross section.
:param dict organ_dimensions: dict of organ dimensions at time t. Keys = ['length', 'thickness', 'width'] (m)
:return: resistance (MPa s g-1)
:rtype: float
"""
#: Coussement et al. (2018)
resistance = 0.5 * Xylem.PARAMETERS.R_xylem_organ * organ_dimensions['length'] / (organ_dimensions['width'] * organ_dimensions['thickness'])
return resistance
[docs]
@staticmethod
def calculate_total_water_influx(water_influx):
"""
Water influx from xylem to organ
:param float water_influx: Water influx (g H2O)
:return: Total water influx (g H2O)
"""
total_water_influx = water_influx
return total_water_influx
[docs]
@staticmethod
def calculate_Total_Transpiration(Tr, green_area, delta_t):
"""Total organ transpiration
:param float Tr: Transpiration rate (mmol H2O m-2 s-1)
:param float green_area: Green area (m2)
:param float delta_t: time step of the simulation (s)
:return: Total transpiration (g H2O)
:rtype: float
"""
conversion_ratio = parameters.WATER_MOLAR_MASS * 1E-03 * delta_t # gH2O
return Tr * green_area * conversion_ratio
# FLUXES
#: Water flow equations common to all photosynthetic organ elements
[docs]
@staticmethod
def calculate_volume(water_content):
""" Photosynthetic element volume, assumed to be proportional to water content.
:param float water_content: (g H2O)
:return: volume (m3)
:rtype: float
"""
return water_content / parameters.RHO_WATER
[docs]
@staticmethod
def calculate_osmotic_water_potential(sucrose, amino_acids, volume, temperature, fructan):
""" Osmotic water potential of the hiddenzone calculated according to metabolites
:param float sucrose: µmol C under the form of sucrose
:param float amino_acids: µmol N under the form of amino acids
:param float volume: (m3)
:param float temperature: air temperature (°C)
:param float fructan: µmol C under the form of fructan
:return: Osmotic water potential (MPa)
:rtype: float
"""
temperature_K = temperature + parameters.CELSIUS_2_KELVIN
#: Concentration of solutes
sucrose = (sucrose * 1E-6) / parameters.NB_C_SUCROSE
amino_acids = (amino_acids * 1E-6) / parameters.AMINO_ACIDS_N_RATIO
fructan = (fructan * 1E-6) / parameters.NB_C_SUCROSE
conc_solutes = (fructan + sucrose + amino_acids) / (volume * parameters.VSTORAGE)
#: Effective concentration of solutes
conc_solutes_eff = PhotosyntheticOrganElement.PARAMETERS.Sa / (PhotosyntheticOrganElement.PARAMETERS.Sb + exp(PhotosyntheticOrganElement.PARAMETERS.Sc *
conc_solutes / PhotosyntheticOrganElement.PARAMETERS.Sd))
osmotic_water_potential = - parameters.R * temperature_K * conc_solutes_eff / parameters.RHO_WATER
return osmotic_water_potential
[docs]
@staticmethod
def calculate_water_potential(turgor_water_potential, osmotic_water_potential):
""" Total water potential of the organ
:param float turgor_water_potential: MPa
:param float osmotic_water_potential: MPa
:return: Total water potential (MPa)
:rtype: float
"""
return turgor_water_potential + osmotic_water_potential
[docs]
@staticmethod
def calculate_water_flux(water_potential, xylem_water_potential, resistance, delta_t):
""" Water flow into the organ according to water potential gradient with the xylem.
:param float water_potential: water potential of the current organ (MPa)
:param float xylem_water_potential: water potential of the xylem (MPa)
:param float resistance: transport resistance between organ and xylem (MPa s g-1)
:param float delta_t: time step of the simulation (s)
:return: Water influx into the current organ integrated over delta_t (g H2O)
:rtype: float
"""
return ((xylem_water_potential - water_potential) / resistance) * delta_t
[docs]
@staticmethod
def calculate_delta_water_content(water_influx, Total_Transpiration_turgor,):
""" Delta of water flow for the lamina.
:param float water_influx: Water influx from xylem integrated over delta_t (g)
:param float Total_Transpiration_turgor: Element transpiration (g H2O)
:return: Delta of water flow into the organ (g H2O)
:rtype: float
"""
return water_influx - Total_Transpiration_turgor
[docs]
@staticmethod
def calculate_delta_turgor_water_potential(volume, delta_water_content):
""" Delta of turgor water potential according to organ volume and elasticity.
Extensibility (phi) is supposed to be 0 as this tissue is mature (growth completed).
:param float volume: organ volume at time t (m3).
:param float delta_water_content: delta water content integrated over delta t (g)
:return: Delta of turgor water potential (MPa)
:rtype: float
"""
epsilon_z, epsilon_x, epsilon_y = PhotosyntheticOrganElement.PARAMETERS.epsilon['z'], PhotosyntheticOrganElement.PARAMETERS.epsilon['x'], PhotosyntheticOrganElement.PARAMETERS.epsilon['y']
elastic_component = (epsilon_z * epsilon_x * epsilon_y) / (epsilon_z * epsilon_x + epsilon_z * epsilon_y + epsilon_x * epsilon_y) #: Elastic reversible growth (MPa)
plastic_component = 0 #: Plastic irreversible growth (MPa)
delta_turgor_water_potential = ((1 / (
parameters.RHO_WATER * volume * parameters.VSTORAGE)) * delta_water_content - plastic_component) * elastic_component #: (MPa)
return delta_turgor_water_potential
[docs]
@staticmethod
def calculate_delta_organ_dimensions(delta_turgor_water_potential, organ_dimensions):
"""Delta of lamina dimensions according to turgor water potential, dimensions, and elasticity.
:param float delta_turgor_water_potential: delta of turgor water potential integrated over delta t (MPa)
:param dict organ_dimensions: dict of organ dimensions at time t. Keys = ['length', 'thickness', 'width'] (m)
:return: Delta of organ specific-dimensions (m). Keys = ['length', 'width', 'thickness']
:rtype: dict
"""
delta_organ_dimensions = {}
epsilon_dict = PhotosyntheticOrganElement.PARAMETERS.epsilon.items()
mapping_dimensions = {'x': 'width', 'y': 'thickness', 'z': 'length'}
for epsilon_dimension, epsilon_value in epsilon_dict:
delta_organ_dimensions[mapping_dimensions[epsilon_dimension]] = ((1 / epsilon_value) * delta_turgor_water_potential) * organ_dimensions[mapping_dimensions[epsilon_dimension]]
return delta_organ_dimensions
[docs]
class LaminaElement(PhotosyntheticOrganElement):
"""
The class :class:`LaminaElement`.
"""
PARAMETERS = parameters.LAMINA_ELEMENT_PARAMETERS #: the internal parameters of the lamina
INIT_COMPARTMENTS = parameters.LAMINA_ELEMENT_INIT_COMPARTMENTS #: the initial values of compartments and state parameters
[docs]
class InternodeElement(PhotosyntheticOrganElement):
"""
The class :class:`InternodeElement`.
"""
PARAMETERS = parameters.INTERNODE_ELEMENT_PARAMETERS #: the internal parameters of the internode
INIT_COMPARTMENTS = parameters.INTERNODE_ELEMENT_INIT_COMPARTMENTS #: the initial values of compartments and state parameters
[docs]
class SheathElement(PhotosyntheticOrganElement):
"""
The class :class:`SheathElement`.
"""
PARAMETERS = parameters.SHEATH_ELEMENT_PARAMETERS #: the internal parameters of the sheath
INIT_COMPARTMENTS = parameters.SHEATH_ELEMENT_INIT_COMPARTMENTS #: the initial values of compartments and state parameters
[docs]
class Soil:
"""
The class :class:`Soil` defines the soil water potential as function of the soil relative water content.
"""
PARAMETERS = parameters.SOIL_PARAMETERS #: the internal parameters of the soil
def __init__(self, water_content, constant_water_content=True, hourly_rehydration=0):
"""
:param float water_content: soil water content (g).
:param bool constant_water_content: If True, the model run with a constant soil water content.
:param float hourly_rehydration: Hourly rehydration calculated from an external scenario defined by user (g).
"""
self.constant_water_content = constant_water_content
self.hourly_rehydration = hourly_rehydration
# state variables
self.water_content = water_content #: water content of the soil (g)
# intermediate variables
self.SRWC = (self.water_content / self.PARAMETERS.AWC) * 100 #: Soil Relative Water Content (%)
self.water_potential = self.calculate_water_potential(self.SRWC) #: Water potential of the soil (MPa)
# VARIABLES
[docs]
@staticmethod
def calculate_SRWC(water_content):
"""Soil Relative Water Content
:param float water_content: soil water content (g)
:return: SRWC (dimensionless)
:rtype: %
"""
return (water_content / Soil.PARAMETERS.AWC) * 100
[docs]
@staticmethod
def calculate_water_potential(SRWC):
"""Total water potential of the xylem (Mpa)
Equation from Chen et al. (2019), adapted for a deep loam-clay soil (leached soil on loessic silt), typical of Grignon (France).
:param float SRWC: %
:return: Total water potential (MPa)
:rtype: float
"""
soil_water_potential = - exp((-SRWC + parameters.SOIL_PARAMETERS.Soil_a) / parameters.SOIL_PARAMETERS.Soil_b)
return soil_water_potential
# COMPARTMENTS
[docs]
def calculate_water_content_derivative(self, soil_water_outputs, constant_water_content):
"""delta soil nitrates.
:param float soil_water_outputs: Sum of water used for plant transpiration of growth over delta_t (g)
:param bool constant_water_content: whether the water content is constant or not
:return: delta water_content (g)
:rtype: float
"""
# todo: should include rainfall in a future version
if constant_water_content:
return 0
else:
return self.hourly_rehydration - soil_water_outputs