Source code for openalea.cnwgrass.morphogenesis.simulation

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

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

import warnings
import copy

import pandas as pd
import numpy as np

from openalea.cnwgrass.morphogenesis import model

"""
    morphogenesis.simulation
    ~~~~~~~~~~~~~~~~~~

    The module :mod:`morphogenesis.simulation` is the front-end to run the Morphogenesis model.

"""


[docs] class SimulationError(Exception): pass
[docs] class SimulationRunError(SimulationError): pass
[docs] class Simulation(object): """The Simulation class allows to initialize and run a simulation. """ def __init__(self, delta_t=1, hydraulics=False, optimal_growth_option=False, update_parameters=None): """ :param int delta_t: the delta t of the simulation (in seconds) :param bool hydraulics: if True the model will assume the coupling to the turgor-driven growth model :param bool optimal_growth_option: if True the model will assume optimal growth conditions :param None or dict update_parameters: if a dict is provided, the specified parameters in keys will be updated """ #: `inputs` is a dictionary of dictionaries: #: {'hiddenzone': {(plant_index, axis_label, metamer_index): {hiddenzone_input_name: hiddenzone_input_value, ...}, ...}, #: 'elements': {(plant_index, axis_label, metamer_index, organ_label, element): {element_input_name: element_input_value, ...}, ...}, #: 'axes': {(plant_index, axis_label): {axis_input_name: axis_input_value, ...}, ...}, #: 'sheath_internode_lengths': {(plant_index, axis_label, metamer_index): {'sheath': [list of sheath length belonging to the phytomer], #: 'cumulated_internode': [list of internode lengths cumulated from phytomer 1 to n]}, ...}} self.inputs = {} #: `outputs` is a dictionary of dictionaries: #: {'hiddenzone': {(plant_index, axis_label, metamer_index): {hiddenzone_input_name: hiddenzone_input_value, ...}, ...}, #: 'elements': {(plant_index, axis_label, metamer_index, organ_label, element): {element_output_name: element_output_value, ...}, ...}, #: 'axes': {(plant_index, axis_label): {axis_output_name: axis_output_value, ...}, ...}} self.outputs = {} #: the delta t of the simulation (in seconds) self.delta_t = delta_t #: Checks whether the Hydraulic version should be used self.hydraulics = hydraulics if not self.hydraulics: self.model = model.MorphogenesisModel() else: self.model = model.MorphogenesisModelHydraulics() self.hiddenzone_init = self.model.hiddenzone_init self.element_init = self.model.element_init #: The inputs and outputs of Morphogenesis at each scale. self.axis_inputs = self.model.axis_inputs self.axis_outputs = self.model.axis_outputs self.axis_inputs_outputs = sorted(set(self.axis_inputs + self.axis_outputs)) self.hiddenzone_inputs = self.model.hiddenzone_inputs self.hiddenzone_outputs = self.model.hiddenzone_outputs self.hiddenzone_inputs_outputs = sorted(set(self.hiddenzone_inputs + self.hiddenzone_outputs)) self.element_inputs = self.model.element_inputs self.element_outputs = self.model.element_outputs self.element_inputs_outputs = sorted(set(self.element_inputs + self.element_outputs)) #: Optimal growth option self.optimal_growth_option = optimal_growth_option #: Update parameters if specified if update_parameters: for key, value in update_parameters.items(): if hasattr(self.model.parameters, key): setattr(self.model.parameters, key, value) else: warnings.warn(f"Parameter '{key}' is not defined in class self.model.parameters.")
[docs] def initialize(self, inputs): """ Initialize :attr:`inputs` from `inputs`. :param dict inputs: must be a dictionary with the same structure as :attr:`inputs`. """ self.inputs.clear() self.inputs.update(inputs)
[docs] def run(self, Tair, Tsoil, Zsowing=0.025): """ Run the simulation. :param float Tair: Air temperature at t (degree Celsius) :param float Tsoil: Soil temperature at t (degree Celsius) :param float Zsowing: Sowing depth (m) """ # Copy the inputs into the output dict self.outputs.update({inputs_type: copy.deepcopy(all_inputs) for inputs_type, all_inputs in self.inputs.items() if inputs_type in {'hiddenzone', 'elements', 'axes', 'sheath_internode_lengths'}}) # Hidden zones all_hiddenzone_inputs = self.inputs['hiddenzone'] all_hiddenzone_outputs = self.outputs['hiddenzone'] # Elements all_element_inputs = self.inputs['elements'] all_element_outputs = self.outputs['elements'] # Axes all_axes_inputs = self.inputs['axes'] all_axes_outputs = self.outputs['axes'] # sheath and internode lengths all_sheath_internode_lengths = self.inputs['sheath_internode_lengths'] # Ligule heights all_ligule_height_df = pd.DataFrame(columns=self.model.ligule_topology_columns) # -- Beginning of computations # ----------------------------- # ---------- SAM -------------- # ----------------------------- for axis_id, axis_inputs in sorted(all_axes_inputs.items()): curr_axis_outputs = all_axes_outputs[axis_id] nb_leaves = curr_axis_outputs['nb_leaves'] # height of the SAM below_internode_lengths = all_sheath_internode_lengths[axis_id][nb_leaves]['cumulated_internode'] SAM_height = self.model.calculate_cumulated_internode_length(below_internode_lengths) curr_axis_outputs['SAM_height'] = SAM_height # SAM temperature growth_temperature = self.model.calculate_growing_temperature(Tair, Tsoil, SAM_height, Zsowing) curr_axis_outputs['SAM_temperature'] = growth_temperature # temperature-compensated time curr_axis_outputs['delta_teq'] = self.model.calculate_time_equivalent_Tref(growth_temperature, self.delta_t) curr_axis_outputs['delta_teq_roots'] = self.model.calculate_time_equivalent_Tref(Tsoil, self.delta_t) # cumulated thermal time curr_axis_outputs['sum_TT'] = self.model.calculate_cumulated_thermal_time(curr_axis_outputs['sum_TT'], growth_temperature, curr_axis_outputs['delta_teq']) # update SAM status, leaf number and init_leaf, curr_axis_outputs['nb_leaves'], curr_axis_outputs['status'], curr_axis_outputs['teq_since_primordium'] = self.model.calculate_SAM_primodia(axis_inputs['status'], curr_axis_outputs['teq_since_primordium'], curr_axis_outputs['delta_teq'], nb_leaves, curr_axis_outputs['cohort']) # GA production curr_axis_outputs['GA'] = self.model.calculate_SAM_GA(curr_axis_outputs['status'], curr_axis_outputs['teq_since_primordium']) # hiddenzone initiation for i in range(0, init_leaf): # Initialise hiddenzone hiddenzone_id = axis_id + tuple([1 + i + curr_axis_outputs['nb_leaves'] - init_leaf]) # TODO: peut etre simplifié tant que 'calculate_SAM_status' renvoie 1 erreur si init_leaf>1 new_hiddenzone = self.hiddenzone_init.__dict__.copy() self.outputs['hiddenzone'][hiddenzone_id] = new_hiddenzone # Ligule height all_ligule_height_df = self.model.calculate_ligule_height(all_sheath_internode_lengths[axis_id], all_element_inputs, axis_id, all_ligule_height_df) self.outputs['axes'][axis_id] = curr_axis_outputs # ----------------------------- # ---------- Elements --------- # ----------------------------- for element_id, element_inputs in sorted(all_element_inputs.items()): # Update element's age, only used by ADEL to adapt element's geometry (so far lamina curvature, could be used to adapt stem geometry too) # TODO : the calculation of element's age must be extracted from morphogenesis as it is run even for mature leaves curr_age = all_element_inputs[element_id]['age'] curr_age_teq = all_element_inputs[element_id]['age_teq'] axis_id = element_id[:2] curr_axis_outputs = all_axes_outputs[axis_id] self.outputs['elements'][element_id]['age'] = self.model.calculate_cumulated_thermal_time(curr_age, curr_axis_outputs['SAM_temperature'], curr_axis_outputs['delta_teq']) self.outputs['elements'][element_id]['age_teq'] = self.model.calculate_element_age_teq(curr_age_teq, curr_axis_outputs['delta_teq']) # ----------------------------- # ---------- Hiddenzones ------ # ----------------------------- for hiddenzone_id, hiddenzone_inputs in sorted(all_hiddenzone_inputs.items()): axe_label = hiddenzone_id[1] axis_id = hiddenzone_id[:2] phytomer_id = hiddenzone_id[2] if phytomer_id == 0: #: coleoptile continue # Get xylem water potential if hydraulics xylem_water_potential = all_axes_inputs[axis_id].get('xylem_water_potential') # Set at None if hydraulics is False hz_turgor_water_potential = hiddenzone_inputs.get('turgor_water_potential') # Set at None if hydraulics is False #: Tillers: in this version tillers functioning is replicated from corresponding elements of MS if axe_label != 'MS': tiller_to_MS_phytomer_id = tuple([axis_id[0], 'MS', all_axes_outputs[axis_id]['cohort'] + phytomer_id - 1]) if tiller_to_MS_phytomer_id in all_hiddenzone_outputs.keys(): self.outputs['hiddenzone'][hiddenzone_id] = all_hiddenzone_outputs[tiller_to_MS_phytomer_id] if all_hiddenzone_outputs[tiller_to_MS_phytomer_id]['leaf_is_emerged']: # Lamina tiller_to_MS_lamina_id = tiller_to_MS_phytomer_id + tuple(['blade', 'LeafElement1']) tiller_lamina_id = hiddenzone_id + tuple(['blade', 'LeafElement1']) if tiller_to_MS_lamina_id in all_element_outputs.keys(): self.outputs['elements'][tiller_lamina_id] = all_element_outputs[tiller_to_MS_lamina_id] # else: # warnings.warn('No leaf found on main stem for tiller {}.'.format(tiller_to_MS_lamina_id)) # Emerged Sheath tiller_to_MS_emerged_sheath_id = tiller_to_MS_phytomer_id + tuple(['sheath', 'StemElement']) tiller_emerged_sheath_id = hiddenzone_id + tuple(['sheath', 'StemElement']) if tiller_to_MS_emerged_sheath_id in all_element_outputs.keys(): self.outputs['elements'][tiller_emerged_sheath_id] = all_element_outputs[tiller_to_MS_emerged_sheath_id] # else: # warnings.warn('No emerged sheath found on main stem for tiller {}.'.format(tiller_to_MS_emerged_sheath_id)) # Enclosed Sheath tiller_to_MS_enclosed_sheath_id = tiller_to_MS_phytomer_id + tuple(['sheath', 'HiddenElement']) tiller_enclosed_sheath_id = hiddenzone_id + tuple(['sheath', 'HiddenElement']) if tiller_to_MS_enclosed_sheath_id in all_element_outputs.keys(): self.outputs['elements'][tiller_enclosed_sheath_id] = all_element_outputs[tiller_to_MS_enclosed_sheath_id] # else: # warnings.warn('No enclosed sheath found on main stem for tiller {}.'.format(tiller_to_MS_enclosed_sheath_id)) # Emerged internode tiller_to_MS_emerged_internode_id = tiller_to_MS_phytomer_id + tuple(['internode', 'StemElement']) tiller_emerged_internode_id = hiddenzone_id + tuple(['internode', 'StemElement']) if tiller_to_MS_emerged_internode_id in all_element_outputs.keys(): self.outputs['elements'][tiller_emerged_internode_id] = all_element_outputs[tiller_to_MS_emerged_internode_id] # else: # warnings.warn('No emerged internode found on main stem for tiller {}.'.format(tiller_to_MS_emerged_internode_id)) # Enclosed internode tiller_to_MS_enclosed_internode_id = tiller_to_MS_phytomer_id + tuple(['internode', 'HiddenElement']) tiller_enclosed_internode_id = hiddenzone_id + tuple(['internode', 'HiddenElement']) if tiller_to_MS_enclosed_internode_id in all_element_outputs.keys(): self.outputs['elements'][tiller_enclosed_internode_id] = all_element_outputs[tiller_to_MS_enclosed_internode_id] # else: # warnings.warn('No enclosed internode found on main stem for tiller {}.'.format(tiller_to_MS_enclosed_internode_id)) # else: # warnings.warn('No main stem found for tiller {}.'.format(tiller_to_MS_phytomer_id)) #: Main Stem else: curr_hiddenzone_outputs = all_hiddenzone_outputs[hiddenzone_id] curr_axis_outputs = all_axes_outputs[axis_id] curr_hiddenzone_outputs['hiddenzone_age'] += curr_axis_outputs['delta_teq'] if self.hydraulics: curr_hiddenzone_outputs['init_leaf_L'] = hiddenzone_inputs['leaf_L'] # leaf length before Hydraulics sub-model hidden_sheath_id = hiddenzone_id + tuple(['sheath', 'HiddenElement']) visible_sheath_id = hiddenzone_id + tuple(['sheath', 'StemElement']) hidden_lamina_id = hiddenzone_id + tuple(['blade', 'HiddenElement']) hidden_internode_id = hiddenzone_id + tuple(['internode', 'HiddenElement']) visible_internode_id = hiddenzone_id + tuple(['internode', 'StemElement']) next_hiddenzone_id = tuple(list(hiddenzone_id[:2]) + [hiddenzone_id[2] + 1]) # Found previous hidden zone prev_hiddenzone_id = tuple(list(axis_id) + [phytomer_id - 1]) if prev_hiddenzone_id in all_hiddenzone_inputs: prev_leaf_emerged = all_hiddenzone_inputs[prev_hiddenzone_id]['leaf_is_emerged'] prev_hiddenzone_inputs = all_hiddenzone_inputs[prev_hiddenzone_id] else: prev_hiddenzone_inputs = None prev_leaf_emerged = True prev_leaf2_hiddenzone_id = tuple(list(axis_id) + [phytomer_id - 2]) if prev_leaf2_hiddenzone_id in all_hiddenzone_inputs: prev_leaf2_emerged = all_hiddenzone_inputs[prev_leaf2_hiddenzone_id]['leaf_is_emerged'] else: prev_leaf2_emerged = True # Cumulated length of internodes up to the hidden zone below_internode_lengths = all_sheath_internode_lengths[axis_id][phytomer_id]['cumulated_internode'] bottom_hiddenzone_height = self.model.calculate_cumulated_internode_length(below_internode_lengths) # Distance between the bottom of the hiddenzone and the highest previous ligule leaf_pseudostem_length = self.model.calculate_leaf_pseudostem_length(all_ligule_height_df[all_ligule_height_df['axis_id'] == axis_id], bottom_hiddenzone_height, phytomer_id) curr_hiddenzone_outputs['leaf_pseudostem_length'] = leaf_pseudostem_length curr_hiddenzone_outputs['delta_leaf_pseudostem_length'] = leaf_pseudostem_length - hiddenzone_inputs['leaf_pseudostem_length'] # Variable used in growth # Calculate the internode pseudostem length curr_internode_L = hiddenzone_inputs['internode_L'] internode_distance_to_emerge = self.model.calculate_internode_distance_to_emerge(all_ligule_height_df[all_ligule_height_df['axis_id'] == axis_id], bottom_hiddenzone_height, phytomer_id, curr_internode_L) curr_hiddenzone_outputs['internode_distance_to_emerge'] = internode_distance_to_emerge curr_hiddenzone_outputs['delta_internode_distance_to_emerge'] = internode_distance_to_emerge - hiddenzone_inputs['internode_distance_to_emerge'] # Variable used in growth # In case leaf is already mature but internode is growing, we update sheath visible and hidden lengths. if not curr_hiddenzone_outputs['leaf_is_growing'] and curr_hiddenzone_outputs['leaf_is_emerged']: if hidden_sheath_id in self.inputs['elements'].keys(): sheath_hidden_length = self.inputs['elements'][hidden_sheath_id]['length'] else: sheath_hidden_length = 0. new_sheath = self.element_init.__dict__.copy() self.outputs['elements'][hidden_sheath_id] = new_sheath if visible_sheath_id not in self.outputs['elements'].keys(): new_sheath = self.element_init.__dict__.copy() self.outputs['elements'][visible_sheath_id] = new_sheath total_sheath_L = sheath_hidden_length + self.outputs['elements'][visible_sheath_id]['length'] updated_sheath_hidden_length = min(total_sheath_L, leaf_pseudostem_length) updated_sheath_visible_length = max(0, total_sheath_L - updated_sheath_hidden_length) self.outputs['elements'][hidden_sheath_id]['length'] = updated_sheath_hidden_length self.outputs['elements'][visible_sheath_id]['length'] = updated_sheath_visible_length ################### # Leaf elongation # ################### if curr_hiddenzone_outputs['leaf_is_growing']: if leaf_pseudostem_length < 0: warnings.warn('Pseudostem length of {} decreased while leaf growing.'.format(hiddenzone_id)) if prev_leaf2_emerged and not curr_hiddenzone_outputs['leaf_is_emerged'] and phytomer_id != 1: prev_leaf2_lamina_id = tuple(list(axis_id) + [phytomer_id - 2] + ['blade'] + ['LeafElement1']) # Two alternatives to find the time of leaf n-2 emergence: either from the age (time equivalent) of lamina n-2 or from the pseudo_age of hz n-1 if prev_leaf2_lamina_id in all_element_inputs: time_prev_leaf2_emergence = all_element_inputs[prev_leaf2_lamina_id]['age_teq'] elif prev_hiddenzone_inputs is not None: time_prev_leaf2_emergence = prev_hiddenzone_inputs['leaf_pseudo_age'] else: raise Exception("Can't find previous hz for current hz {}".format(hiddenzone_id)) curr_hiddenzone_outputs['mean_conc_sucrose'] = self.model.calculate_mean_conc_sucrose(hiddenzone_inputs['mean_conc_sucrose'], time_prev_leaf2_emergence, curr_axis_outputs['delta_teq'], hiddenzone_inputs['sucrose'], hiddenzone_inputs['mstruct']) if not prev_leaf_emerged and phytomer_id != 1: #: Before the emergence of the previous leaf. Exponential-like elongation. Leaf1 starts directly in phase 2 # delta leaf length delta_leaf_L = self.model.calculate_deltaL_preE(hiddenzone_inputs['sucrose'], hiddenzone_inputs['leaf_L'], hiddenzone_inputs['amino_acids'], hiddenzone_inputs['mstruct'], curr_axis_outputs['delta_teq'], phytomer_id, self.optimal_growth_option, xylem_water_potential) leaf_L = hiddenzone_inputs['leaf_L'] + delta_leaf_L curr_hiddenzone_outputs['ratio_DZ'] = 1 else: #: After the emergence of the previous leaf. # Leaf length leaf_pseudo_age = self.model.calculate_leaf_pseudo_age(hiddenzone_inputs['leaf_pseudo_age'], curr_axis_outputs['delta_teq'], hz_turgor_water_potential) curr_hiddenzone_outputs['leaf_pseudo_age'] = leaf_pseudo_age curr_hiddenzone_outputs['delta_leaf_pseudo_age'] = leaf_pseudo_age - hiddenzone_inputs['leaf_pseudo_age'] if not self.hydraulics: delta_leaf_L = self.model.calculate_deltaL_postE(hiddenzone_inputs['leaf_pseudo_age'], leaf_pseudo_age, hiddenzone_inputs['leaf_L'], hiddenzone_inputs['leaf_Lmax_em'], hiddenzone_inputs['sucrose'], hiddenzone_inputs['amino_acids'], hiddenzone_inputs['mstruct'], phytomer_id, self.optimal_growth_option) leaf_L = hiddenzone_inputs['leaf_L'] + delta_leaf_L # Update leaf_Lmax. Subsequently, lamina_Lmax and sheath_Lmax will be updated depending on each element status (growing or mature) curr_hiddenzone_outputs['leaf_Lmax'] = self.model.calculate_update_leaf_Lmax(hiddenzone_inputs['leaf_Lmax_em'], leaf_L, leaf_pseudo_age, phytomer_id) # Ratio (mass) of Division Zone in the hiddenzone curr_hiddenzone_outputs['ratio_DZ'] = self.model.calculate_ratio_DZ_postE(leaf_L, curr_hiddenzone_outputs['leaf_Lmax'], leaf_pseudostem_length) else: delta_leaf_L = 0 leaf_L = hiddenzone_inputs['leaf_L'] + delta_leaf_L # Ratio (mass) of Division Zone in the hiddenzone curr_hiddenzone_outputs['ratio_DZ'] = self.model.calculate_ratio_DZ_postE(hiddenzone_inputs['leaf_L'], leaf_pseudo_age, leaf_pseudostem_length) #: Lamina : sheath distinction lamina_id = hiddenzone_id + tuple(['blade', 'LeafElement1']) ##: Lamina has not emerged if not curr_hiddenzone_outputs['leaf_is_emerged']: #: Test of leaf emergence against distance to leaf emergence. Assumes that a leaf cannot emerge before the previous one # TODO: besoin correction pour savoir a quel pas de temps exact?? curr_hiddenzone_outputs['leaf_is_emerged'] = self.model.calculate_leaf_emergence(hiddenzone_inputs['leaf_L'], leaf_pseudostem_length) if curr_hiddenzone_outputs['leaf_is_emerged']: # Initialise lamina outputs print('leaf emergence', hiddenzone_id) new_lamina = self.element_init.__dict__.copy() self.outputs['elements'][lamina_id] = new_lamina # Length of the HZ at the emergence curr_hiddenzone_outputs['length_hz_En'] = curr_hiddenzone_outputs['leaf_L'] curr_lamina_outputs = all_element_outputs[lamina_id] # Length of emerged lamina lamina_L = self.model.calculate_lamina_L(leaf_L, leaf_pseudostem_length, curr_hiddenzone_outputs['lamina_Lmax']) curr_lamina_outputs['length'] = lamina_L # Update of lamina outputs self.outputs['elements'][lamina_id] = curr_lamina_outputs # Initialise variables for the next hidden zone as its previous leaf has now emerged if next_hiddenzone_id in all_hiddenzone_inputs: next_hiddenzone_inputs = all_hiddenzone_inputs[next_hiddenzone_id] next_hiddenzone_outputs = all_hiddenzone_outputs[next_hiddenzone_id] if not self.hydraulics: next_hiddenzone_outputs['leaf_Lmax'] = self.model.calculate_leaf_Lmax(next_hiddenzone_inputs['leaf_L'], phytomer_id+1) #: Final leaf length next_hiddenzone_outputs['leaf_Lmax_em'] = next_hiddenzone_outputs['leaf_Lmax'] #: Final leaf length at Ln-1 em sheath_lamina_ratio = self.model.calculate_SL_ratio(next_hiddenzone_id[2]) #: Sheath:Lamina final length ratio next_hiddenzone_outputs['lamina_Lmax'] = self.model.calculate_lamina_Lmax(next_hiddenzone_outputs['leaf_Lmax'], sheath_lamina_ratio) #: Final lamina length next_hiddenzone_outputs['sheath_Lmax'] = self.model.calculate_sheath_Lmax(next_hiddenzone_outputs['leaf_Lmax'], next_hiddenzone_outputs['lamina_Lmax']) #: Final sheath length else: next_hiddenzone_outputs['lamina_Lmax'] = self.model.calculate_lamina_Lmax(hiddenzone_id[2]) #: Maximal lamina length next_hiddenzone_outputs['leaf_pseudo_age'] = 0 #: Pseudo age of the leaf since beginning of automate growth (s) self.outputs['hiddenzone'][next_hiddenzone_id] = next_hiddenzone_outputs else: warnings.warn('No next hidden zone found for hiddenzone {}.'.format(hiddenzone_id)) # Define lamina_Wmax and structural weight of the current sheath and lamina if not self.hydraulics: curr_hiddenzone_outputs['leaf_Wmax'] = self.outputs['elements'][lamina_id]['Wmax'] = self.model.calculate_leaf_Wmax(curr_hiddenzone_outputs['lamina_Lmax'], hiddenzone_id[2], curr_hiddenzone_outputs['mean_conc_sucrose'], self.optimal_growth_option) curr_hiddenzone_outputs['SSLW'] = self.model.calculate_SSLW(hiddenzone_id[2], curr_hiddenzone_outputs['mean_conc_sucrose'], self.optimal_growth_option) curr_hiddenzone_outputs['LSSW'] = self.model.calculate_LSSW(hiddenzone_id[2], curr_hiddenzone_outputs['mean_conc_sucrose'], self.optimal_growth_option) #: Test end of elongation when a leaf stops elongation inside the pseudostem (extreme stress) if not self.hydraulics and leaf_L >= curr_hiddenzone_outputs['leaf_Lmax']: # Update lamina_Lmax and sheath_Lmax based on updates of leaf_Lmax sheath_lamina_ratio = self.model.calculate_SL_ratio(hiddenzone_id[2]) # Initialise hidden lamina new_hidden_lamina = self.element_init.__dict__.copy() self.outputs['elements'][hidden_lamina_id] = new_hidden_lamina self.outputs['elements'][hidden_lamina_id]['length'] = curr_hiddenzone_outputs['lamina_Lmax'] = self.model.calculate_lamina_Lmax(curr_hiddenzone_outputs['leaf_Lmax'], sheath_lamina_ratio) self.outputs['elements'][hidden_lamina_id]['Wmax'] = curr_hiddenzone_outputs['leaf_Wmax'] self.outputs['elements'][hidden_lamina_id]['is_growing'] = False # Initialise hidden sheath outputs new_sheath = self.element_init.__dict__.copy() self.outputs['elements'][hidden_sheath_id] = new_sheath self.outputs['elements'][hidden_sheath_id]['length'] = curr_hiddenzone_outputs['sheath_Lmax'] = self.model.calculate_sheath_Lmax(curr_hiddenzone_outputs['leaf_Lmax'], curr_hiddenzone_outputs['lamina_Lmax']) self.outputs['elements'][hidden_sheath_id]['is_growing'] = False # End of leaf growth curr_hiddenzone_outputs['leaf_is_growing'] = False curr_hiddenzone_outputs['leaf_is_remobilizing'] = True else: transition_lamina_to_sheath = False end_leaf_elongation = False if self.hydraulics: # hydraulics -> the whole leaf is considered if curr_hiddenzone_outputs['leaf_pseudo_age'] >= self.model.parameters.te: end_leaf_elongation = True else: #: Lamina has emerged and is growing if all_element_inputs[lamina_id]['is_growing']: curr_lamina_outputs = all_element_outputs[lamina_id] # Update lamina_Lmax and sheath_Lmax based on updates of leaf_Lmax sheath_lamina_ratio = self.model.calculate_SL_ratio(hiddenzone_id[2]) curr_hiddenzone_outputs['lamina_Lmax'] = self.model.calculate_lamina_Lmax(curr_hiddenzone_outputs['leaf_Lmax'], sheath_lamina_ratio) curr_hiddenzone_outputs['sheath_Lmax'] = self.model.calculate_sheath_Lmax(curr_hiddenzone_outputs['leaf_Lmax'], curr_hiddenzone_outputs['lamina_Lmax']) # Length of emerged lamina lamina_L = self.model.calculate_lamina_L(leaf_L, leaf_pseudostem_length, curr_hiddenzone_outputs['lamina_Lmax']) curr_lamina_outputs['length'] = lamina_L # Update of lamina outputs self.outputs['elements'][lamina_id] = curr_lamina_outputs # Test end of elongation if (lamina_L >= curr_hiddenzone_outputs['lamina_Lmax']) or (leaf_L >= curr_hiddenzone_outputs['leaf_Lmax']): transition_lamina_to_sheath = True # Mature lamina, growing sheath else: curr_visible_sheath_outputs = all_element_outputs[visible_sheath_id] curr_hidden_sheath_outputs = all_element_outputs[hidden_sheath_id] # Update only sheath_Lmax based on updates of leaf_Lmax curr_hiddenzone_outputs['sheath_Lmax'] = curr_hiddenzone_outputs['leaf_Lmax'] - \ curr_hiddenzone_outputs['lamina_Lmax'] # Length of emerged sheath lamina_L = \ self.outputs['elements'][hiddenzone_id + tuple(['blade', 'LeafElement1'])][ 'length'] emerged_sheath_L = self.model.calculate_emerged_sheath_L(leaf_L, leaf_pseudostem_length, lamina_L, curr_hiddenzone_outputs[ 'sheath_Lmax']) curr_visible_sheath_outputs['length'] = emerged_sheath_L # Length of hidden sheath curr_hidden_sheath_outputs['length'] = leaf_pseudostem_length #: Test end of elongation if leaf_L >= curr_hiddenzone_outputs['leaf_Lmax']: end_leaf_elongation = True # Deals with the initiation of the sheath element and the end of lamina elongation (only in the non-hydraulics version) if transition_lamina_to_sheath: curr_lamina_outputs['is_growing'] = False curr_lamina_outputs['length'] = min(curr_hiddenzone_outputs['lamina_Lmax'], lamina_L) # Initialise hidden sheath outputs self.outputs['elements'][hidden_sheath_id] = self.element_init.__dict__.copy() self.outputs['elements'][hidden_sheath_id]['length'] = min(curr_hiddenzone_outputs['sheath_Lmax'], leaf_pseudostem_length) # Initialise visible sheath outputs self.outputs['elements'][visible_sheath_id] = self.element_init.__dict__.copy() self.outputs['elements'][visible_sheath_id]['length'] = self.model.calculate_emerged_sheath_L(leaf_L, leaf_pseudostem_length, lamina_L, curr_hiddenzone_outputs['sheath_Lmax']) # Case a mature sheath is shorter than the previous one. hidden_lamina_L = self.model.calculate_hidden_lamina_L(lamina_L, curr_hiddenzone_outputs['lamina_Lmax']) if hidden_lamina_L > 0: # Initialise hidden lamina if any self.outputs['elements'][hidden_lamina_id] = self.element_init.__dict__.copy() self.outputs['elements'][hidden_lamina_id]['length'] = hidden_lamina_L self.outputs['elements'][hidden_lamina_id]['Wmax'] = curr_hiddenzone_outputs['leaf_Wmax'] self.outputs['elements'][hidden_lamina_id]['is_growing'] = False # End of sheath elongation self.outputs['elements'][visible_sheath_id]['is_growing'] = False self.outputs['elements'][hidden_sheath_id]['is_growing'] = False # End of leaf growth end_leaf_elongation = True # Initialise variables for the next internode if next_hiddenzone_id in all_hiddenzone_inputs: next_hiddenzone_outputs = all_hiddenzone_outputs[next_hiddenzone_id] if curr_axis_outputs['GA']: next_hiddenzone_outputs['internode_Lmax'] = self.model.calculate_internode_Lmax(next_hiddenzone_outputs['internode_L']) #: Estimate of final internode length next_hiddenzone_outputs['internode_Lmax_lig'] = next_hiddenzone_outputs['internode_Lmax'] #: Estimate of final internode length at previous leaf ligulation next_hiddenzone_outputs['LSIW'] = self.model.calculate_LSIW(next_hiddenzone_outputs['LSSW'], next_hiddenzone_id[2], optimal_growth_option=True) #: Lineic Structural Internode Weight next_hiddenzone_outputs['internode_pseudo_age'] = 0 #: Pseudo age of the internode since beginning of automate growth (s) self.outputs['hiddenzone'][next_hiddenzone_id] = next_hiddenzone_outputs else: warnings.warn('No next hidden zone found for hiddenzone {}.'.format(hiddenzone_id)) # End of leaf growth if end_leaf_elongation: curr_hiddenzone_outputs['leaf_is_growing'] = False curr_hiddenzone_outputs['leaf_is_remobilizing'] = True if self.hydraulics: all_element_outputs[lamina_id]['is_growing'] = False curr_hiddenzone_outputs['sheath_is_emerged'] = True # Initialise hidden and visible sheath outputs self.outputs['elements'][hidden_sheath_id] = self.element_init.__dict__.copy() self.outputs['elements'][visible_sheath_id] = self.element_init.__dict__.copy() #- Hidden sheath self.outputs['elements'][hidden_sheath_id]['length'] = leaf_pseudostem_length self.outputs['elements'][hidden_sheath_id]['width'] = all_hiddenzone_inputs[hiddenzone_id]['width'] self.outputs['elements'][hidden_sheath_id]['thickness'] = all_hiddenzone_inputs[hiddenzone_id]['thickness'] self.outputs['elements'][hidden_sheath_id]['is_growing'] = False # - Visible sheath SL_ratio = self.model.calculate_SL_ratio(hiddenzone_id[2]) all_element_outputs[lamina_id]['length'] = curr_hiddenzone_outputs['leaf_L'] / (1 + SL_ratio) self.outputs['elements'][visible_sheath_id]['length'] = max(1E-03, curr_hiddenzone_outputs[ 'leaf_L'] - all_element_outputs[ lamina_id][ 'length'] - leaf_pseudostem_length) self.outputs['elements'][visible_sheath_id]['is_growing'] = False self.outputs['elements'][visible_sheath_id]['width'] = all_hiddenzone_inputs[hiddenzone_id]['width'] self.outputs['elements'][visible_sheath_id]['thickness'] = all_hiddenzone_inputs[hiddenzone_id]['thickness'] # Hiddenzone curr_hiddenzone_outputs['lamina_Lmax'] = all_element_outputs[lamina_id]['length'] curr_hiddenzone_outputs['sheath_Lmax'] = self.outputs['elements'][visible_sheath_id]['length'] + leaf_pseudostem_length curr_hiddenzone_outputs['SL_ratio'] = SL_ratio # Initialise variables for the next internode todo: temporary version for hydraulics : new internodes should initiate at ligulation if next_hiddenzone_id in all_hiddenzone_inputs: next_hiddenzone_outputs = all_hiddenzone_outputs[next_hiddenzone_id] if curr_axis_outputs['GA']: next_hiddenzone_outputs['internode_Lmax'] = self.model.calculate_internode_Lmax(next_hiddenzone_outputs['internode_L']) #: Estimate of final internode length next_hiddenzone_outputs['internode_Lmax_lig'] = next_hiddenzone_outputs['internode_Lmax'] #: Estimate of final internode length at previous leaf ligulation next_hiddenzone_outputs['LSIW'] = self.model.calculate_LSIW(next_hiddenzone_outputs['LSSW'], next_hiddenzone_id[2], optimal_growth_option=True) #: Lineic Structural Internode Weight next_hiddenzone_outputs['internode_pseudo_age'] = 0 #: Pseudo age of the internode since beginning of automate growth (s) self.outputs['hiddenzone'][next_hiddenzone_id] = next_hiddenzone_outputs else: warnings.warn('No next hidden zone found for hiddenzone {}.'.format(hiddenzone_id)) else: # Without hydraulics self.outputs['elements'][visible_sheath_id]['is_growing'] = False self.outputs['elements'][hidden_sheath_id]['is_growing'] = False #: Leaf is mature but internode may be growing else: leaf_L = hiddenzone_inputs['leaf_L'] delta_leaf_L = 0 # Update of leaf outputs curr_hiddenzone_outputs['leaf_L'] = leaf_L curr_hiddenzone_outputs['delta_leaf_L'] = delta_leaf_L ######################## # Internode elongation # ######################## #: Initialisation of internode elongation if (not curr_hiddenzone_outputs['internode_is_growing']) and (curr_hiddenzone_outputs['internode_L'] == 0): #: As for leaf primordia, we neglect CN growth due to IN length initialisation curr_hiddenzone_outputs['internode_is_growing'], curr_hiddenzone_outputs['internode_L'] = self.model.calculate_init_internode_elongation(curr_hiddenzone_outputs['hiddenzone_age'], phytomer_id) if curr_hiddenzone_outputs['internode_is_growing']: new_internode = self.element_init.__dict__.copy() self.outputs['elements'][hidden_internode_id] = new_internode self.outputs['elements'][hidden_internode_id]['length'] = curr_hiddenzone_outputs['internode_L'] if curr_hiddenzone_outputs['internode_is_growing']: #: Found previous lamina to know if the previous leaf is ligulated. prev_lamina_id = tuple(list(hiddenzone_id[:2]) + [hiddenzone_id[2] - 1]) + tuple(['blade', 'LeafElement1']) if phytomer_id == 1 and tuple(list(hiddenzone_id[:2]) + [hiddenzone_id[2] - 1]) + tuple(['sheath', 'StemElement']) in all_element_inputs: prev_leaf_ligulated = not all_element_inputs[tuple(list(hiddenzone_id[:2]) + [hiddenzone_id[2] - 1]) + tuple(['sheath', 'StemElement'])]['is_growing'] elif prev_lamina_id in all_element_inputs: prev_leaf_ligulated = not all_element_inputs[prev_lamina_id]['is_growing'] else: prev_leaf_ligulated = False #: Before ligulation of the leaf on the previous phytomer. Exponential-like elong. cf. Kirby 1988, Malvoisin 1984 II if not prev_leaf_ligulated: delta_internode_L = self.model.calculate_delta_internode_L_preL(phytomer_id, curr_hiddenzone_outputs['sucrose'], curr_hiddenzone_outputs['internode_L'], curr_hiddenzone_outputs['amino_acids'], curr_hiddenzone_outputs['mstruct'], curr_axis_outputs['delta_teq']) internode_L = curr_hiddenzone_outputs['internode_L'] + delta_internode_L # TODO: Check internode_L is not too large (in the case of long delta_t) curr_hiddenzone_outputs['internode_L'] = internode_L curr_hiddenzone_outputs['delta_internode_L'] = delta_internode_L # Hidden internode if hidden_internode_id not in self.outputs['elements'].keys(): new_internode = self.element_init.__dict__.copy() self.outputs['elements'][hidden_internode_id] = new_internode self.outputs['elements'][hidden_internode_id]['length'] = internode_L #: After ligulation of the leaf on the previous phytomer. else: prev_internode_pseudo_age = curr_hiddenzone_outputs['internode_pseudo_age'] internode_pseudo_age = self.model.calculate_internode_pseudo_age(prev_internode_pseudo_age, curr_axis_outputs['delta_teq']) curr_hiddenzone_outputs['internode_pseudo_age'] = internode_pseudo_age curr_hiddenzone_outputs['delta_internode_pseudo_age'] = internode_pseudo_age - curr_hiddenzone_outputs['internode_pseudo_age'] #: Elongation only if Gibberelin production by SAM if curr_axis_outputs['GA']: #: Case of internodes that will not fully elongate, GA synthesis started after their previous leaf ligulation (i.e. no Lmax defined) if pd.isnull(curr_hiddenzone_outputs['internode_Lmax']) or pd.isnull(curr_hiddenzone_outputs['internode_Lmax_lig']): curr_hiddenzone_outputs['internode_Lmax'] = curr_hiddenzone_outputs['internode_Lmax_lig'] = self.model.calculate_short_internode_Lmax(curr_hiddenzone_outputs['internode_L'], curr_hiddenzone_outputs[ 'internode_pseudo_age']) delta_internode_L = self.model.calculate_delta_internode_L_postL(prev_internode_pseudo_age, curr_hiddenzone_outputs['internode_pseudo_age'], hiddenzone_inputs['internode_L'], curr_hiddenzone_outputs['internode_Lmax_lig'], hiddenzone_inputs['sucrose'], hiddenzone_inputs['amino_acids'], hiddenzone_inputs['mstruct'], optimal_growth_option=True) internode_L = hiddenzone_inputs['internode_L'] + delta_internode_L # Update internode_Lmax if hiddenzone_inputs['internode_Lmax']: curr_hiddenzone_outputs['internode_Lmax'] = self.model.calculate_update_internode_Lmax(curr_hiddenzone_outputs['internode_Lmax_lig'], internode_L, internode_pseudo_age) #: Internode is not visible if not curr_hiddenzone_outputs['internode_is_visible']: #: Test of internode emergence. curr_hiddenzone_outputs['internode_is_visible'] = self.model.calculate_internode_visibility(curr_hiddenzone_outputs['internode_L'], internode_distance_to_emerge) if curr_hiddenzone_outputs['internode_is_visible']: #: Initialise internode outputs new_internode_outputs = self.element_init.__dict__.copy() self.outputs['elements'][visible_internode_id] = new_internode_outputs self.outputs['elements'][visible_internode_id]['length'] = min(curr_hiddenzone_outputs['internode_Lmax'], self.model.calculate_emerged_internode_L(internode_L, internode_distance_to_emerge)) self.outputs['elements'][hidden_internode_id]['length'] = internode_distance_to_emerge else: self.outputs['elements'][hidden_internode_id]['length'] = internode_L #: Internode is visible else: self.outputs['elements'][visible_internode_id]['length'] = min(curr_hiddenzone_outputs['internode_Lmax'], self.model.calculate_emerged_internode_L(internode_L, internode_distance_to_emerge)) self.outputs['elements'][hidden_internode_id]['length'] = internode_distance_to_emerge #: Test end of elongation if self.model.calculate_end_internode_elongation(internode_L, curr_hiddenzone_outputs['internode_Lmax'], curr_hiddenzone_outputs['internode_pseudo_age']): curr_hiddenzone_outputs['internode_is_growing'] = False curr_hiddenzone_outputs['internode_is_remobilizing'] = True # Visible internode if curr_hiddenzone_outputs['internode_is_visible']: self.outputs['elements'][visible_internode_id]['is_growing'] = False # Hidden internode self.outputs['elements'][hidden_internode_id]['is_growing'] = False #: Internode not elongating because no GA else: internode_L = curr_hiddenzone_outputs['internode_L'] delta_internode_L = 0 # Test end of elongation if self.model.calculate_end_internode_elongation(internode_L, curr_hiddenzone_outputs['internode_Lmax'], curr_hiddenzone_outputs['internode_pseudo_age']): curr_hiddenzone_outputs['internode_is_growing'] = False curr_hiddenzone_outputs['internode_is_remobilizing'] = True curr_hiddenzone_outputs['internode_Lmax'] = curr_hiddenzone_outputs['internode_L'] # Visible internode if curr_hiddenzone_outputs['internode_is_visible']: self.outputs['elements'][visible_internode_id]['is_growing'] = False # Hidden internode if hidden_internode_id not in self.outputs['elements'].keys(): new_internode = self.element_init.__dict__.copy() self.outputs['elements'][hidden_internode_id] = new_internode self.outputs['elements'][hidden_internode_id]['is_growing'] = False self.outputs['elements'][hidden_internode_id]['length'] = min(internode_L, internode_distance_to_emerge) print('internode {} is over'.format(hidden_internode_id)) #: Internode not elongating (not yet or already mature) else: internode_L = curr_hiddenzone_outputs['internode_L'] delta_internode_L = 0 # Update internodes outputs curr_hiddenzone_outputs['internode_L'] = internode_L curr_hiddenzone_outputs['delta_internode_L'] = delta_internode_L # Only growing hiddenzones are sent # after end of elongation (leaf and/or internode), it should : # - pass by Growth for remobilisation # - pass another time by morphogenesis for update of curr_element_outputs['final_hidden_length'] # the hiddenzone will then be deleted since both growing flags are False and both delta_L are zeros. if hiddenzone_inputs['internode_is_growing'] or curr_hiddenzone_outputs['leaf_is_growing'] or \ curr_hiddenzone_outputs['leaf_is_remobilizing'] or curr_hiddenzone_outputs['internode_is_remobilizing']: self.outputs['hiddenzone'][hiddenzone_id] = curr_hiddenzone_outputs else: # End of internode elongation del self.outputs['hiddenzone'][hiddenzone_id] print('hgz {} is over'.format(hiddenzone_id)) # ----------------------------- # -------- COLEOPTILE --------- # ----------------------------- # todo : do not use with hydraulics version # todo: add coleoptile internode (seems to elongate only if sowing depth is too high ; nodes/internodes of leaf 1-2 can also elongate in such cases) coleoptiles = {coleo_id: coleo_inputs for coleo_id, coleo_inputs in all_hiddenzone_inputs.items() if coleo_id[1:] == ('MS', 0)} for coleo_id, coleo_inputs in coleoptiles.items(): curr_axis_outputs = all_axes_outputs[coleo_id[:2]] curr_coleoptile_outputs = all_hiddenzone_outputs[coleo_id] leaf1_id = coleo_id[:2] + (1,) if leaf1_id in all_hiddenzone_outputs.keys(): leaf1_outputs = all_hiddenzone_outputs[leaf1_id] if not leaf1_outputs['leaf_is_growing']: continue else: continue #: Maximal coleoptile length provided in inputs or calculated from sowing depth if np.isnan(curr_coleoptile_outputs['leaf_Lmax']): curr_coleoptile_outputs['leaf_Lmax'] = self.model.calculate_coleo_Lmax(Zsowing) #: Coleoptile elongation equals that of leaf 1 curr_coleoptile_outputs['delta_leaf_L'] = min(leaf1_outputs['delta_leaf_L'], curr_coleoptile_outputs['leaf_Lmax'] - curr_coleoptile_outputs['leaf_L']) curr_coleoptile_outputs['leaf_L'] += curr_coleoptile_outputs['delta_leaf_L'] curr_coleoptile_outputs['leaf_pseudostem_length'] = curr_coleoptile_outputs['leaf_L'] curr_coleoptile_outputs['leaf_pseudo_age'] += curr_axis_outputs['delta_teq'] curr_coleoptile_outputs['delta_leaf_pseudo_age'] = leaf1_outputs['delta_leaf_pseudo_age'] #: Coleoptile emergence if not curr_coleoptile_outputs['leaf_is_emerged']: curr_coleoptile_outputs['leaf_is_emerged'] = self.model.calculate_coleoptile_emergence(curr_coleoptile_outputs['leaf_L'], Zsowing) if curr_coleoptile_outputs['leaf_is_emerged']: print('coleoptile has emerged', curr_coleoptile_outputs['leaf_L']) # Initialise visible coleoptile outputs visible_coleo_id = coleo_id + tuple(['sheath', 'StemElement']) visible_coleoptile = self.element_init.__dict__.copy() self.outputs['elements'][visible_coleo_id] = visible_coleoptile visible_coleoptile_outputs = all_element_outputs[visible_coleo_id] emerged_coleoptile_L = self.model.calculate_emerged_coleo_L(curr_coleoptile_outputs['leaf_L'], Zsowing) visible_coleoptile_outputs['length'] = emerged_coleoptile_L self.outputs['elements'][visible_coleo_id] = visible_coleoptile_outputs # Update of coleoptile outputs # Initialise hidden coleoptile outputs hidden_coleo_id = coleo_id + tuple(['sheath', 'HiddenElement']) hidden_coleoptile = self.element_init.__dict__.copy() self.outputs['elements'][hidden_coleo_id] = hidden_coleoptile self.outputs['elements'][hidden_coleo_id]['length'] = Zsowing # Length of hidden coleoptile else: # Coleoptile has emerged visible_coleo_id = coleo_id + tuple(['sheath', 'StemElement']) visible_coleoptile_outputs = all_element_outputs[visible_coleo_id] emerged_coleoptile_L = self.model.calculate_emerged_coleo_L(curr_coleoptile_outputs['leaf_L'], Zsowing) visible_coleoptile_outputs['length'] = emerged_coleoptile_L self.outputs['elements'][visible_coleo_id] = visible_coleoptile_outputs # Update of coleoptile outputs # Update of coleoptile outputs self.outputs['hiddenzone'][coleo_id] = curr_coleoptile_outputs #: Test end of elongation (independently of coleoptile emergence) if curr_coleoptile_outputs['leaf_L'] >= curr_coleoptile_outputs['leaf_Lmax'] and curr_coleoptile_outputs['leaf_is_growing']: print('end of coleoptile', curr_coleoptile_outputs['leaf_Lmax']) curr_coleoptile_outputs['leaf_is_growing'] = False curr_coleoptile_outputs['leaf_is_remobilizing'] = True # Update of coleoptile outputs self.outputs['hiddenzone'][coleo_id] = curr_coleoptile_outputs # Update of visible coleoptile outputs visible_coleo_id = coleo_id + tuple(['sheath', 'StemElement']) if visible_coleo_id in all_element_outputs.keys(): visible_coleoptile_outputs = all_element_outputs[visible_coleo_id] visible_coleoptile_outputs['is_growing'] = False self.outputs['elements'][visible_coleo_id] = visible_coleoptile_outputs # Update of hidden coleoptile outputs hidden_coleo_id = coleo_id + tuple(['sheath', 'HiddenElement']) hidden_coleoptile_outputs = all_element_outputs[hidden_coleo_id] hidden_coleoptile_outputs['is_growing'] = False self.outputs['elements'][hidden_coleo_id] = hidden_coleoptile_outputs if not curr_coleoptile_outputs['leaf_is_growing'] and not curr_coleoptile_outputs['leaf_is_remobilizing']: # to be adapted if accounting for coleoptile's internode elongation del self.outputs['hiddenzone'][coleo_id]