# -*- coding: latin-1 -*-
from __future__ import division # use "//" to do integer division
import warnings
import copy
import numpy as np
import pandas as pd
from openalea.elongwheat import model
from openalea.elongwheat import parameters
"""
elongwheat.simulation
~~~~~~~~~~~~~~~~~~
The module :mod:`elongwheat.simulation` is the front-end to run the Elong-Wheat model.
"""
#: the inputs needed by ElongWheat
HIDDENZONE_INPUTS = ['leaf_is_growing', 'internode_is_growing', 'leaf_pseudo_age', 'internode_pseudo_age', 'leaf_pseudostem_length', 'internode_distance_to_emerge', 'leaf_L', 'internode_L',
'hiddenzone_age', 'leaf_Lmax', 'leaf_Lmax_em', 'lamina_Lmax', 'sheath_Lmax', 'leaf_Wmax', 'SSLW', 'LSSW', 'leaf_is_emerged', 'internode_Lmax', 'internode_Lmax_lig', 'LSIW',
'internode_is_visible', 'sucrose', 'amino_acids', 'fructan', 'proteins', 'leaf_enclosed_mstruct', 'leaf_enclosed_Nstruct', 'internode_enclosed_mstruct',
'internode_enclosed_Nstruct', 'mstruct', 'is_over', 'mean_conc_sucrose']
ELEMENT_INPUTS = ['length', 'Wmax', 'is_growing', 'age', 'is_over']
AXIS_INPUTS = ['SAM_temperature', 'delta_teq', 'teq_since_primordium', 'status', 'nb_leaves', 'GA', 'SAM_height', 'cohort', 'sum_TT']
#: the outputs computed by ElongWheat
# TODO : add be default all the attributes of the class HiddenZoneInit and ElementInit, and define which attribute is set by growthwheat.parameters or elongwheat.parameters
HIDDENZONE_OUTPUTS = ['leaf_is_growing', 'internode_is_growing', 'leaf_pseudo_age', 'delta_leaf_pseudo_age', 'internode_pseudo_age', 'delta_internode_pseudo_age', 'leaf_pseudostem_length',
'hiddenzone_age', 'delta_leaf_pseudostem_length', 'internode_distance_to_emerge',
'delta_internode_distance_to_emerge', 'leaf_L', 'delta_leaf_L', 'internode_L', 'delta_internode_L', 'leaf_Lmax', 'leaf_Lmax_em', 'lamina_Lmax', 'sheath_Lmax', 'leaf_Wmax',
'SSLW', 'LSSW', 'leaf_is_emerged', 'internode_Lmax', 'internode_Lmax_lig', 'LSIW', 'internode_is_visible', 'sucrose', 'amino_acids', 'fructan', 'proteins',
'leaf_enclosed_mstruct',
'leaf_enclosed_Nstruct', 'internode_enclosed_mstruct', 'internode_enclosed_Nstruct', 'mstruct', 'is_over', 'ratio_DZ',
'mean_conc_sucrose', 'leaf_is_remobilizing', 'internode_is_remobilizing']
ELEMENT_OUTPUTS = ['length', 'Wmax', 'is_growing', 'sucrose', 'amino_acids', 'fructan', 'proteins', 'nitrates', 'starch', 'cytokinins',
'mstruct', 'Nstruct', 'age', 'Nresidual', 'max_proteins', 'senesced_length_element', 'green_area', 'max_mstruct',
'senesced_mstruct', 'is_over']
AXIS_OUTPUTS = ['SAM_temperature', 'delta_teq', 'delta_teq_roots', 'teq_since_primordium', 'status', 'nb_leaves', 'GA', 'SAM_height', 'cohort', 'sum_TT']
#: the inputs and outputs of ElongWheat.
HIDDENZONE_INPUTS_OUTPUTS = sorted(set(HIDDENZONE_INPUTS + HIDDENZONE_OUTPUTS))
ELEMENT_INPUTS_OUTPUTS = sorted(set(ELEMENT_INPUTS + ELEMENT_OUTPUTS))
AXIS_INPUTS_OUTPUTS = sorted(set(AXIS_INPUTS + AXIS_OUTPUTS))
#: topology colums for ligule height dataframe
LIGULE_TOPOLOGY_COLUMNS = ['axis_id', 'phytomer', 'ligule height']
[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, update_parameters=None):
"""The inputs of elong-Wheat."""
#: `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]}, ...}
#:
#: See :TODO?
#: for more information about the inputs.
self.inputs = {}
#: The outputs of elong-Wheat.
#:
#: `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, ...}, ...}}
#:
#: See :TODO?
#: for more information about the inputs.
self.outputs = {}
#: the delta t of the simulation (in seconds)
self.delta_t = delta_t
#: Update parameters if specified
if update_parameters:
parameters.__dict__.update(update_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, optimal_growth_option):
"""
Run the simulation.
:param float Tair: Air temperature at t (degree Celsius)
:param float Tsoil: Soil temperature at t (degree Celsius)
:param bool optimal_growth_option: if True the model will assume optimal growth conditions
"""
# 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=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 = model.calculate_cumulated_internode_length(below_internode_lengths)
curr_axis_outputs['SAM_height'] = SAM_height
# SAM temperature
growth_temperature = model.calculate_growing_temperature(Tair, Tsoil, SAM_height)
curr_axis_outputs['SAM_temperature'] = growth_temperature
# temperature-compensated time
curr_axis_outputs['delta_teq'] = model.calculate_time_equivalent_Tref(growth_temperature, self.delta_t)
curr_axis_outputs['delta_teq_roots'] = model.calculate_time_equivalent_Tref(Tsoil, self.delta_t)
# cumulated thermal time
curr_axis_outputs['sum_TT'] = 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'] = 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'] = 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 = parameters.HiddenZoneInit().__dict__
self.outputs['hiddenzone'][hiddenzone_id] = new_hiddenzone
# Ligule height
all_ligule_height_df = 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 (sor far lamina curvature, could be used to adapt stem geometry too)
# TODO : the calculation of element's age must be extracted from openalea.elongwheat as it is run even for mature leaves
curr_age = all_element_inputs[element_id]['age']
axis_id = element_id[:2]
curr_axis_outputs = all_axes_outputs[axis_id]
self.outputs['elements'][element_id]['age'] = model.calculate_cumulated_thermal_time(curr_age, curr_axis_outputs['SAM_temperature'], 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]
#: 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']
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_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 = model.calculate_cumulated_internode_length(below_internode_lengths)
# Distance between the bottom of the hiddenzone and the highest previous ligule
leaf_pseudostem_length = 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 growthwheat
# Calculate the internode pseudostem length
curr_internode_L = hiddenzone_inputs['internode_L']
internode_distance_to_emerge = 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 growthwheat
# 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 = parameters.ElementInit().__dict__
self.outputs['elements'][hidden_sheath_id] = new_sheath
if visible_sheath_id not in self.outputs['elements'].keys():
new_sheath = parameters.ElementInit().__dict__
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']:
time_prev_leaf2_emergence = prev_hiddenzone_inputs['leaf_pseudo_age']
curr_hiddenzone_outputs['mean_conc_sucrose'] = 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: #: Before the emergence of the previous leaf. Exponential-like elongation.
# delta leaf length
delta_leaf_L = 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, optimal_growth_option)
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 = model.calculate_leaf_pseudo_age(hiddenzone_inputs['leaf_pseudo_age'], curr_axis_outputs['delta_teq'])
curr_hiddenzone_outputs['leaf_pseudo_age'] = leaf_pseudo_age
curr_hiddenzone_outputs['delta_leaf_pseudo_age'] = leaf_pseudo_age - hiddenzone_inputs['leaf_pseudo_age']
delta_leaf_L = 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'], 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 of each element status (growing or mature)
curr_hiddenzone_outputs['leaf_Lmax'] = model.calculate_update_leaf_Lmax(hiddenzone_inputs['leaf_Lmax_em'], leaf_L, leaf_pseudo_age)
# Ratio (mass) of Division Zone in the hiddenzone
curr_hiddenzone_outputs['ratio_DZ'] = model.calculate_ratio_DZ_postE(leaf_L, curr_hiddenzone_outputs['leaf_Lmax'], leaf_pseudostem_length)
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'] = model.calculate_leaf_emergence(hiddenzone_inputs['leaf_L'], leaf_pseudostem_length)
if curr_hiddenzone_outputs['leaf_is_emerged']: # Initialise lamina outputs
new_lamina = parameters.ElementInit().__dict__
self.outputs['elements'][lamina_id] = new_lamina
curr_lamina_outputs = all_element_outputs[lamina_id]
# Length of emerged lamina
lamina_L = model.calculate_lamina_L(leaf_L, leaf_pseudostem_length, hiddenzone_id, 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]
next_hiddenzone_outputs['leaf_Lmax'] = model.calculate_leaf_Lmax(next_hiddenzone_inputs['leaf_L']) #: Final leaf length
next_hiddenzone_outputs['leaf_Lmax_em'] = next_hiddenzone_outputs['leaf_Lmax'] #: Final leaf length at Ln-1 em
sheath_lamina_ratio = model.calculate_SL_ratio(next_hiddenzone_id[2]) #: Sheath:Lamina final length ratio
next_hiddenzone_outputs['lamina_Lmax'] = model.calculate_lamina_Lmax(next_hiddenzone_outputs['leaf_Lmax'], sheath_lamina_ratio) #: Final lamina length
next_hiddenzone_outputs['sheath_Lmax'] = model.calculate_sheath_Lmax(next_hiddenzone_outputs['leaf_Lmax'],
next_hiddenzone_outputs['lamina_Lmax']) #: Final sheath 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
curr_hiddenzone_outputs['leaf_Wmax'] = self.outputs['elements'][lamina_id]['Wmax'] = model.calculate_leaf_Wmax(curr_hiddenzone_outputs['lamina_Lmax'], hiddenzone_id[2],
curr_hiddenzone_outputs['mean_conc_sucrose'],
optimal_growth_option)
curr_hiddenzone_outputs['SSLW'] = model.calculate_SSLW(hiddenzone_id[2], curr_hiddenzone_outputs['mean_conc_sucrose'], optimal_growth_option)
curr_hiddenzone_outputs['LSSW'] = model.calculate_LSSW(hiddenzone_id[2], curr_hiddenzone_outputs['mean_conc_sucrose'], optimal_growth_option)
#: Test end of elongation when a leaf stops elongation inside the pseudostem (extreme stress)
if leaf_L >= curr_hiddenzone_outputs['leaf_Lmax']:
# Update lamina_Lmax and sheath_Lmax based on updates of leaf_Lmax
sheath_lamina_ratio = model.calculate_SL_ratio(hiddenzone_id[2])
# Initialise hidden lamina
new_hidden_lamina = parameters.ElementInit().__dict__
self.outputs['elements'][hidden_lamina_id] = new_hidden_lamina
self.outputs['elements'][hidden_lamina_id]['length'] = curr_hiddenzone_outputs['lamina_Lmax'] = 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 = parameters.ElementInit().__dict__
self.outputs['elements'][hidden_sheath_id] = new_sheath
self.outputs['elements'][hidden_sheath_id]['length'] = curr_hiddenzone_outputs['sheath_Lmax'] = 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
#: Lamina has emerged and is growing
elif curr_hiddenzone_outputs['leaf_is_emerged'] and 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 = model.calculate_SL_ratio(hiddenzone_id[2])
curr_hiddenzone_outputs['lamina_Lmax'] = model.calculate_lamina_Lmax(curr_hiddenzone_outputs['leaf_Lmax'], sheath_lamina_ratio)
curr_hiddenzone_outputs['sheath_Lmax'] = model.calculate_sheath_Lmax(curr_hiddenzone_outputs['leaf_Lmax'], curr_hiddenzone_outputs['lamina_Lmax'])
# Length of emerged lamina
lamina_L = model.calculate_lamina_L(leaf_L, leaf_pseudostem_length, hiddenzone_id, curr_hiddenzone_outputs['lamina_Lmax'])
curr_lamina_outputs['length'] = lamina_L
# Test end of elongation
if (lamina_L >= curr_hiddenzone_outputs['lamina_Lmax']) or (leaf_L >= curr_hiddenzone_outputs['leaf_Lmax']):
curr_lamina_outputs['is_growing'] = False
curr_lamina_outputs['length'] = min(curr_hiddenzone_outputs['lamina_Lmax'], lamina_L)
# Initialise visible sheath outputs
new_sheath = parameters.ElementInit().__dict__
self.outputs['elements'][visible_sheath_id] = new_sheath
curr_visible_sheath_outputs = all_element_outputs[visible_sheath_id]
emerged_sheath_L = model.calculate_emerged_sheath_L(leaf_L, leaf_pseudostem_length, lamina_L, curr_hiddenzone_outputs['sheath_Lmax']) # Length of emerged sheath
curr_visible_sheath_outputs['length'] = emerged_sheath_L
self.outputs['elements'][visible_sheath_id] = curr_visible_sheath_outputs # Update of sheath outputs
# Initialise hidden sheath outputs
new_sheath = parameters.ElementInit().__dict__
self.outputs['elements'][hidden_sheath_id] = new_sheath
self.outputs['elements'][hidden_sheath_id]['length'] = min(curr_hiddenzone_outputs['sheath_Lmax'], leaf_pseudostem_length) # Length of hidden sheath
# 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'] = 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'] = 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))
# Case a mature sheath is shorter than the previous one.
hidden_lamina_L = model.calculate_hidden_lamina_L(lamina_L, curr_hiddenzone_outputs['lamina_Lmax'])
if hidden_lamina_L > 0:
# Initialise hidden lamina if any
new_hidden_lamina = parameters.ElementInit().__dict__
self.outputs['elements'][hidden_lamina_id] = new_hidden_lamina
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
curr_hiddenzone_outputs['leaf_is_growing'] = False
curr_hiddenzone_outputs['leaf_is_remobilizing'] = True
# Update of lamina outputs
self.outputs['elements'][lamina_id] = curr_lamina_outputs
# Mature lamina, growing sheath
else:
visible_sheath_id = hiddenzone_id + tuple(['sheath', 'StemElement'])
curr_visible_sheath_outputs = all_element_outputs[visible_sheath_id]
hidden_sheath_id = hiddenzone_id + tuple(['sheath', 'HiddenElement'])
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 = 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']:
curr_visible_sheath_outputs['is_growing'] = False
curr_hiddenzone_outputs['leaf_is_growing'] = False
curr_hiddenzone_outputs['leaf_is_remobilizing'] = True
# Hidden sheath
curr_hidden_sheath_outputs['is_growing'] = False
# Update of sheath outputs
self.outputs['elements'][visible_sheath_id] = curr_visible_sheath_outputs
self.outputs['elements'][hidden_sheath_id] = curr_hidden_sheath_outputs
#: 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'] = model.calculate_init_internode_elongation(curr_hiddenzone_outputs['hiddenzone_age'])
if curr_hiddenzone_outputs['internode_is_growing']:
new_internode = parameters.ElementInit().__dict__
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 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 = 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'], optimal_growth_option=True)
internode_L = curr_hiddenzone_outputs['internode_L'] + delta_internode_L # TODO: Ckeck 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 = parameters.ElementInit().__dict__
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 = 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'] = model.calculate_short_internode_Lmax(curr_hiddenzone_outputs['internode_L'],
curr_hiddenzone_outputs[
'internode_pseudo_age'])
delta_internode_L = 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'] = 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'] = 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 = parameters.ElementInit().__dict__
self.outputs['elements'][visible_internode_id] = new_internode_outputs
self.outputs['elements'][visible_internode_id]['length'] = min(curr_hiddenzone_outputs['internode_Lmax'],
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'],
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 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 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 = parameters.ElementInit().__dict__
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)
#: 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 wheat for remobilisation
# - pass another time by elong wheat 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.get('leaf_is_remobilizing', False) or curr_hiddenzone_outputs.get('internode_is_remobilizing', False):
self.outputs['hiddenzone'][hiddenzone_id] = curr_hiddenzone_outputs
else: # End of internode elongation
del self.outputs['hiddenzone'][hiddenzone_id]