Source code for openalea.cnwheat.simulation

# -*- coding: latin-1 -*-

from __future__ import division  # use "//" to do integer division
import logging

import numpy as np
from scipy.integrate import solve_ivp
from scipy import interpolate

from openalea.cnwheat import model
from openalea.cnwheat import tools

"""
    cnwheat.simulation
    ~~~~~~~~~~~~~~~~~~

    The module :mod:`cnwheat.simulation` is the front-end to run the model CN-Wheat.
    The public API consists of methods :meth:`initialize` and :meth:`run`.

"""


[docs] class SimulationError(Exception): """ Abstract class for the management of simulation errors. Do not instance it directly. """ pass
[docs] class SimulationConstructionError(SimulationError): """ Exception raised when a problem occurs in the constructor, in particular when the arguments are not consistent with each other. """ pass
[docs] class SimulationInitializationError(SimulationError): """ Exception raised when a problem occurs at initialization time, in particular when checking the consistency of inputs `population` and `soils` (see :meth:`initialize`). """ pass
[docs] class SimulationRunError(SimulationError): """ Exception raised when running a simulation, for example when a problem occurs during the integration of the system of differential equations. """ pass
[docs] class Simulation(object): """ The Simulation class permits to initialize and run the model. User should use method :meth:`initialize` to initialize the model, and method :meth:`run` to run the model. :param class respiration_model: the model of respiration to use. This model must define a class implementing these functions: * R_Nnit_upt(U_Nnit, sucrose): Nitrate uptake respiration. * Parameters: - `U_Nnit` (:class:`float`) - uptake of N nitrates (µmol` N) - `sucrose` (:class:`float`) - amount of C sucrose in organ (µmol` C) * Returns: _R_Nnit_upt (µmol` C respired) * Returns Type: :class:`float` * R_phloem(sucrose_loading, sucrose, mstruct): Phloem loading respiration * Parameters: - `sucrose_loading` (:class:`float`) - Loading flux from the C substrate pool to phloem (µmol` C g-1 mstruct) - `sucrose` (:class:`float`) - amount of C sucrose in organ (µmol` C) - `mstruct` (:class:`float`) - structural dry mass of organ (g) * Returns: _R_phloem (µmol` C respired) * Returns Type: :class:`float` * R_Nnit_red(s_amino_acids, sucrose, mstruct, root=False): Nitrate reduction-linked respiration Distinction is made between nitrate realised in roots or in shoots where a part of the energy required is derived from ATP and reducing power obtained directly from photosynthesis (rather than C substrate) * Parameters: - `s_amino_acids` (:class:`float`) - consumption of N for the synthesis of amino acids (µmol` N g-1 mstruct) (in the present version, this is used to approximate nitrate reduction needed in the original model of Thornley and Cannell, 2000) - `sucrose` (:class:`float`) - amount of C sucrose in organ (µmol` C) - `mstruct` (:class:`float`) - structural dry mass of organ (g) - `root` (:class:`bool`) - specifies if the nitrate reduction-linked respiration is computed for shoot (False) or root (True) tissues. * Returns: _R_Nnit_upt (µmol` C respired) * Returns Type: :class:`float` * R_residual(sucrose, mstruct, Ntot, delta_t, Ts): Residual maintenance respiration (cost from protein turn-over, cell ion gradients, futile cycles...) * Parameters: - `sucrose` (:class:`float`) - amount of C sucrose (µmol` C) - `mstruct` (:class:`float`) - structural dry mass of organ (g) - `Ntot` (:class:`float`) - total N in organ (µmol` N) - `delta_t` (:class:`float`) - timestep (s) - `Ts` (:class:`float`) - organ temperature (°C) * Returns: _R_residual (µmol` C respired) * Returns Type: :class:`float` * R_grain_growth(mstruct_growth, starch_filling, mstruct): Grain growth respiration * Parameters: - `mstruct_growth` (:class:`float`) - gross growth of grain structure (µmol` C added in grain structure) - `starch_filling` (:class:`float`) - gross growth of grain starch (µmol` C added in grain starch g-1 mstruct) - `mstruct` (:class:`float`) - structural dry mass of organ (g) * Returns: R_grain_growth (µmol` C respired) * Returns Type: :class:`float` :param int delta_t: the delta t of the simulation (in seconds) ; default is `1`. :param dict [int, int] culm_density: culm density (culm m-2). :param bool interpolate_forcings: if True: interpolate senescence and photosynthesis forcings from values of `senescence_forcings_delta_t`and `senescence_forcings_delta_t`. Default is `False` (do not interpolate the forcings). :param int senescence_forcings_delta_t: the delta t of the senescence forcings (in seconds) ; default is `None`. If the user sets `interpolate_forcings` to `True`, then he/she must also set `senescence_forcings_delta_t` to an integer value greater or equal to `delta_t`. For example, if `interpolate_forcings` is `True` and `delta_t==3600`, then `senescence_forcings_delta_t` must be greater or equal to `3600`, that is for example `7200`. :param int photosynthesis_forcings_delta_t: the delta t of the photosynthesis forcings (in seconds) ; default is `None`. If the user sets `interpolate_forcings` to `True`, then he/she must also set `photosynthesis_forcings_delta_t` to an integer value greater or equal to `delta_t`. For example, if `interpolate_forcings` is `True` and `delta_t==3600`, then `photosynthesis_forcings_delta_t` must be greater or equal to `3600`, that is for example `7200`. - interpolate_forcings (:class:`bool`) - if True: interpolate senescence and photosynthesis forcings from values of `senescence_forcings_delta_t` and `senescence_forcings_delta_t`. Default is `False` (do not interpolate the forcings). - senescence_forcings_delta_t (:class:`int`) - the delta t of the senescence forcings (in seconds) ; default is `None`. If the user sets `interpolate_forcings` to `True`, then he/she must also set `senescence_forcings_delta_t` to an integer value greater or equal to `delta_t`. For example, if `interpolate_forcings` is `True` and `delta_t==3600`, then `senescence_forcings_delta_t` must be greater or equal to `3600`, that is for example `7200`. - photosynthesis_forcings_delta_t (:class:`int`) - the delta t of the photosynthesis forcings (in seconds) ; default is `None`. If the user sets `interpolate_forcings` to `True`, then he/she must also set `photosynthesis_forcings_delta_t` to an integer value greater or equal to `delta_t`. For example, if `interpolate_forcings` is `True` and `delta_t==3600`, then `photosynthesis_forcings_delta_t` must be greater or equal to `3600`, that is for example `7200`. """ #: the name of the compartments attributes in the model, for objects of types #: :class:`model.Plant`, :class:`model.Axis`, :class:`model.Phytomer`, #: :class:`model.Organ`, :class:`model.HiddenZone`, :class:`model.PhotosyntheticOrganElement`, #: and :class:`model.Soil`. MODEL_COMPARTMENTS_NAMES = {model.Plant: [], model.Axis: ['C_exudated', 'sum_respi_shoot', 'sum_respi_roots'], model.Phytomer: [], model.Organ: ['age_from_flowering', 'amino_acids', 'cytokinins', 'nitrates', 'proteins', 'starch', 'structure', 'sucrose'], model.HiddenZone: ['amino_acids', 'fructan', 'proteins', 'sucrose'], model.PhotosyntheticOrganElement: ['amino_acids', 'cytokinins', 'fructan', 'nitrates', 'proteins', 'starch', 'sucrose', 'triosesP'], model.Soil: ['nitrates']} #: the time index T_INDEX = ['t'] # --------------------------------------------------------------------------- # ---------- DEFINITION OF THE PARAMETERS AND COMPUTED VARIABLES ------------ # --------------------------------------------------------------------------- # ---------- PLANT scale ---------- #: the index to locate the plants in the modeled system PLANTS_INDEXES = ['plant'] #: concatenation of :attr:`T_INDEX` and :attr:`PLANTS_INDEXES` PLANTS_T_INDEXES = T_INDEX + PLANTS_INDEXES #: the parameters which define the state of the modeled system at plant scale PLANTS_STATE_PARAMETERS = ['Tair'] #: the variables which define the state of the modeled system at plant scale, #: formed be the concatenation of :attr:`PLANTS_STATE_PARAMETERS` and the names #: of the compartments associated to each plant (see :attr:`MODEL_COMPARTMENTS_NAMES`) PLANTS_STATE = PLANTS_STATE_PARAMETERS + MODEL_COMPARTMENTS_NAMES.get(model.Plant, []) #: the variables that we need to compute in order to compute fluxes and/or compartments values at plant scale PLANTS_INTERMEDIATE_VARIABLES = [] #: the fluxes exchanged between the compartments at plant scale PLANTS_FLUXES = [] #: the variables computed by integrating values of plant components parameters/variables recursively PLANTS_INTEGRATIVE_VARIABLES = [] #: all the variables computed during a run step of the simulation at plant scale PLANTS_RUN_VARIABLES = PLANTS_STATE + PLANTS_INTERMEDIATE_VARIABLES + PLANTS_FLUXES + PLANTS_INTEGRATIVE_VARIABLES # ---------- AXIS scale ---------- #: the indexes to locate the axes in the modeled system AXES_INDEXES = ['plant', 'axis'] #: concatenation of :attr:`T_INDEX` and :attr:`AXES_INDEXES` AXES_T_INDEXES = T_INDEX + AXES_INDEXES #: the parameters which define the state of the modeled system at axis scale AXES_STATE_PARAMETERS = ['mstruct', 'senesced_mstruct'] #: the variables which define the state of the modeled system at axis scale, #: formed be the concatenation of :attr:`AXES_STATE_PARAMETERS` and the names #: of the compartments associated to each axis (see :attr:`MODEL_COMPARTMENTS_NAMES`) AXES_STATE = AXES_STATE_PARAMETERS + MODEL_COMPARTMENTS_NAMES.get(model.Axis, []) #: the variables that we need to compute in order to compute fluxes and/or compartments values at axis scale AXES_INTERMEDIATE_VARIABLES = [] #: the fluxes exchanged between the compartments at axis scale AXES_FLUXES = [] #: the variables computed by integrating values of axis components parameters/variables recursively AXES_INTEGRATIVE_VARIABLES = ['Total_Transpiration'] #: all the variables computed during a run step of the simulation at axis scale AXES_RUN_VARIABLES = AXES_STATE + AXES_INTERMEDIATE_VARIABLES + AXES_FLUXES + AXES_INTEGRATIVE_VARIABLES # ---------- PHYTOMER scale ---------- #: the indexes to locate the phytomers in the modeled system PHYTOMERS_INDEXES = ['plant', 'axis', 'metamer'] #: concatenation of :attr:`T_INDEX` and :attr:`PHYTOMERS_INDEXES` PHYTOMERS_T_INDEXES = T_INDEX + PHYTOMERS_INDEXES #: the parameters which define the state of the modeled system at phytomer scale PHYTOMERS_STATE_PARAMETERS = ['mstruct'] #: the variables which define the state of the modeled system at phytomer scale, #: formed be the concatenation of :attr:`PHYTOMERS_STATE_PARAMETERS` and the names #: of the compartments associated to each phytomer (see :attr:`MODEL_COMPARTMENTS_NAMES`) PHYTOMERS_STATE = PHYTOMERS_STATE_PARAMETERS + MODEL_COMPARTMENTS_NAMES.get(model.Phytomer, []) #: the variables that we need to compute in order to compute fluxes and/or compartments values at phytomer scale PHYTOMERS_INTERMEDIATE_VARIABLES = [] #: the fluxes exchanged between the compartments at phytomer scale PHYTOMERS_FLUXES = [] #: the variables computed by integrating values of phytomer components parameters/variables recursively PHYTOMERS_INTEGRATIVE_VARIABLES = [] #: all the variables computed during a run step of the simulation at phytomer scale PHYTOMERS_RUN_VARIABLES = PHYTOMERS_STATE + PHYTOMERS_INTERMEDIATE_VARIABLES + PHYTOMERS_FLUXES + PHYTOMERS_INTEGRATIVE_VARIABLES # ---------- ORGAN scale ---------- #: the indexes to locate the organs in the modeled system ORGANS_INDEXES = ['plant', 'axis', 'organ'] #: concatenation of :attr:`T_INDEX` and :attr:`ORGANS_INDEXES` ORGANS_T_INDEXES = T_INDEX + ORGANS_INDEXES #: the parameters which define the state of the modeled system at organ scale ORGANS_STATE_PARAMETERS = ['mstruct', 'Nstruct', 'senesced_mstruct'] #: the variables which define the state of the modeled system at organ scale, #: formed be the concatenation of :attr:`ORGANS_STATE_PARAMETERS` and the names #: of the compartments associated to each organ (see :attr:`MODEL_COMPARTMENTS_NAMES`) ORGANS_STATE = ORGANS_STATE_PARAMETERS + MODEL_COMPARTMENTS_NAMES.get(model.Organ, []) #: the variables that we need to compute in order to compute fluxes and/or compartments values at organ scale ORGANS_INTERMEDIATE_VARIABLES = ['C_exudation', 'HATS_LATS', 'N_exudation', 'RGR_Structure', 'R_Nnit_red', 'R_Nnit_upt', 'Respi_growth', 'R_grain_growth_starch', 'R_grain_growth_struct', 'R_residual', 'regul_transpiration', 'sum_respi'] #: the fluxes exchanged between the compartments at organ scale ORGANS_FLUXES = ['Export_Amino_Acids', 'Export_Nitrates', 'Export_cytokinins', 'S_Amino_Acids', 'S_cytokinins', 'S_grain_starch', 'S_grain_structure', 'S_Proteins', 'Unloading_Amino_Acids', 'Unloading_Sucrose', 'Uptake_Nitrates'] #: the variables computed by integrating values of organ components parameters/variables recursively ORGANS_INTEGRATIVE_VARIABLES = ['Total_Organic_Nitrogen'] #: all the variables computed during a run step of the simulation at organ scale ORGANS_RUN_VARIABLES = ORGANS_STATE + ORGANS_INTERMEDIATE_VARIABLES + ORGANS_FLUXES + ORGANS_INTEGRATIVE_VARIABLES # ---------- HIDDENZONE scale ---------- #: the indexes to locate the hidden zones in the modeled system HIDDENZONE_INDEXES = ['plant', 'axis', 'metamer'] #: concatenation of :attr:`T_INDEX` and :attr:`HIDDENZONE_INDEXES` HIDDENZONE_T_INDEXES = T_INDEX + HIDDENZONE_INDEXES #: the parameters which define the state of the modeled system at hidden zone scale HIDDENZONE_STATE_PARAMETERS = ['Nstruct', 'mstruct', 'ratio_DZ'] #: the variables which define the state of the modeled system at hidden zone scale, #: formed be the concatenation of :attr:`HIDDENZONE_STATE_PARAMETERS` and the names #: of the compartments associated to each hidden zone (see :attr:`MODEL_COMPARTMENTS_NAMES`) HIDDENZONE_STATE = HIDDENZONE_STATE_PARAMETERS + MODEL_COMPARTMENTS_NAMES.get(model.HiddenZone, []) #: the variables that we need to compute in order to compute fluxes and/or compartments values at hidden zone scale HIDDENZONE_INTERMEDIATE_VARIABLES = ['nb_replications'] #: the fluxes exchanged between the compartments at hidden zone scale HIDDENZONE_FLUXES = ['D_Fructan', 'D_Proteins', 'S_Fructan', 'S_Proteins', 'Unloading_Amino_Acids', 'Unloading_Sucrose'] #: the variables computed by integrating values of hidden zone components parameters/variables recursively HIDDENZONE_INTEGRATIVE_VARIABLES = [] #: all the variables computed during a run step of the simulation at plnat scale HIDDENZONE_RUN_VARIABLES = HIDDENZONE_STATE + HIDDENZONE_INTERMEDIATE_VARIABLES + HIDDENZONE_FLUXES + HIDDENZONE_INTEGRATIVE_VARIABLES # ---------- ELEMENT scale ---------- #: the indexes to locate the elements in the modeled system ELEMENTS_INDEXES = ['plant', 'axis', 'metamer', 'organ', 'element'] #: concatenation of :attr:`T_INDEX` and :attr:`ELEMENTS_INDEXES` ELEMENTS_T_INDEXES = T_INDEX + ELEMENTS_INDEXES #: the parameters which define the state of the modeled system at element scale ELEMENTS_STATE_PARAMETERS = ['Ag', 'Nstruct', 'Tr', 'Ts', 'green_area', 'is_growing', 'mstruct', 'senesced_mstruct'] #: the variables which define the state of the modeled system at element scale, #: formed be the concatenation of :attr:`ELEMENTS_STATE_PARAMETERS` and the names #: of the compartments associated to each element (see :attr:`MODEL_COMPARTMENTS_NAMES`) ELEMENTS_STATE = ELEMENTS_STATE_PARAMETERS + MODEL_COMPARTMENTS_NAMES.get(model.PhotosyntheticOrganElement, []) #: the variables that we need to compute in order to compute fluxes and/or compartments values at element scale ELEMENTS_INTERMEDIATE_VARIABLES = ['Photosynthesis', 'R_Nnit_red', 'R_phloem_loading', 'R_residual', 'Transpiration', 'sum_respi', 'nb_replications'] #: the fluxes exchanged between the compartments at element scale ELEMENTS_FLUXES = ['Amino_Acids_import', 'D_Fructan', 'D_Proteins', 'D_Starch', 'D_cytokinins', 'Loading_Amino_Acids', 'Loading_Sucrose', 'Nitrates_import', 'Regul_S_Fructan', 'S_Fructan', 'S_Starch', 'S_Sucrose', 'S_Amino_Acids', 'S_Proteins', 'cytokinins_import'] #: the variables computed by integrating values of element components parameters/variables recursively ELEMENTS_INTEGRATIVE_VARIABLES = ['Total_Organic_Nitrogen'] #: all the variables computed during a run step of the simulation at element scale ELEMENTS_RUN_VARIABLES = ELEMENTS_STATE + ELEMENTS_INTERMEDIATE_VARIABLES + ELEMENTS_FLUXES + ELEMENTS_INTEGRATIVE_VARIABLES # ---------- SOIL scale ---------- #: the indexes to locate the soils in the modeled system SOILS_INDEXES = ['plant', 'axis'] #: concatenation of :attr:`T_INDEX` and :attr:`SOILS_INDEXES` SOILS_T_INDEXES = T_INDEX + SOILS_INDEXES #: the parameters which define the state of the modeled system at soil scale SOILS_STATE_PARAMETERS = ['Tsoil', 'volume'] #: the variables which define the state of the modeled system at soil scale, #: formed be the concatenation of :attr:`SOILS_STATE_PARAMETERS` and the names #: of the compartments associated to each soil (see :attr:`MODEL_COMPARTMENTS_NAMES`) SOILS_STATE = SOILS_STATE_PARAMETERS + MODEL_COMPARTMENTS_NAMES.get(model.Soil, []) #: the variables that we need to compute in order to compute fluxes and/or compartments values at soil scale SOILS_INTERMEDIATE_VARIABLES = ['Conc_Nitrates_Soil', 'mineralisation'] #: the fluxes exchanged between the compartments at soil scale SOILS_FLUXES = [] #: the variables computed by integrating values of soil components parameters/variables recursively SOILS_INTEGRATIVE_VARIABLES = [] #: all the variables computed during a run step of the simulation at soil scale SOILS_RUN_VARIABLES = SOILS_STATE + SOILS_INTERMEDIATE_VARIABLES + SOILS_FLUXES + SOILS_INTEGRATIVE_VARIABLES #: a dictionary of all the variables which define the state of the modeled system, for each scale ALL_STATE_PARAMETERS = {model.Plant: PLANTS_STATE_PARAMETERS, model.Axis: AXES_STATE_PARAMETERS, model.Phytomer: PHYTOMERS_STATE_PARAMETERS, model.Organ: ORGANS_STATE_PARAMETERS, model.HiddenZone: HIDDENZONE_STATE_PARAMETERS, model.PhotosyntheticOrganElement: ELEMENTS_STATE_PARAMETERS, model.Soil: SOILS_STATE_PARAMETERS} #: the names of the roots (scenescence) forcings ROOTS_FORCINGS = ('Nstruct', 'mstruct') #: the names of the elements photosynthesis forcings ELEMENTS_PHOTOSYNTHESIS_FORCINGS = ('Ag', 'Tr', 'Ts') #: the names of the elements scenescence forcings ELEMENTS_SENESCENCE_FORCINGS = ('Nstruct', 'green_area', 'mstruct') #: the names of the elements photosynthesis and scenescence forcings ELEMENTS_FORCINGS = ELEMENTS_PHOTOSYNTHESIS_FORCINGS + ELEMENTS_SENESCENCE_FORCINGS #: the name of the loggers for compartments and derivatives LOGGERS_NAMES = {'compartments': {model.Plant: 'cnwheat.compartments.plants', model.Axis: 'cnwheat.compartments.axes', model.Phytomer: 'cnwheat.compartments.phytomers', model.Organ: 'cnwheat.compartments.organs', model.HiddenZone: 'cnwheat.compartments.hiddenzones', model.PhotosyntheticOrganElement: 'cnwheat.compartments.elements', model.Soil: 'cnwheat.compartments.soils'}, 'derivatives': {model.Plant: 'cnwheat.derivatives.plants', model.Axis: 'cnwheat.derivatives.axes', model.Phytomer: 'cnwheat.derivatives.phytomers', model.Organ: 'cnwheat.derivatives.organs', model.HiddenZone: 'cnwheat.derivatives.hiddenzones', model.PhotosyntheticOrganElement: 'cnwheat.derivatives.elements', model.Soil: 'cnwheat.derivatives.soils'}} def __init__(self, respiration_model, delta_t=1, culm_density=None, interpolate_forcings=False, senescence_forcings_delta_t=None, photosynthesis_forcings_delta_t=None): self.respiration_model = respiration_model #: the model of respiration to use self.population = model.Population() #: the population to simulate on #: The inputs of the soils. #: #: `soils` is a dictionary of objects of type :class:`model.Soil`: #: {(plant_index, axis_label): soil_object, ...} self.soils = {} self.initial_conditions = [] #: the initial conditions of the compartments in the population and soils self.initial_conditions_mapping = {} #: dictionary to map the compartments to their indexes in :attr:`initial_conditions` self.progressbar = tools.ProgressBar(title='Solver progress') #: progress bar to show the progress of the solver self.show_progressbar = False #: True: show the progress bar ; False: DO NOT show the progress bar self.delta_t = delta_t #: the delta t of the simulation (in seconds) self.time_step = self.delta_t / 3600.0 #: time step of the simulation (in hours) self.time_grid = np.array([0.0, self.time_step]) #: the time grid of the simulation (in hours) self.culm_density = culm_density #: culm density (culm m-2) self.interpolate_forcings = interpolate_forcings #: a boolean flag which indicates if we want to interpolate or not the forcings (True: interpolate, False: do not interpolate) # set the loggers for compartments and derivatives compartments_logger = logging.getLogger('cnwheat.compartments') derivatives_logger = logging.getLogger('cnwheat.derivatives') if compartments_logger.isEnabledFor(logging.DEBUG) or derivatives_logger.isEnabledFor(logging.DEBUG): sep = ',' if compartments_logger.isEnabledFor(logging.DEBUG): plants_compartments_logger = logging.getLogger('cnwheat.compartments.plants') plants_compartments_logger.debug(sep.join(Simulation.PLANTS_T_INDEXES + Simulation.PLANTS_STATE)) axes_compartments_logger = logging.getLogger('cnwheat.compartments.axes') axes_compartments_logger.debug(sep.join(Simulation.AXES_T_INDEXES + Simulation.AXES_STATE)) phytomers_compartments_logger = logging.getLogger('cnwheat.compartments.phytomers') phytomers_compartments_logger.debug(sep.join(Simulation.PHYTOMERS_T_INDEXES + Simulation.PHYTOMERS_STATE)) organs_compartments_logger = logging.getLogger('cnwheat.compartments.organs') organs_compartments_logger.debug(sep.join(Simulation.ORGANS_T_INDEXES + Simulation.ORGANS_STATE)) hiddenzones_compartments_logger = logging.getLogger('cnwheat.compartments.hiddenzones') hiddenzones_compartments_logger.debug(sep.join(Simulation.HIDDENZONE_T_INDEXES + Simulation.HIDDENZONE_STATE)) elements_compartments_logger = logging.getLogger('cnwheat.compartments.elements') elements_compartments_logger.debug(sep.join(Simulation.ELEMENTS_T_INDEXES + Simulation.ELEMENTS_STATE)) soils_compartments_logger = logging.getLogger('cnwheat.compartments.soils') soils_compartments_logger.debug(sep.join(Simulation.SOILS_T_INDEXES + Simulation.SOILS_STATE)) if derivatives_logger.isEnabledFor(logging.DEBUG): plants_derivatives_logger = logging.getLogger('cnwheat.derivatives.plants') plants_derivatives_logger.debug(sep.join(Simulation.PLANTS_T_INDEXES + Simulation.PLANTS_STATE)) axes_derivatives_logger = logging.getLogger('cnwheat.derivatives.axes') axes_derivatives_logger.debug(sep.join(Simulation.AXES_T_INDEXES + Simulation.AXES_STATE)) phytomers_derivatives_logger = logging.getLogger('cnwheat.derivatives.phytomers') phytomers_derivatives_logger.debug(sep.join(Simulation.PHYTOMERS_T_INDEXES + Simulation.PHYTOMERS_STATE)) organs_derivatives_logger = logging.getLogger('cnwheat.derivatives.organs') organs_derivatives_logger.debug(sep.join(Simulation.ORGANS_T_INDEXES + Simulation.ORGANS_STATE)) hiddenzones_derivatives_logger = logging.getLogger('cnwheat.derivatives.hiddenzones') hiddenzones_derivatives_logger.debug(sep.join(Simulation.HIDDENZONE_T_INDEXES + Simulation.HIDDENZONE_STATE)) elements_derivatives_logger = logging.getLogger('cnwheat.derivatives.elements') elements_derivatives_logger.debug(sep.join(Simulation.ELEMENTS_T_INDEXES + Simulation.ELEMENTS_STATE)) soils_derivatives_logger = logging.getLogger('cnwheat.derivatives.soils') soils_derivatives_logger.debug(sep.join(Simulation.SOILS_T_INDEXES + Simulation.SOILS_STATE)) logger = logging.getLogger(__name__) if logger.isEnabledFor(logging.DEBUG): self.t_offset = 0.0 #: the absolute time offset elapsed from the beginning of the simulation if interpolate_forcings: if senescence_forcings_delta_t is not None and photosynthesis_forcings_delta_t is not None and \ senescence_forcings_delta_t >= delta_t and photosynthesis_forcings_delta_t >= delta_t: self.senescence_forcings_delta_t_ratio = senescence_forcings_delta_t / delta_t #: the ratio between the delta t of the senescence forcings and the delta t of the simulation self.photosynthesis_forcings_delta_t_ratio = photosynthesis_forcings_delta_t / delta_t #: the ratio between the delta t of the photosynthesis forcings and the delta t of the simulation elif senescence_forcings_delta_t is None: message = """The value of `interpolate_forcings` passed to the Simulation constructor is `True`, but `senescence_forcings_delta_t` is `None`. Please set `senescence_forcings_delta_t` (through the Simulation constructor) to a not `None` value.""" logger.exception(message) raise SimulationConstructionError(message) elif photosynthesis_forcings_delta_t is None: message = """The value of `interpolate_forcings` passed to the Simulation constructor is `True`, but `photosynthesis_forcings_delta_t` is `None`. Please set `photosynthesis_forcings_delta_t` (through the Simulation constructor) to a not `None` value.""" logger.exception(message) raise SimulationConstructionError(message) elif senescence_forcings_delta_t < delta_t: message = """The value of `senescence_forcings_delta_t` passed to the Simulation constructor is lesser than the one of `delta_t`. Please set a `senescence_forcings_delta_t` that is at least equal to `delta_t`.""" logger.exception(message) raise SimulationConstructionError(message) elif photosynthesis_forcings_delta_t < delta_t: message = """The value of `photosynthesis_forcings_delta_t` passed to the Simulation constructor is lesser than the one of `delta_t`. Please set a `photosynthesis_forcings_delta_t` that is at least equal to `delta_t`.""" logger.exception(message) raise SimulationConstructionError(message) self.previous_forcings_values = {} #: previous values of the forcings self.new_forcings_values = {} #: new values of the forcings self.interpolation_functions = {} #: functions to interpolate the forcings self.nfev_total = 0 #: cumulative number of RHS function evaluations
[docs] def initialize(self, population, soils, Tair=12, Tsoil=12): """ Initialize: * :attr:`population`, * :attr:`soils`, * :attr:`initial_conditions_mapping`, * and :attr:`initial_conditions` from `population` and `soils`. :param model.Population population: a population of plants. :param dict soils: the soil associated to each axis. `soils` must be a dictionary with the same structure as :attr:`soils` :param float Tair: air temperature (°C) :param float Tsoil: soil temperature (°C) """ logger = logging.getLogger(__name__) logger.info('Initialization of the simulation...') # clean the attributes of the simulation del self.population.plants[:] self.soils.clear() del self.initial_conditions[:] self.initial_conditions_mapping.clear() # create new population and soils self.population.plants.extend(population.plants) self.soils.update(soils) # check the consistency of population and soils if len(self.population.plants) != 0: # population must contain at least 1 plant for plant in self.population.plants: if len(plant.axes) != 0: # each plant must contain at least 1 axis for axis in plant.axes: if axis.roots is None: # each axis must have a "roots" message = 'No roots found in (plant={},axis={})'.format(plant.index, axis.label) logger.exception(message) raise SimulationInitializationError(message) if axis.phloem is None: # each axis must have a phloem message = 'No phloem found in (plant={},axis={})'.format(plant.index, axis.label) logger.exception(message) raise SimulationInitializationError(message) if len(axis.phytomers) != 0: # each axis must contain at least 1 phytomer for phytomer in axis.phytomers: phytomer_organs = (phytomer.lamina, phytomer.internode, phytomer.sheath, phytomer.chaff, phytomer.peduncle) # each phytomer must contain at least 1 photosynthetic organ or an hidden growing zone if phytomer_organs.count(None) != len(phytomer_organs) or phytomer.hiddenzone is not None: for organ in phytomer_organs: if organ is not None: organ_elements = (organ.exposed_element, organ.enclosed_element) # each photosynthetic organ must contain at least 1 element if organ_elements.count(None) != len(organ_elements): for element in organ_elements: if element is not None: # an element must belong to an organ of the same type (e.g. a LaminaElement must belong to a Lamina) if organ.__class__.__name__ not in element.__class__.__name__: message = 'In (plant={},axis={},phytomer={}), a {} belongs to a {}'.format(plant.index, axis.label, phytomer.index, element.__class__.__name__, organ.__class__.__name__) logger.exception(message) raise SimulationInitializationError(message) else: message = 'No element found in (plant={},axis={},phytomer={},organ={})'.format(plant.index, axis.label, phytomer.index, organ.label) logger.exception(message) raise SimulationInitializationError(message) else: message = 'Neither photosynthetic organ nor hidden growing zone found in (plant={},axis={},phytomer={})'.format(plant.index, axis.label, phytomer.index) logger.exception(message) raise SimulationInitializationError(message) else: message = 'No phytomer found in (plant={},axis={})'.format(plant.index, axis.label) logger.exception(message) raise SimulationInitializationError(message) if (plant.index, axis.label) not in self.soils: # each axis must be associated to a soil message = 'No soil found in (plant={},axis={})'.format(plant.index, axis.label) logger.exception(message) raise SimulationInitializationError(message) else: message = 'No axis found in (plant={})'.format(plant.index) logger.exception(message) raise SimulationInitializationError(message) else: message = 'No plant found in the population.' logger.exception(message) raise SimulationInitializationError(message) if self.interpolate_forcings: # Save the new value of each forcing and set the state parameters to the previous forcing values. self.new_forcings_values.clear() for plant in self.population.plants: for axis in plant.axes: if axis.roots is not None: roots_id = (plant.index, axis.label) self.new_forcings_values[roots_id] = {} for forcing_label in Simulation.ROOTS_FORCINGS: self.new_forcings_values[roots_id][forcing_label] = getattr(axis.roots, forcing_label) if roots_id in self.previous_forcings_values: setattr(axis.roots, forcing_label, self.previous_forcings_values[roots_id][forcing_label]) for phytomer in axis.phytomers: for organ in (phytomer.lamina, phytomer.sheath): if organ is None: continue for element in (organ.exposed_element, organ.enclosed_element): if element is not None: element_id = (plant.index, axis.label, phytomer.index, organ.label, element.label) self.new_forcings_values[element_id] = {} for forcing_label in Simulation.ELEMENTS_FORCINGS: self.new_forcings_values[element_id][forcing_label] = getattr(element, forcing_label) if element_id in self.previous_forcings_values: setattr(element, forcing_label, self.previous_forcings_values[element_id][forcing_label]) # Update soil and air temperature using weather data for soil_id, soil_inputs in self.soils.items(): self.soils[soil_id].Tsoil = Tsoil for plant in self.population.plants: plant.Tair = Tair # initialize initial conditions def _init_initial_conditions(model_object, index): class_ = model_object.__class__ if issubclass(class_, model.HiddenZone): class_ = model.HiddenZone elif issubclass(class_, model.Organ): class_ = model.Organ elif issubclass(class_, model.PhotosyntheticOrganElement): class_ = model.PhotosyntheticOrganElement compartments_names = Simulation.MODEL_COMPARTMENTS_NAMES[class_] self.initial_conditions_mapping[model_object] = {} for compartment_name in compartments_names: if hasattr(model_object, compartment_name): self.initial_conditions_mapping[model_object][compartment_name] = index self.initial_conditions.append(0) index += 1 return index i = 0 for soil in soils.values(): i = _init_initial_conditions(soil, i) for plant in self.population.plants: i = _init_initial_conditions(plant, i) for axis in plant.axes: i = _init_initial_conditions(axis, i) for organ in (axis.roots, axis.phloem, axis.grains): if organ is None: continue i = _init_initial_conditions(organ, i) for phytomer in axis.phytomers: i = _init_initial_conditions(phytomer, i) for organ in (phytomer.chaff, phytomer.peduncle, phytomer.lamina, phytomer.internode, phytomer.sheath, phytomer.hiddenzone): if organ is None: continue i = _init_initial_conditions(organ, i) if organ is phytomer.hiddenzone: continue for element in (organ.exposed_element, organ.enclosed_element): if element is None: continue i = _init_initial_conditions(element, i) self.population.calculate_aggregated_variables() logger.info('Initialization of the simulation DONE')
[docs] def run(self, show_progressbar=False): """ Compute CN exchanges which occurred in :attr:`population` and :attr:`soils` over :attr:`delta_t`. :param bool show_progressbar: True: show the progress bar of the solver ; False: do not show the progress bar (default). """ logger = logging.getLogger(__name__) logger.info('Run of CN-Wheat...') if self.interpolate_forcings: # interpolate the forcings self._interpolate_forcings() # set the progress-bar self.show_progressbar = show_progressbar if self.show_progressbar: self.progressbar.set_t_max(self.time_step) self._update_initial_conditions() if logger.isEnabledFor(logging.DEBUG): logger.debug("Run the solver with delta_t = %s", self.time_step) # call :func:`scipy.integrate.solve_ivp` to integrate the system during 1 time step ; # :func:`scipy.integrate.solve_ivp` computes the derivatives of each function by calling :meth:`_calculate_all_derivatives` sol = solve_ivp(fun=self._calculate_all_derivatives, t_span=self.time_grid, y0=self.initial_conditions, method='BDF', t_eval=np.array([self.time_step]), dense_output=False) self.nfev_total += sol.nfev if logger.isEnabledFor(logging.DEBUG): logger.debug("Run of the solver DONE") # check the integration ; raise an exception if the integration failed if not sol.success: message = "Integration failed: {}".format(sol.message) logger.exception(message) raise SimulationRunError(message) # Re-compute integrative variables self.population.calculate_aggregated_variables() if logger.isEnabledFor(logging.DEBUG): self.t_offset += self.time_step logger.info('Run of CN-Wheat DONE')
def _update_initial_conditions(self): """Update the compartments values in :attr:`initial_conditions` from the compartments values of :attr:`population` and :attr:`soils`. """ # Update the compartments values for model_object, compartments in self.initial_conditions_mapping.items(): for compartment_name, compartment_index in compartments.items(): self.initial_conditions[compartment_index] = getattr(model_object, compartment_name) def _interpolate_forcings(self): """Create functions to interpolate the forcings of the model to any time inside the time grid (see `self.time_grid`). If this is the first run of the model, then we consider that the forcings are constant. The interpolation functions are stored in :attr:`interpolation_functions`, and will be used later on and as needed by the SciPy solver. """ self.interpolation_functions.clear() next_forcings_values = {} for plant in self.population.plants: for axis in plant.axes: if axis.roots is not None: roots_id = (plant.index, axis.label) self.interpolation_functions[roots_id] = {} next_forcings_values[roots_id] = {} for forcing_label in Simulation.ROOTS_FORCINGS: if roots_id in self.previous_forcings_values and \ self.previous_forcings_values[roots_id][forcing_label] != self.new_forcings_values[roots_id][forcing_label]: prev_forcing_value = self.previous_forcings_values[roots_id][forcing_label] next_forcing_value = prev_forcing_value + (self.new_forcings_values[roots_id][forcing_label] - prev_forcing_value) / self.senescence_forcings_delta_t_ratio else: next_forcing_value = self.new_forcings_values[roots_id][forcing_label] prev_forcing_value = next_forcing_value self.interpolation_functions[roots_id][forcing_label] = interpolate.interp1d(self.time_grid, [prev_forcing_value, next_forcing_value], assume_sorted=True) next_forcings_values[roots_id][forcing_label] = next_forcing_value for phytomer in axis.phytomers: for organ in (phytomer.lamina, phytomer.sheath): if organ is None: continue for element in (organ.exposed_element, organ.enclosed_element): if element is not None: element_id = (plant.index, axis.label, phytomer.index, organ.label, element.label) self.interpolation_functions[element_id] = {} next_forcings_values[element_id] = {} for (forcing_labels, forcings_delta_t_ratio) in ((Simulation.ELEMENTS_PHOTOSYNTHESIS_FORCINGS, self.photosynthesis_forcings_delta_t_ratio), (Simulation.ELEMENTS_SENESCENCE_FORCINGS, self.senescence_forcings_delta_t_ratio)): for forcing_label in forcing_labels: if element_id in self.previous_forcings_values and \ self.previous_forcings_values[element_id][forcing_label] != self.new_forcings_values[element_id][forcing_label]: prev_forcing_value = self.previous_forcings_values[element_id][forcing_label] next_forcing_value = prev_forcing_value + (self.new_forcings_values[element_id][forcing_label] - prev_forcing_value) / forcings_delta_t_ratio else: next_forcing_value = self.new_forcings_values[element_id][forcing_label] prev_forcing_value = next_forcing_value self.interpolation_functions[element_id][forcing_label] = interpolate.interp1d(self.time_grid, [prev_forcing_value, next_forcing_value], assume_sorted=True) next_forcings_values[element_id][forcing_label] = next_forcing_value self.previous_forcings_values.clear() self.previous_forcings_values.update(next_forcings_values) def _log_compartments(self, t, y, loggers_names): """Log the values in `y` to the loggers in `loggers_names`. """ def update_rows(model_object, indexes, rows, i): """Update list `rows` appending a new row corresponding to the compartment values associated to object `model_object` located at indexes `indexes`. `i` is used to reach the values associated to object `model_object` from array `y`. """ row = [] class_ = model_object.__class__ if issubclass(class_, model.HiddenZone): class_ = model.HiddenZone elif issubclass(class_, model.Organ): class_ = model.Organ elif issubclass(class_, model.PhotosyntheticOrganElement): class_ = model.PhotosyntheticOrganElement parameters_names = Simulation.ALL_STATE_PARAMETERS[class_] for parameter_name in parameters_names: if hasattr(model_object, parameter_name): row.append(str(getattr(model_object, parameter_name))) else: row.append('NA') compartments_names = Simulation.MODEL_COMPARTMENTS_NAMES[class_] for compartment_name in compartments_names: if hasattr(model_object, compartment_name): row.append(str(y[i])) i += 1 else: row.append('NA') rows.append([str(index) for index in indexes] + row) return i i = 0 all_rows = dict([(class_, []) for class_ in loggers_names]) for soil_id, soil in self.soils.items(): i = update_rows(soil, (t,) + soil_id, all_rows[model.Soil], i) for plant in self.population.plants: i = update_rows(plant, [t, plant.index], all_rows[model.Plant], i) for axis in plant.axes: i = update_rows(axis, [t, plant.index, axis.label], all_rows[model.Axis], i) for organ in (axis.roots, axis.phloem, axis.grains): if organ is None: continue i = update_rows(organ, [t, plant.index, axis.label, organ.label], all_rows[model.Organ], i) for phytomer in axis.phytomers: i = update_rows(phytomer, [t, plant.index, axis.label, phytomer.index], all_rows[model.Phytomer], i) for organ in (phytomer.chaff, phytomer.peduncle, phytomer.lamina, phytomer.internode, phytomer.sheath, phytomer.hiddenzone): if organ is None: continue if organ is phytomer.hiddenzone: i = update_rows(organ, [t, plant.index, axis.label, phytomer.index], all_rows[model.HiddenZone], i) continue for element in (organ.exposed_element, organ.enclosed_element): if element is None: continue i = update_rows(element, [t, plant.index, axis.label, phytomer.index, organ.label, element.label], all_rows[model.PhotosyntheticOrganElement], i) row_sep = '\n' column_sep = ',' for class_, logger_name in loggers_names.items(): compartments_logger = logging.getLogger(logger_name) formatted_initial_conditions = row_sep.join([column_sep.join(row) for row in all_rows[class_]]) compartments_logger.debug(formatted_initial_conditions) def _calculate_all_derivatives(self, t, y): """Compute the derivative of `y` at `t`. :meth:`_calculate_all_derivatives` is passed as **func** argument to :func:`solve_ivp(fun, t_span, y0,...) <scipy.integrate.solve_ivp>`. :meth:`_calculate_all_derivatives` is called automatically by :func:`scipy.integrate.solve_ivp <scipy.integrate.solve_ivp>`. First call to :meth:`_calculate_all_derivatives` uses `y` = **y0** and `t` = **t_span** [0], where **y0** and **t_span** are arguments passed to :func:`solve_ivp(fun, t_span, y0,...) <scipy.integrate.solve_ivp>`. Following calls to :meth:`_calculate_all_derivatives` use `t` in [**t_span** [0], **t_span** [1]]. :param float t: The current t at which we want to compute the derivatives. Values of `t` are chosen automatically by :func:`scipy.integrate.solve_ivp`. At first call to :meth:`_calculate_all_derivatives` by :func:`scipy.integrate.solve_ivp`, `t` = **t_span** [0], where **t_span** is one of the arguments passed to :func:`solve_ivp(fun, t_span, y0,...) <scipy.integrate.solve_ivp>`. For each following call to :meth:`_calculate_all_derivatives`, `t` belongs to the interval [**t_span** [0], **t_span** [1]]. :param list [float] y: The current values of y. At first call to :meth:`_calculate_all_derivatives` by :func:`scipy.integrate.solve_ivp`, `y` = **y0** where **y0** is one of the arguments passed to :func:`solve_ivp(fun, t_span, y0,...) <scipy.integrate.solve_ivp>`. Then, values of `y` are chosen automatically by :func:`scipy.integrate.solve_ivp`. :return: The derivatives of `y` at `t`. :rtype: list [float] """ logger = logging.getLogger(__name__) if logger.isEnabledFor(logging.DEBUG): t_abs = t + self.t_offset logger.debug('t = {}'.format(t_abs)) if self.interpolate_forcings: # Update state parameters using interpolation functions for plant in self.population.plants: for axis in plant.axes: if axis.roots is not None: roots_id = (plant.index, axis.label) for forcing_label in Simulation.ROOTS_FORCINGS: setattr(axis.roots, forcing_label, float(self.interpolation_functions[roots_id][forcing_label](t))) for phytomer in axis.phytomers: for organ in (phytomer.lamina, phytomer.sheath): if organ is None: continue for element in (organ.exposed_element, organ.enclosed_element): if element is not None: element_id = (plant.index, axis.label, phytomer.index, organ.label, element.label) for forcing_label in Simulation.ELEMENTS_FORCINGS: setattr(element, forcing_label, float(self.interpolation_functions[element_id][forcing_label](t))) # Compute integrative variables self.population.calculate_aggregated_variables() compartments_logger = logging.getLogger('cnwheat.compartments') if logger.isEnabledFor(logging.DEBUG) and compartments_logger.isEnabledFor(logging.DEBUG): self._log_compartments(t_abs, y, Simulation.LOGGERS_NAMES['compartments']) # check that the solver is not crashed y_isnan = np.isnan(y) if y_isnan.any(): message = 'The solver did not manage to compute a compartment. See the logs. NaN found in y' logger.exception(message) raise SimulationRunError(message) y_derivatives = np.zeros_like(y) # TODO: TEMP !!!! soil_contributors = [] soil = self.soils[(1, 'MS')] soil.nitrates = y[self.initial_conditions_mapping[soil]['nitrates']] soil.Conc_Nitrates_Soil = soil.calculate_Conc_Nitrates(soil.nitrates) soil.T_effect_Vmax = soil.calculate_temperature_effect_on_Vmax(soil.Tsoil) soil.T_effect_conductivity = soil.calculate_temperature_effect_on_conductivity(soil.Tsoil) for plant in self.population.plants: plant.T_effect_conductivity = plant.calculate_temperature_effect_on_conductivity(plant.Tair) plant.T_effect_Vmax = plant.calculate_temperature_effect_on_Vmax(plant.Tair) for axis in plant.axes: sum_respi_shoot = 0.0 axis.C_exudated = y[self.initial_conditions_mapping[axis]['C_exudated']] axis.sum_respi_shoot = y[self.initial_conditions_mapping[axis]['sum_respi_shoot']] axis.sum_respi_roots = y[self.initial_conditions_mapping[axis]['sum_respi_roots']] # Phloem phloem_contributors = [] axis.phloem.sucrose = y[self.initial_conditions_mapping[axis.phloem]['sucrose']] axis.phloem.amino_acids = y[self.initial_conditions_mapping[axis.phloem]['amino_acids']] # Roots axis.roots.nitrates = y[self.initial_conditions_mapping[axis.roots]['nitrates']] axis.roots.amino_acids = y[self.initial_conditions_mapping[axis.roots]['amino_acids']] axis.roots.sucrose = y[self.initial_conditions_mapping[axis.roots]['sucrose']] axis.roots.cytokinins = y[self.initial_conditions_mapping[axis.roots]['cytokinins']] phloem_contributors.append(axis.roots) # compute total transpiration at t_inf axis.Total_Transpiration = 0.0 # mmol s-1 total_green_area = 0.0 # m2 for phytomer in axis.phytomers: for organ in (phytomer.chaff, phytomer.peduncle, phytomer.lamina, phytomer.internode, phytomer.sheath): if organ is not None: for element in (organ.exposed_element, organ.enclosed_element): if element is not None and element.green_area > 0: element.Transpiration = element.calculate_Total_Transpiration(element.Tr, element.green_area) axis.Total_Transpiration += (element.Transpiration * element.nb_replications) total_green_area += (element.green_area * element.nb_replications) # Compute the regulating factor of root exports by shoot transpiration axis.roots.regul_transpiration = axis.roots.calculate_regul_transpiration(axis.Total_Transpiration) # compute the flows from/to the roots to/from photosynthetic organs axis.roots.Uptake_Nitrates, axis.roots.HATS_LATS = axis.roots.calculate_Uptake_Nitrates(soil.Conc_Nitrates_Soil, axis.roots.nitrates, axis.roots.sucrose, soil.T_effect_Vmax) soil_contributors.append((axis.roots.Uptake_Nitrates, plant.index)) #: TODO TEMP!!! axis.roots.R_Nnit_upt = self.respiration_model.RespirationModel.R_Nnit_upt(axis.roots.Uptake_Nitrates, axis.roots.sucrose) axis.roots.Export_Nitrates = axis.roots.calculate_Export_Nitrates(axis.roots.nitrates, axis.roots.regul_transpiration) axis.roots.Export_Amino_Acids = axis.roots.calculate_Export_Amino_Acids(axis.roots.amino_acids, axis.roots.regul_transpiration) axis.roots.Export_cytokinins = axis.roots.calculate_Export_cytokinins(axis.roots.cytokinins, axis.roots.regul_transpiration) # compute the derivative of each photosynthetic organ element compartment for phytomer in axis.phytomers: # Hidden zone hiddenzone = phytomer.hiddenzone if phytomer.hiddenzone is not None: if hiddenzone.mstruct == 0: continue hiddenzone.sucrose = y[self.initial_conditions_mapping[hiddenzone]['sucrose']] hiddenzone.fructan = y[self.initial_conditions_mapping[hiddenzone]['fructan']] hiddenzone.amino_acids = y[self.initial_conditions_mapping[hiddenzone]['amino_acids']] hiddenzone.proteins = y[self.initial_conditions_mapping[hiddenzone]['proteins']] phloem_contributors.append(hiddenzone) hiddenzone_Loading_Sucrose_contribution = 0 hiddenzone_Loading_Amino_Acids_contribution = 0 for organ in (phytomer.chaff, phytomer.peduncle, phytomer.lamina, phytomer.internode, phytomer.sheath): if organ is None: continue for element in (organ.exposed_element, organ.enclosed_element): if element is None or element.green_area <= 0.25E-6 or element.mstruct <= 0.0: continue element.starch = y[self.initial_conditions_mapping[element]['starch']] element.sucrose = y[self.initial_conditions_mapping[element]['sucrose']] element.triosesP = y[self.initial_conditions_mapping[element]['triosesP']] element.fructan = y[self.initial_conditions_mapping[element]['fructan']] element.nitrates = y[self.initial_conditions_mapping[element]['nitrates']] element.amino_acids = y[self.initial_conditions_mapping[element]['amino_acids']] element.proteins = y[self.initial_conditions_mapping[element]['proteins']] element.cytokinins = y[self.initial_conditions_mapping[element]['cytokinins']] # intermediate variables element.Photosynthesis = element.calculate_total_Photosynthesis(element.Ag, element.green_area) # flows if element.is_growing: #: Export of sucrose and amino acids towards the HZ. Several growing elements might export toward the HZ at the same time (leaf and internode) element.Loading_Sucrose = element.calculate_export_sucrose(element.sucrose, hiddenzone.sucrose, hiddenzone.mstruct, plant.T_effect_conductivity) hiddenzone_Loading_Sucrose_contribution += element.Loading_Sucrose element.Loading_Amino_Acids = element.calculate_Export_Amino_Acids(element.amino_acids, hiddenzone.amino_acids, hiddenzone.mstruct, plant.T_effect_conductivity) hiddenzone_Loading_Amino_Acids_contribution += element.Loading_Amino_Acids else: #: Loading of sucrose and amino acids towards the phloem phloem_contributors.append(element) element.Loading_Sucrose = element.calculate_Loading_Sucrose(element.sucrose, axis.phloem.sucrose, axis.mstruct, plant.T_effect_conductivity) element.Loading_Amino_Acids = element.calculate_Loading_Amino_Acids(element.amino_acids, axis.phloem.amino_acids, axis.mstruct, plant.T_effect_conductivity) element.Regul_S_Fructan = element.calculate_Regul_S_Fructan(element.Loading_Sucrose) element.S_Fructan = element.calculate_S_Fructan(element.sucrose, element.Regul_S_Fructan, plant.T_effect_Vmax) element.D_Fructan = element.calculate_D_Fructan(element.sucrose, element.fructan, plant.T_effect_Vmax) element.S_Starch = element.calculate_S_Starch(element.triosesP, plant.T_effect_Vmax) element.D_Starch = element.calculate_D_Starch(element.starch, plant.T_effect_Vmax) element.S_Sucrose = element.calculate_S_Sucrose(element.triosesP, plant.T_effect_Vmax) element.R_phloem_loading, element.Loading_Sucrose = self.respiration_model.RespirationModel.R_phloem(element.Loading_Sucrose, element.mstruct * element.__class__.PARAMETERS.ALPHA) element.Nitrates_import = element.calculate_Nitrates_import(axis.roots.Export_Nitrates, element.Transpiration, axis.Total_Transpiration) element.Amino_Acids_import = element.calculate_Amino_Acids_import(axis.roots.Export_Amino_Acids, element.Transpiration, axis.Total_Transpiration) element.S_Amino_Acids = element.calculate_S_amino_acids(element.nitrates, element.triosesP, plant.T_effect_Vmax) element.R_Nnit_red, element.S_Amino_Acids = self.respiration_model.RespirationModel.R_Nnit_red(element.S_Amino_Acids, element.sucrose, element.mstruct * element.__class__.PARAMETERS.ALPHA) element.S_Proteins = element.calculate_S_proteins(element.amino_acids, plant.T_effect_Vmax) element.D_Proteins = element.calculate_D_Proteins(element.proteins, element.cytokinins, plant.T_effect_Vmax) element.cytokinins_import = element.calculate_cytokinins_import(axis.roots.Export_cytokinins, element.Transpiration, axis.Total_Transpiration) element.D_cytokinins = element.calculate_D_cytokinins(element.cytokinins, plant.T_effect_Vmax) # compartments derivatives starch_derivative = element.calculate_starch_derivative(element.S_Starch, element.D_Starch) element.R_residual = self.respiration_model.RespirationModel.R_residual(element.sucrose, element.mstruct * element.__class__.PARAMETERS.ALPHA, element.Total_Organic_Nitrogen, element.Ts) element.sum_respi = element.R_phloem_loading + element.R_Nnit_red + element.R_residual sum_respi_shoot += element.sum_respi * element.nb_replications sucrose_derivative = element.calculate_sucrose_derivative(element.S_Sucrose, element.D_Starch, element.Loading_Sucrose, element.S_Fructan, element.D_Fructan, element.sum_respi) triosesP_derivative = element.calculate_triosesP_derivative(element.Photosynthesis, element.S_Sucrose, element.S_Starch, element.S_Amino_Acids) fructan_derivative = element.calculate_fructan_derivative(element.S_Fructan, element.D_Fructan) nitrates_derivative = element.calculate_nitrates_derivative(element.Nitrates_import, element.S_Amino_Acids) amino_acids_derivative = element.calculate_amino_acids_derivative(element.Amino_Acids_import, element.S_Amino_Acids, element.S_Proteins, element.D_Proteins, element.Loading_Amino_Acids) proteins_derivative = element.calculate_proteins_derivative(element.S_Proteins, element.D_Proteins) cytokinins_derivative = element.calculate_cytokinins_derivative(element.cytokinins_import, element.D_cytokinins) y_derivatives[self.initial_conditions_mapping[element]['starch']] = starch_derivative y_derivatives[self.initial_conditions_mapping[element]['sucrose']] = sucrose_derivative y_derivatives[self.initial_conditions_mapping[element]['triosesP']] = triosesP_derivative y_derivatives[self.initial_conditions_mapping[element]['fructan']] = fructan_derivative y_derivatives[self.initial_conditions_mapping[element]['nitrates']] = nitrates_derivative y_derivatives[self.initial_conditions_mapping[element]['amino_acids']] = amino_acids_derivative y_derivatives[self.initial_conditions_mapping[element]['proteins']] = proteins_derivative y_derivatives[self.initial_conditions_mapping[element]['cytokinins']] = cytokinins_derivative if phytomer.hiddenzone is not None: # Unloading of sucrose from phloem hiddenzone.Unloading_Sucrose = hiddenzone.calculate_Unloading_Sucrose(hiddenzone.sucrose, axis.phloem.sucrose, axis.mstruct, plant.T_effect_conductivity) # Unloading of AA from phloem hiddenzone.Unloading_Amino_Acids = hiddenzone.calculate_Unloading_Amino_Acids(hiddenzone.amino_acids, axis.phloem.amino_acids, axis.mstruct, plant.T_effect_conductivity) # Fructan synthesis Regul_Sfructanes = hiddenzone.calculate_Regul_S_Fructan(hiddenzone.Unloading_Sucrose) hiddenzone.S_Fructan = hiddenzone.calculate_S_Fructan(hiddenzone.sucrose, Regul_Sfructanes, plant.T_effect_Vmax) # Fructan degradation hiddenzone.D_Fructan = hiddenzone.calculate_D_Fructan(hiddenzone.sucrose, hiddenzone.fructan, plant.T_effect_Vmax) # Synthesis proteins hiddenzone.S_Proteins = hiddenzone.calculate_S_proteins(hiddenzone.amino_acids, plant.T_effect_Vmax) # Degradation proteins hiddenzone.D_Proteins = hiddenzone.calculate_D_Proteins(hiddenzone.proteins, plant.T_effect_Vmax) # Residual respiration hiddenzone.R_residual = self.respiration_model.RespirationModel.R_residual(hiddenzone.sucrose, hiddenzone.mstruct * hiddenzone.__class__.PARAMETERS.ALPHA, hiddenzone.Total_Organic_Nitrogen, plant.Tair) sum_respi_shoot += hiddenzone.R_residual * hiddenzone.nb_replications # compute the derivatives of the hidden zone y_derivatives[self.initial_conditions_mapping[hiddenzone]['sucrose']] = hiddenzone.calculate_sucrose_derivative(hiddenzone.Unloading_Sucrose, hiddenzone.S_Fructan, hiddenzone.D_Fructan, hiddenzone_Loading_Sucrose_contribution, hiddenzone.R_residual) y_derivatives[self.initial_conditions_mapping[hiddenzone]['amino_acids']] = hiddenzone.calculate_amino_acids_derivative(hiddenzone.Unloading_Amino_Acids, hiddenzone.S_Proteins, hiddenzone.D_Proteins, hiddenzone_Loading_Amino_Acids_contribution) y_derivatives[self.initial_conditions_mapping[hiddenzone]['fructan']] = hiddenzone.calculate_fructan_derivative(hiddenzone.S_Fructan, hiddenzone.D_Fructan) y_derivatives[self.initial_conditions_mapping[hiddenzone]['proteins']] = hiddenzone.calculate_proteins_derivative(hiddenzone.S_Proteins, hiddenzone.D_Proteins) if axis.grains is not None: phloem_contributors.append(axis.grains) # compute the derivative of each compartment of grains axis.grains.structure = y[self.initial_conditions_mapping[axis.grains]['structure']] axis.grains.starch = y[self.initial_conditions_mapping[axis.grains]['starch']] axis.grains.proteins = y[self.initial_conditions_mapping[axis.grains]['proteins']] axis.grains.age_from_flowering = y[self.initial_conditions_mapping[axis.grains]['age_from_flowering']] # intermediate variables T_effect_growth = axis.grains.calculate_temperature_effect_on_growth(plant.Tair) axis.grains.RGR_Structure = axis.grains.calculate_RGR_Structure(axis.phloem.sucrose, axis.mstruct, T_effect_growth) axis.grains.structural_dry_mass = axis.grains.calculate_structural_dry_mass(axis.grains.structure) # flows axis.grains.S_grain_structure = axis.grains.calculate_S_grain_structure(axis.grains.structure, axis.grains.RGR_Structure) axis.grains.S_grain_starch = axis.grains.calculate_S_grain_starch(axis.phloem.sucrose, axis.mstruct, plant.T_effect_Vmax) axis.grains.S_Proteins = axis.grains.calculate_S_proteins(axis.grains.S_grain_structure, axis.grains.S_grain_starch, axis.phloem.amino_acids, axis.phloem.sucrose, axis.grains.structural_dry_mass) # compartments derivatives axis.grains.R_grain_growth_struct, axis.grains.R_grain_growth_starch = self.respiration_model.RespirationModel.R_grain_growth(axis.grains.S_grain_structure, axis.grains.S_grain_starch, axis.grains.structural_dry_mass) sum_respi_shoot += axis.grains.R_grain_growth_struct + axis.grains.R_grain_growth_starch structure_derivative = axis.grains.calculate_structure_derivative(axis.grains.S_grain_structure, axis.grains.R_grain_growth_struct) starch_derivative = axis.grains.calculate_starch_derivative(axis.grains.S_grain_starch, axis.grains.structural_dry_mass, axis.grains.R_grain_growth_starch) proteins_derivative = axis.grains.calculate_proteins_derivative(axis.grains.S_Proteins) y_derivatives[self.initial_conditions_mapping[axis.grains]['structure']] = structure_derivative y_derivatives[self.initial_conditions_mapping[axis.grains]['starch']] = starch_derivative y_derivatives[self.initial_conditions_mapping[axis.grains]['proteins']] = proteins_derivative y_derivatives[self.initial_conditions_mapping[axis.grains]['age_from_flowering']] += (self.delta_t * T_effect_growth) #TODO: create a function # compute the derivative of each compartment of roots # flows axis.roots.Unloading_Sucrose = axis.roots.calculate_Unloading_Sucrose(axis.roots.sucrose, axis.phloem.sucrose, axis.mstruct, plant.T_effect_conductivity) axis.roots.Unloading_Amino_Acids = axis.roots.calculate_Unloading_Amino_Acids(axis.roots.Unloading_Sucrose, axis.phloem.sucrose, axis.phloem.amino_acids) axis.roots.S_Amino_Acids = axis.roots.calculate_S_amino_acids(axis.roots.nitrates, axis.roots.sucrose, soil.T_effect_Vmax) axis.roots.R_Nnit_red, axis.roots.S_Amino_Acids = self.respiration_model.RespirationModel.R_Nnit_red(axis.roots.S_Amino_Acids, axis.roots.sucrose, axis.roots.mstruct * model.Roots.PARAMETERS.ALPHA, root=True) axis.roots.C_exudation, axis.roots.N_exudation = axis.roots.calculate_exudation(axis.roots.Unloading_Sucrose, axis.roots.sucrose, axis.roots.amino_acids, axis.phloem.amino_acids) axis.roots.S_cytokinins = axis.roots.calculate_S_cytokinins(axis.roots.sucrose, axis.roots.nitrates, soil.T_effect_Vmax) # compartments derivatives axis.roots.R_residual = self.respiration_model.RespirationModel.R_residual(axis.roots.sucrose, axis.roots.mstruct * model.Roots.PARAMETERS.ALPHA, axis.roots.Total_Organic_Nitrogen, soil.Tsoil) axis.roots.sum_respi = axis.roots.R_Nnit_upt + axis.roots.R_Nnit_red + axis.roots.R_residual sucrose_derivative = axis.roots.calculate_sucrose_derivative(axis.roots.Unloading_Sucrose, axis.roots.S_Amino_Acids, axis.roots.C_exudation, axis.roots.sum_respi) nitrates_derivative = axis.roots.calculate_nitrates_derivative(axis.roots.Uptake_Nitrates, axis.roots.Export_Nitrates, axis.roots.S_Amino_Acids) amino_acids_derivative = axis.roots.calculate_amino_acids_derivative(axis.roots.Unloading_Amino_Acids, axis.roots.S_Amino_Acids, axis.roots.Export_Amino_Acids, axis.roots.N_exudation) cytokinins_derivative = axis.roots.calculate_cytokinins_derivative(axis.roots.S_cytokinins, axis.roots.Export_cytokinins) y_derivatives[self.initial_conditions_mapping[axis.roots]['sucrose']] = sucrose_derivative y_derivatives[self.initial_conditions_mapping[axis.roots]['nitrates']] = nitrates_derivative y_derivatives[self.initial_conditions_mapping[axis.roots]['amino_acids']] = amino_acids_derivative y_derivatives[self.initial_conditions_mapping[axis.roots]['cytokinins']] = cytokinins_derivative # compute the derivative of each compartment of phloem sucrose_phloem_derivative = axis.phloem.calculate_sucrose_derivative(phloem_contributors) amino_acids_phloem_derivative = axis.phloem.calculate_amino_acids_derivative(phloem_contributors) y_derivatives[self.initial_conditions_mapping[axis.phloem]['sucrose']] = sucrose_phloem_derivative y_derivatives[self.initial_conditions_mapping[axis.phloem]['amino_acids']] = amino_acids_phloem_derivative # compute the derivative of each compartment of axis C_exudated = axis.calculate_C_exudated(axis.roots.C_exudation, axis.roots.N_exudation, axis.roots.mstruct) y_derivatives[self.initial_conditions_mapping[axis]['C_exudated']] += C_exudated y_derivatives[self.initial_conditions_mapping[axis]['sum_respi_roots']] += axis.roots.sum_respi y_derivatives[self.initial_conditions_mapping[axis]['sum_respi_shoot']] += sum_respi_shoot # compute the derivative of each compartment of soil soil.mineralisation = soil.calculate_mineralisation(soil.T_effect_Vmax) y_derivatives[self.initial_conditions_mapping[soil]['nitrates']] = soil.calculate_nitrates_derivative(soil.mineralisation, soil_contributors, self.culm_density, soil.constant_Conc_Nitrates) if self.show_progressbar: self.progressbar.update(t) derivatives_logger = logging.getLogger('cnwheat.derivatives') if logger.isEnabledFor(logging.DEBUG) and derivatives_logger.isEnabledFor(logging.DEBUG): self._log_compartments(t_abs, y_derivatives, Simulation.LOGGERS_NAMES['derivatives']) return y_derivatives