# -*- coding: latin-1 -*-
import numpy as np
from openalea.senescwheat import converter, simulation
from openalea.fspmwheat import tools
"""
fspmwheat.senescwheat_facade
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The module :mod:`fspmwheat.senescwheat_facade` is a facade of the model SenescWheat.
This module permits to initialize and run the model SenescWheat from a :class:`MTG <openalea.mtg.mtg.MTG>`
in a convenient and transparent way, wrapping all the internal complexity of the model, and dealing
with all the tedious initialization and conversion processes.
"""
#: the name of the photosynthetic organs modeled by SenescWheat
PHOTOSYNTHETIC_ORGANS_NAMES = {'internode', 'blade', 'sheath', 'peduncle', 'ear'}
#: the columns which define the topology in the organs scale dataframe shared between all models
SHARED_AXES_INPUTS_OUTPUTS_INDEXES = ['plant', 'axis']
#: the columns which define the topology in the organs scale dataframe shared between all models
SHARED_ORGANS_INPUTS_OUTPUTS_INDEXES = ['plant', 'axis', 'organ']
#: the columns which define the topology in the elements scale dataframe shared between all models
SHARED_ELEMENTS_INPUTS_OUTPUTS_INDEXES = ['plant', 'axis', 'metamer', 'organ', 'element']
[docs]
class SenescWheatFacade(object):
"""
The SenescWheatFacade class permits to initialize, run the model SenescWheat
from a :class:`MTG <openalea.mtg.mtg.MTG>`, and update the MTG and the dataframes
shared between all models.
Use :meth:`run` to run the model.
"""
def __init__(self, shared_mtg, delta_t,
model_roots_inputs_df,
model_axes_inputs_df,
model_elements_inputs_df,
shared_organs_inputs_outputs_df,
shared_axes_inputs_outputs_df,
shared_elements_inputs_outputs_df,
update_parameters=None,
update_shared_df=True):
"""
:param openalea.mtg.mtg.MTG shared_mtg: The MTG shared between all models.
:param int delta_t: The delta between two runs, in seconds.
:param pandas.DataFrame model_roots_inputs_df: the inputs of the model at roots scale.
:param pandas.DataFrame model_axes_inputs_df: the inputs of the model at axes scale.
:param pandas.DataFrame model_elements_inputs_df: the inputs of the model at elements scale.
:param pandas.DataFrame shared_organs_inputs_outputs_df: the dataframe of inputs and outputs at organs scale shared between all models.
:param pandas.DataFrame shared_axes_inputs_outputs_df: the dataframe of inputs and outputs at axis scale shared between all models.
:param pandas.DataFrame shared_elements_inputs_outputs_df: the dataframe of inputs and outputs at element scale shared between all models.
:param dict update_parameters: A dictionary with the parameters to update, should have the form {'param1': value1, 'param2': value2, ...}.
:param bool update_shared_df: If `True` update the shared dataframes at init and at each run (unless stated otherwise)
"""
self._shared_mtg = shared_mtg #: the MTG shared between all models
self._simulation = simulation.Simulation(delta_t=delta_t, update_parameters=update_parameters) #: the simulator to use to run the model
all_senescwheat_inputs_dict = converter.from_dataframes(model_roots_inputs_df, model_axes_inputs_df, model_elements_inputs_df)
self._update_shared_MTG(all_senescwheat_inputs_dict['roots'], all_senescwheat_inputs_dict['axes'], all_senescwheat_inputs_dict['elements'])
self._shared_organs_inputs_outputs_df = shared_organs_inputs_outputs_df #: the dataframe at organs scale shared between all models
self._shared_axes_inputs_outputs_df = shared_axes_inputs_outputs_df #: the dataframe at axis scale shared between all models
self._shared_elements_inputs_outputs_df = shared_elements_inputs_outputs_df #: the dataframe at elements scale shared between all models
self._update_shared_df = update_shared_df
if self._update_shared_df:
self._update_shared_dataframes(model_roots_inputs_df, model_axes_inputs_df, model_elements_inputs_df)
[docs]
def run(self, forced_max_protein_elements=None, postflowering_stages=False, update_shared_df=None):
"""
Run the model and update the MTG and the dataframes shared between all models.
:param set forced_max_protein_elements: The elements ids with fixed max proteins.
:param bool postflowering_stages: True to run a simulation with postflo parameter
:param bool update_shared_df: if 'True', update the shared dataframes at this time step.
"""
self._initialize_model()
self._simulation.run(forced_max_protein_elements=forced_max_protein_elements, postflowering_stages=postflowering_stages)
self._update_shared_MTG(self._simulation.outputs['roots'], self._simulation.outputs['axes'], self._simulation.outputs['elements'])
if update_shared_df or (update_shared_df is None and self._update_shared_df):
senescwheat_roots_outputs_df, senescwheat_axes_outputs_df, senescwheat_elements_outputs_df = converter.to_dataframes(self._simulation.outputs)
self._update_shared_dataframes(senescwheat_roots_outputs_df, senescwheat_axes_outputs_df, senescwheat_elements_outputs_df)
def _initialize_model(self):
"""
Initialize the inputs of the model from the MTG shared between all models.
"""
all_senescwheat_roots_inputs_dict = {}
all_senescwheat_axes_inputs_dict = {}
all_senescwheat_elements_inputs_dict = {}
# traverse the MTG recursively from the top ...
for mtg_plant_vid in self._shared_mtg.components_iter(self._shared_mtg.root):
mtg_plant_index = int(self._shared_mtg.index(mtg_plant_vid))
for mtg_axis_vid in self._shared_mtg.components_iter(mtg_plant_vid):
mtg_axis_label = self._shared_mtg.label(mtg_axis_vid)
if mtg_axis_label != 'MS':
continue
axis_id = (mtg_plant_index, mtg_axis_label)
mtg_axis_properties = self._shared_mtg.get_vertex_property(mtg_axis_vid)
if set(mtg_axis_properties).issuperset(converter.SENESCWHEAT_AXES_INPUTS):
senescwheat_axis_inputs_dict = {}
for senescwheat_axis_input_name in converter.SENESCWHEAT_AXES_INPUTS:
senescwheat_axis_inputs_dict[senescwheat_axis_input_name] = mtg_axis_properties[senescwheat_axis_input_name]
all_senescwheat_axes_inputs_dict[axis_id] = senescwheat_axis_inputs_dict
if 'roots' in mtg_axis_properties:
mtg_roots_properties = mtg_axis_properties['roots']
if set(mtg_roots_properties).issuperset(converter.SENESCWHEAT_ROOTS_INPUTS):
senescwheat_roots_inputs_dict = {}
for senescwheat_roots_input_name in converter.SENESCWHEAT_ROOTS_INPUTS:
senescwheat_roots_inputs_dict[senescwheat_roots_input_name] = mtg_roots_properties[senescwheat_roots_input_name]
all_senescwheat_roots_inputs_dict[axis_id] = senescwheat_roots_inputs_dict
for mtg_metamer_vid in self._shared_mtg.components_iter(mtg_axis_vid):
mtg_metamer_index = int(self._shared_mtg.index(mtg_metamer_vid))
for mtg_organ_vid in self._shared_mtg.components_iter(mtg_metamer_vid):
mtg_organ_label = self._shared_mtg.label(mtg_organ_vid)
if mtg_organ_label not in PHOTOSYNTHETIC_ORGANS_NAMES:
continue
# if np.nan_to_num( self._shared_mtg.property('length').get(mtg_organ_vid,0)) == 0: continue
for mtg_element_vid in self._shared_mtg.components_iter(mtg_organ_vid):
mtg_element_properties = self._shared_mtg.get_vertex_property(mtg_element_vid)
mtg_element_label = self._shared_mtg.label(mtg_element_vid)
element_id = (mtg_plant_index, mtg_axis_label, mtg_metamer_index, mtg_organ_label, mtg_element_label)
if np.nan_to_num(self._shared_mtg.property('length').get(mtg_element_vid, 0)) == 0:
continue
if set(mtg_element_properties).issuperset(converter.SENESCWHEAT_ELEMENTS_INPUTS):
senescwheat_element_inputs_dict = {}
for senescwheat_element_input_name in converter.SENESCWHEAT_ELEMENTS_INPUTS:
senescwheat_element_inputs_dict[senescwheat_element_input_name] = mtg_element_properties[senescwheat_element_input_name]
all_senescwheat_elements_inputs_dict[element_id] = senescwheat_element_inputs_dict
# TODO: temporary ; replace 'SENESCWHEAT_ELEMENT_PROPERTIES_TEMP' by default values
SENESCWHEAT_ELEMENT_PROPERTIES_TEMP = {'starch': 0, 'max_proteins': 0, 'amino_acids': 0,
'proteins': 0, 'Nstruct': 0, 'mstruct': 0, 'fructan': 0,
'sucrose': 0, 'green_area': 0, 'cytokinins': 0}
senescwheat_element_inputs_dict = {}
for senescwheat_element_input_name in converter.SENESCWHEAT_ELEMENTS_INPUTS:
if senescwheat_element_input_name in mtg_element_properties:
senescwheat_element_inputs_dict[senescwheat_element_input_name] = mtg_element_properties[senescwheat_element_input_name]
else:
senescwheat_element_inputs_dict[senescwheat_element_input_name] = SENESCWHEAT_ELEMENT_PROPERTIES_TEMP[senescwheat_element_input_name]
all_senescwheat_elements_inputs_dict[element_id] = senescwheat_element_inputs_dict
self._simulation.initialize({'roots': all_senescwheat_roots_inputs_dict, 'axes': all_senescwheat_axes_inputs_dict, 'elements': all_senescwheat_elements_inputs_dict})
def _update_shared_MTG(self, senescwheat_roots_data_dict, senescwheat_axes_data_dict, senescwheat_elements_data_dict):
"""
Update the MTG shared between all models from the inputs or the outputs of the model.
:param dict senescwheat_roots_data_dict: Senesc-Wheat outputs at root scale
:param dict senescwheat_axes_data_dict: Senesc-Wheat outputs at axis scale
:param dict senescwheat_elements_data_dict: Senesc-Wheat outputs at element scale
"""
# add the properties if needed
mtg_property_names = self._shared_mtg.property_names()
if 'roots' not in mtg_property_names:
self._shared_mtg.add_property('roots')
for senescwheat_elements_data_name in converter.SENESCWHEAT_ELEMENTS_INPUTS_OUTPUTS:
if senescwheat_elements_data_name not in mtg_property_names:
self._shared_mtg.add_property(senescwheat_elements_data_name)
# traverse the MTG recursively from top ...
for mtg_plant_vid in self._shared_mtg.components_iter(self._shared_mtg.root):
mtg_plant_index = int(self._shared_mtg.index(mtg_plant_vid))
for mtg_axis_vid in self._shared_mtg.components_iter(mtg_plant_vid):
mtg_axis_label = self._shared_mtg.label(mtg_axis_vid)
if mtg_axis_label != 'MS':
continue
# update the axis property in the MTG
axis_id = (mtg_plant_index, mtg_axis_label)
if axis_id in senescwheat_axes_data_dict:
senescwheat_axis_data_dict = senescwheat_axes_data_dict[axis_id]
for axis_data_name, axis_data_value in senescwheat_axis_data_dict.items():
self._shared_mtg.property(axis_data_name)[mtg_axis_vid] = axis_data_value
# update the roots in the MTG
if axis_id not in senescwheat_roots_data_dict:
continue
if 'roots' not in self._shared_mtg.get_vertex_property(mtg_axis_vid):
self._shared_mtg.property('roots')[mtg_axis_vid] = {}
mtg_roots_properties = self._shared_mtg.get_vertex_property(mtg_axis_vid)['roots']
mtg_roots_properties.update(senescwheat_roots_data_dict[axis_id])
for mtg_metamer_vid in self._shared_mtg.components_iter(mtg_axis_vid):
mtg_metamer_index = int(self._shared_mtg.index(mtg_metamer_vid))
for mtg_organ_vid in self._shared_mtg.components_iter(mtg_metamer_vid):
mtg_organ_label = self._shared_mtg.label(mtg_organ_vid)
# senesced_length_organ = 0. # Temporaire
if mtg_organ_label not in PHOTOSYNTHETIC_ORGANS_NAMES:
continue
for mtg_element_vid in self._shared_mtg.components_iter(mtg_organ_vid):
mtg_element_label = self._shared_mtg.label(mtg_element_vid)
element_id = (mtg_plant_index, mtg_axis_label, mtg_metamer_index, mtg_organ_label, mtg_element_label)
if element_id not in senescwheat_elements_data_dict:
continue
# update the element in the MTG
senescwheat_element_data_dict = senescwheat_elements_data_dict[element_id]
for senescwheat_element_data_name, senescwheat_element_data_value in senescwheat_element_data_dict.items():
self._shared_mtg.property(senescwheat_element_data_name)[mtg_element_vid] = senescwheat_element_data_value
# Temporaire avant de trouver une solution pour :
# 1) piloter la senescence des feuilles par green_area plutot que par senesced_length,
# 2) updater les organes à partir des éléments et non l'inverse.
if senescwheat_element_data_name == 'senesced_length_element' and mtg_element_label in ['LeafElement1', 'StemElement']:
self._shared_mtg.property('senesced_length')[mtg_organ_vid] = np.nan_to_num(self._shared_mtg.property(senescwheat_element_data_name).get(mtg_element_vid, 0.))
def _update_shared_dataframes(self, senescwheat_roots_data_df, senescwheat_axes_data_df, senescwheat_elements_data_df):
"""
Update the dataframes shared between all models from the inputs dataframes or the outputs dataframes of the model.
:param pandas.DataFrame senescwheat_roots_data_df: Elong-Wheat shared dataframe at root scale
:param pandas.DataFrame senescwheat_axes_data_df: Elong-Wheat shared dataframe at axis scale
:param pandas.DataFrame senescwheat_elements_data_df: Elong-Wheat shared dataframe at element scale
"""
for senescwheat_data_df, \
shared_inputs_outputs_indexes, \
shared_inputs_outputs_df in ((senescwheat_roots_data_df, SHARED_ORGANS_INPUTS_OUTPUTS_INDEXES, self._shared_organs_inputs_outputs_df),
(senescwheat_axes_data_df, SHARED_AXES_INPUTS_OUTPUTS_INDEXES, self._shared_axes_inputs_outputs_df),
(senescwheat_elements_data_df, SHARED_ELEMENTS_INPUTS_OUTPUTS_INDEXES, self._shared_elements_inputs_outputs_df)):
if senescwheat_data_df is senescwheat_roots_data_df:
senescwheat_data_df = senescwheat_data_df.copy()
senescwheat_data_df.loc[:, 'organ'] = 'roots'
tools.combine_dataframes_inplace(senescwheat_data_df, shared_inputs_outputs_indexes, shared_inputs_outputs_df)