# -*- coding: latin-1 -*-
import numpy as np
from openalea.astk.plantgl_utils import get_height # for height calculation
from openalea.farquharwheat import converter, simulation, parameters
from openalea.fspmwheat import tools
"""
fspmwheat.farquharwheat_facade
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The module :mod:`fspmwheat.farquharwheat_facade` is a facade of the model FarquharWheat.
This module permits to initialize and run the model FarquharWheat 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 organs modeled by FarquharWheat
FARQUHARWHEAT_ORGANS_NAMES = {'internode', 'blade', 'sheath', 'peduncle', 'ear'}
#: names of the elements
FARQUHARWHEAT_ELEMENTS_INPUTS = ['HiddenElement', 'StemElement', 'LeafElement1']
FARQUHARWHEAT_VISIBLE_ELEMENTS_INPUTS = ['StemElement', 'LeafElement1']
#: 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 FarquharWheatFacade(object):
"""
The FarquharWheatFacade class permits to initialize, run the model FarquharWheat
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,
model_elements_inputs_df,
model_axes_inputs_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 pandas.DataFrame model_elements_inputs_df: the inputs of the model at elements scale.
:param pandas.DataFrame model_axes_inputs_df: the inputs of the model at axis scale.
:param pandas.DataFrame shared_elements_inputs_outputs_df: the dataframe of inputs and outputs at elements 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(update_parameters=update_parameters) #: the simulator to use to run the model
all_farquharwheat_inputs_dict = converter.from_dataframe(model_elements_inputs_df, model_axes_inputs_df)
self._update_shared_MTG(all_farquharwheat_inputs_dict)
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_elements_inputs_df)
[docs]
def run(self, Ta, ambient_CO2, RH, Ur, update_shared_df=None):
"""
Run the model and update the MTG and the dataframes shared between all models.
:param float Ta: air temperature at t (degree Celsius)
:param float ambient_CO2: air CO2 at t (µmol mol-1)
:param float RH: relative humidity at t (decimal fraction)
:param float Ur: wind speed at the top of the canopy at t (m s-1)
:param bool update_shared_df: if 'True', update the shared dataframes at this time step.
"""
self._initialize_model()
self._simulation.run(Ta, ambient_CO2, RH, Ur)
self._update_shared_MTG({'elements': self._simulation.outputs, 'axes': ''})
if update_shared_df or (update_shared_df is None and self._update_shared_df):
farquharwheat_elements_outputs_df = converter.to_dataframe(self._simulation.outputs)
self._update_shared_dataframes(farquharwheat_elements_outputs_df)
def _initialize_model(self):
"""
Initialize the inputs of the model from the MTG shared between all models.
"""
all_farquharwheat_elements_inputs_dict = {}
all_farquharwheat_axes_inputs_dict = {}
# 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
axis_id = (mtg_plant_index, mtg_axis_label)
farquharwheat_axis_inputs_dict = {}
for farquharwheat_axis_input_name in converter.FARQUHARWHEAT_AXES_INPUTS:
farquharwheat_axis_inputs_dict[farquharwheat_axis_input_name] = self._shared_mtg.get_vertex_property(mtg_axis_vid).get(farquharwheat_axis_input_name)
height_element_list = [0.]
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)
# mtg_organ_length = np.nan_to_num(self._shared_mtg.get_vertex_property(mtg_organ_vid).get('length', 0))
if mtg_organ_label not in FARQUHARWHEAT_ORGANS_NAMES: # or mtg_organ_length <= 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)
mtg_element_length = np.nan_to_num(self._shared_mtg.get_vertex_property(mtg_element_vid).get('length', 0.))
mtg_element_green_area = np.nan_to_num(self._shared_mtg.get_vertex_property(mtg_element_vid).get('green_area', 0.))
if mtg_element_label not in FARQUHARWHEAT_ELEMENTS_INPUTS or mtg_element_length <= 0 or mtg_element_green_area == 0:
continue # to excluse topElement, baseElement and elements with null length
if mtg_element_label == 'HiddenElement' and (self._shared_mtg.get_vertex_property(mtg_element_vid).get('is_growing', True) or np.isnan(
self._shared_mtg.get_vertex_property(mtg_element_vid).get('is_growing', True))):
continue
element_id = (mtg_plant_index, mtg_axis_label, mtg_metamer_index, mtg_organ_label, mtg_element_label)
farquharwheat_element_inputs_dict = {}
FARQUHARWHEAT_ELEMENT_DEFAULT_PROPERTIES = parameters.ElementDefaultProperties().__dict__
for farquharwheat_element_input_name in converter.FARQUHARWHEAT_ELEMENTS_INPUTS:
mtg_element_input = mtg_element_properties.get(farquharwheat_element_input_name)
if mtg_element_input is None:
mtg_element_input = FARQUHARWHEAT_ELEMENT_DEFAULT_PROPERTIES.get(farquharwheat_element_input_name)
#: Height computation for growing visible elements
if mtg_element_label in FARQUHARWHEAT_VISIBLE_ELEMENTS_INPUTS and farquharwheat_element_input_name == 'height':
mtg_element_geom = self._shared_mtg.property('geometry').get(mtg_element_vid)
if mtg_element_geom is not None: # It seems like visible elements with very little area don't have geometry.
# TODO : Ckeck ADEL's area threshold for geometry representation
triangle_heights = get_height({mtg_element_vid: self._shared_mtg.property('geometry')[mtg_element_vid]})
mtg_element_input = np.nanmean(triangle_heights[mtg_element_vid])
else:
mtg_element_input = None
height_element_list.append(mtg_element_input)
#: Width is actually diameter for Sheath and Internodes
if mtg_organ_label in ['sheath', 'internode', 'pedoncule', 'ear'] and farquharwheat_element_input_name == 'width':
mtg_element_input = mtg_element_properties.get('diameter', 0.0)
farquharwheat_element_inputs_dict[farquharwheat_element_input_name] = mtg_element_input
all_farquharwheat_elements_inputs_dict[element_id] = farquharwheat_element_inputs_dict
farquharwheat_axis_inputs_dict['height_canopy'] = np.nanmax(np.array(height_element_list, dtype=np.float64))
if np.isnan(farquharwheat_axis_inputs_dict['height_canopy']) or (farquharwheat_axis_inputs_dict['height_canopy'] is None):
farquharwheat_axis_inputs_dict['height_canopy'] = parameters.AxisDefaultProperties().__dict__['height']
all_farquharwheat_axes_inputs_dict[axis_id] = farquharwheat_axis_inputs_dict
self._simulation.initialize({'elements': all_farquharwheat_elements_inputs_dict, 'axes': all_farquharwheat_axes_inputs_dict})
def _update_shared_MTG(self, farquharwheat_data_dict):
"""
Update the MTG shared between all models from the inputs or the outputs of the model.
:param dict farquharwheat_data_dict: Farquhar-Wheat outputs.
"""
# add the properties if needed
mtg_property_names = self._shared_mtg.property_names()
for farquharwheat_elements_data_name in converter.FARQUHARWHEAT_ELEMENTS_INPUTS_OUTPUTS:
if farquharwheat_elements_data_name not in mtg_property_names:
self._shared_mtg.add_property(farquharwheat_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)
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 FARQUHARWHEAT_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 farquharwheat_data_dict['elements']:
continue
# update the element in the MTG
farquharwheat_element_data_dict = farquharwheat_data_dict['elements'][element_id]
for farquharwheat_element_data_name, farquharwheat_element_data_value in farquharwheat_element_data_dict.items():
self._shared_mtg.property(farquharwheat_element_data_name)[mtg_element_vid] = farquharwheat_element_data_value
if mtg_organ_label in ['sheath', 'internode', 'pedoncule', 'ear'] and farquharwheat_element_data_name == 'width':
self._shared_mtg.property('diameter')[mtg_element_vid] = farquharwheat_element_data_value
def _update_shared_dataframes(self, farquharwheat_elements_data_df):
"""
Update the dataframes shared between all models from the inputs dataframes or the outputs dataframes of the model.
:param pandas.DataFrame farquharwheat_elements_data_df: Farquhar-Wheat outputs.
"""
tools.combine_dataframes_inplace(farquharwheat_elements_data_df, SHARED_ELEMENTS_INPUTS_OUTPUTS_INDEXES, self._shared_elements_inputs_outputs_df)