"""
hydraulics.converter
~~~~~~~~~~~~~~~~~
The module :mod:`hydraulics.converter` defines functions to convert
:class:`dataframes <pandas.DataFrame>` to/from Hydraulics inputs or outputs format.
"""
import numpy as np
import pandas as pd
from openalea.cnwgrass.hydraulics import simulation
from openalea.cnwgrass.hydraulics import model
#: the columns of the outputs dataframe at PLANT scale
PLANTS_VARIABLES = simulation.Simulation.PLANTS_INDEXES + simulation.Simulation.PLANTS_RUN_VARIABLES
#: the columns of the outputs dataframe at AXIS scale
AXES_VARIABLES = simulation.Simulation.AXES_INDEXES + simulation.Simulation.AXES_RUN_VARIABLES
#: the columns of the outputs dataframe at PHYTOMER scale
PHYTOMERS_VARIABLES = simulation.Simulation.PHYTOMERS_INDEXES + simulation.Simulation.PHYTOMERS_RUN_VARIABLES
#: the columns of the outputs dataframe at HIDDENZONE scale
HIDDENZONE_VARIABLES = simulation.Simulation.HIDDENZONE_INDEXES + simulation.Simulation.HIDDENZONE_RUN_VARIABLES
HIDDENZONE_OUTPUTS_VARIABLES = HIDDENZONE_VARIABLES
HIDDENZONE_OUTPUTS_RUN_VARIABLES = simulation.Simulation.HIDDENZONE_RUN_VARIABLES
#: the columns of the outputs dataframe at ELEMENTS scale
ELEMENTS_VARIABLES = simulation.Simulation.ELEMENTS_INDEXES + simulation.Simulation.ELEMENTS_RUN_VARIABLES
ELEMENTS_OUTPUTS_VARIABLES = ELEMENTS_VARIABLES
ELEMENTS_OUTPUTS_RUN_VARIABLES = simulation.Simulation.ELEMENTS_RUN_VARIABLES
#: the columns of the outputs dataframe at ORGANS scale
ORGANS_VARIABLES = simulation.Simulation.ORGANS_INDEXES + simulation.Simulation.ORGANS_RUN_VARIABLES
ORGANS_OUTPUTS_VARIABLES = ORGANS_VARIABLES
ORGANS_OUTPUTS_RUN_VARIABLES = simulation.Simulation.ORGANS_RUN_VARIABLES
#: the columns of the outputs dataframe at SOIL scale
SOILS_VARIABLES = simulation.Simulation.SOILS_INDEXES + simulation.Simulation.SOILS_RUN_VARIABLES
#: the mapping of the Hydraulics organs classes to organs names in MTG
hydraulics_CLASSES_TO_DATAFRAME_ORGANS_MAPPING = {model.Organ: 'organs', model.Internode: 'internode', model.Lamina: 'blade', model.Sheath: 'sheath', model.HiddenZone: 'hiddenzone', model.Roots: 'roots', model.Xylem: 'xylem'}
#: the mapping of the name of each element, from Dataframe to Hydraulics
DATAFRAME_TO_hydraulics_ELEMENTS_NAMES_MAPPING = {'HiddenElement': 'enclosed_element', 'StemElement': 'exposed_element', 'LeafElement1': 'exposed_element'}
DATAFRAME_TO_hydraulics_LEAF_NAMES_MAPPING = {'LeafElement1': 'exposed_element'}
[docs]
def from_dataframes(axes_inputs=None, hiddenzones_inputs=None, elements_inputs=None, organs_inputs=None, soils_inputs=None):
"""
If `elements_inputs` and `hiddenzones_inputs` are not `None`, converts `elements_inputs` and `hiddenzones_inputs` to a :class:`population <model.Population>`.
:param pandas.DataFrame hiddenzones_inputs: Hidden zone inputs, with one line per hidden zone.
:param pandas.DataFrame elements_inputs: Element inputs, with one line per element.
:param pandas.DataFrame organs_inputs: Organs (xylem and roots) inputs, with one line per organ.
:param pandas.DataFrame soils_inputs: Soils inputs, with one line by soil.
:return: If `elements_inputs` and `hiddenzones_inputs` are not `None`, returns a :class:`population <model.Population>`
and/or
if `soils_inputs` is not `None`, return a :class:`dict` of :class:`soils <model.Soil>`.
:rtype: (model.Population, dict)
"""
convert_dataframes_to_population = axes_inputs is not None and elements_inputs is not None and hiddenzones_inputs is not None and organs_inputs is not None
convert_dataframe_to_soils_dict = soils_inputs is not None
if convert_dataframes_to_population:
population = model.Population()
mapping_topology = {'predecessor': {}, 'successor': {}}
for plant_index in elements_inputs.plant.unique():
# create a new plant
plant = model.Plant(plant_index)
population.plants.append(plant)
# curr_axes_labels = elements_inputs[elements_inputs['plant'] == plant_index].axis.unique()
curr_axes_labels = organs_inputs[organs_inputs['plant'] == plant_index].axis.unique()
for axis_label in curr_axes_labels:
# create a new axis
axis = model.Axis(axis_label)
curr_organs_inputs = organs_inputs[(organs_inputs['plant'] == plant_index) & (organs_inputs['axis'] == axis_label)]
for axis_attribute_name, axis_attribute_class in (('roots', model.Roots), ('xylem', model.Xylem)):
organ_label = hydraulics_CLASSES_TO_DATAFRAME_ORGANS_MAPPING[axis_attribute_class]
organs_inputs = curr_organs_inputs[curr_organs_inputs['organ'] == organ_label]
if not organs_inputs.empty:
# create a new organs
organs = axis_attribute_class(organ_label)
organs_attributes_names = [state_var_name for state_var_name in simulation.Simulation.ORGANS_STATE if hasattr(organs, state_var_name)]
organs_row = organs_inputs.loc[organs_inputs.first_valid_index()]
organs_attributes_values = organs_row[organs_attributes_names].tolist()
organs_attributes = dict(zip(organs_attributes_names, organs_attributes_values))
organs.__dict__.update(organs_attributes)
# Update parameters if specified
# if organs_label in update_parameters:
# organs.PARAMETERS.__dict__.update(update_parameters[organs_label])
organs.initialize()
setattr(axis, axis_attribute_name, organs)
#Topology of organs
roots = model.Roots(label='roots')
# last_elongated_internode = roots
mapping_topology['successor'][roots] = []
xylem = model.Xylem(label='xylem')
mapping_topology['successor'][xylem] = []
curr_metamers_indexes_for_hiddenzones = hiddenzones_inputs[(hiddenzones_inputs['plant'] == plant_index) & (hiddenzones_inputs['axis'] == axis_label)].metamer.unique()
curr_metamers_indexes_for_elements = elements_inputs[(elements_inputs['plant'] == plant_index) & (elements_inputs['axis'] == axis_label)].metamer.unique()
curr_metamers_indexes = np.unique(np.concatenate((curr_metamers_indexes_for_hiddenzones,
curr_metamers_indexes_for_elements)))
for metamer_index in sorted(curr_metamers_indexes):
# create a new phytomer
phytomer = model.Phytomer(metamer_index)
axis.phytomers.append(phytomer)
for phytomer_attribute_name, phytomer_attribute_class, phytomer_attribute_element_class in \
(('lamina', model.Lamina, model.LaminaElement),
('internode', model.Internode, model.InternodeElement),
('sheath', model.Sheath, model.SheathElement)):
organs_label = hydraulics_CLASSES_TO_DATAFRAME_ORGANS_MAPPING[phytomer_attribute_class]
if metamer_index in curr_metamers_indexes_for_elements:
curr_elements_inputs = elements_inputs[(elements_inputs['plant'] == plant_index) & (elements_inputs['axis'] == axis_label) & (elements_inputs['metamer'] == metamer_index) &
(elements_inputs['organ'] == organs_label)]
if organs_label not in curr_elements_inputs.organ.values:
continue
# create a new organs
organs = phytomer_attribute_class(label=organs_label)
setattr(phytomer, phytomer_attribute_name, organs)
for mtg_element_label, hydraulics_element_name in DATAFRAME_TO_hydraulics_ELEMENTS_NAMES_MAPPING.items():
element_inputs = curr_elements_inputs[curr_elements_inputs['element'] == mtg_element_label]
if len(element_inputs) == 0:
continue
element_inputs = element_inputs.loc[:, simulation.Simulation.ELEMENTS_STATE]
element_dict = element_inputs.loc[element_inputs.first_valid_index()].dropna().to_dict()
# create a new element
element = phytomer_attribute_element_class(label=mtg_element_label, **element_dict)
# Add parameters from organ scale
element.PARAMETERS.__dict__.update(organs.PARAMETERS.__dict__)
setattr(organs, hydraulics_element_name, element)
#: Hidden zones
if metamer_index in curr_metamers_indexes_for_hiddenzones:
hiddenzone_inputs = hiddenzones_inputs[(hiddenzones_inputs['plant'] == plant_index) & (hiddenzones_inputs['axis'] == axis_label) &
(hiddenzones_inputs['metamer'] == metamer_index)]
if len(hiddenzone_inputs) == 0:
continue
hiddenzone_inputs = hiddenzone_inputs.loc[:, simulation.Simulation.HIDDENZONE_STATE]
hiddenzone_dict = hiddenzone_inputs.loc[hiddenzone_inputs.first_valid_index()].dropna().to_dict()
# create a new hidden zone
hiddenzone = model.HiddenZone(hydraulics_CLASSES_TO_DATAFRAME_ORGANS_MAPPING[model.HiddenZone], **hiddenzone_dict)
phytomer.hiddenzone = hiddenzone
plant.axes.append(axis)
if convert_dataframe_to_soils_dict:
soils = {}
for soil_id, soil_group in soils_inputs.groupby(simulation.Simulation.SOILS_INDEXES):
# create a new soil
soil_attributes = soil_group.loc[soil_group.first_valid_index(), simulation.Simulation.SOILS_STATE].to_dict()
soil = model.Soil(**soil_attributes)
soils[soil_id] = soil
if convert_dataframes_to_population and convert_dataframe_to_soils_dict:
return population, soils
elif convert_dataframes_to_population:
return population
else:
return soils
[docs]
def to_dataframes(population=None, soils=None):
"""
Convert a Hydraulics :class:`population <model.Population>` to Pandas dataframes and/or a dictionary of :class:`soils <model.Soil>` to Pandas dataframes.
If `population` is not None, convert `population` to Pandas dataframes.
If `soils` is not None, convert `soils` to Pandas dataframe.
:param model.Population population: The Hydraulics population to convert.
:param dict soils: The soils to convert.
:return: If `population` is not None, return :class:`dataframes <pandas.DataFrame>` describing the internal state and compartments of the population at each scale:
* hidden zones: plant index, axis id, phytomer index, state parameters, state variables, intermediate variables,
fluxes and integrative variables of each hidden zone (see :mod:`HIDDENZONE_VARIABLES`)
* element scale: plant index, axis id, phytomer index, organs type, element type, state parameters, state variables, intermediate variables,
fluxes and integrative variables of each element (see :mod:`ELEMENTS_VARIABLES`)
* xylem scale: xylem index, state parameters, state variables, intermediate variables,
fluxes and integrative variables of xylem (see :mod:`XYLEM_VARIABLES`)
and/or
if `soils` is not None, return a :class:`dataframe <pandas.DataFrame>` describing internal state and compartments of the soils, with one line per soil:
* plant index, axis id, state parameters, state variables, intermediate variables, fluxes and integrative variables of each soil (see :mod:`SOILS_RUN_VARIABLES`)
:rtype: (pandas.DataFrame, pandas.DataFrame)
"""
convert_population_to_dataframes = population is not None
convert_soils_to_dataframe = soils is not None
def append_row(model_object, indexes, attributes_names, inputs_df):
# function to append a row to a dataframe
attributes_values = []
for attribute_name in attributes_names:
attributes_values.append(getattr(model_object, attribute_name, np.nan))
inputs_df.loc[len(inputs_df), :] = indexes + attributes_values
if convert_population_to_dataframes:
# initialize the dataframes
all_plants_df = pd.DataFrame(columns=PLANTS_VARIABLES)
all_axes_df = pd.DataFrame(columns=AXES_VARIABLES)
all_phytomers_df = pd.DataFrame(columns=PHYTOMERS_VARIABLES)
all_organs_df = pd.DataFrame(columns=ORGANS_VARIABLES)
all_hiddenzones_df = pd.DataFrame(columns=HIDDENZONE_VARIABLES)
all_elements_df = pd.DataFrame(columns=ELEMENTS_VARIABLES)
# run through the population tree and fill the dataframes
for plant in population.plants:
append_row(plant, [plant.index], simulation.Simulation.PLANTS_RUN_VARIABLES, all_plants_df)
for axis in plant.axes:
append_row(axis, [plant.index, axis.label], simulation.Simulation.AXES_RUN_VARIABLES, all_axes_df)
for organs in (axis.roots, axis.xylem):
if organs is not None:
append_row(organs, [plant.index, axis.label, organs.label], simulation.Simulation.ORGANS_RUN_VARIABLES, all_organs_df)
for phytomer in axis.phytomers:
append_row(phytomer, [plant.index, axis.label, phytomer.index], simulation.Simulation.PHYTOMERS_RUN_VARIABLES, all_phytomers_df)
if phytomer.hiddenzone is not None:
append_row(phytomer.hiddenzone, [plant.index, axis.label, phytomer.index], simulation.Simulation.HIDDENZONE_RUN_VARIABLES, all_hiddenzones_df)
for organs in (phytomer.lamina, phytomer.internode, phytomer.sheath):
if organs is None:
continue
for element in (organs.exposed_element, organs.enclosed_element):
if element is None:
continue
append_row(element, [plant.index, axis.label, phytomer.index, organs.label, element.label], simulation.Simulation.ELEMENTS_RUN_VARIABLES, all_elements_df)
# sort the rows of the dataframes by columns
all_plants_df.sort_values(by=PLANTS_VARIABLES, inplace=True)
all_axes_df.sort_values(by=AXES_VARIABLES, inplace=True)
all_phytomers_df.sort_values(by=PHYTOMERS_VARIABLES, inplace=True)
all_organs_df.sort_values(by=ORGANS_VARIABLES, inplace=True)
all_hiddenzones_df.sort_values(by=HIDDENZONE_VARIABLES, inplace=True)
all_elements_df.sort_values(by=ELEMENTS_VARIABLES, inplace=True)
# convert the indexes of plants, metamers and elements to integers in the dataframes
all_plants_df['plant'] = all_plants_df['plant'].astype(int)
all_axes_df['plant'] = all_axes_df['plant'].astype(int)
all_phytomers_df[['plant', 'metamer']] = all_phytomers_df[['plant', 'metamer']].astype(int)
all_organs_df['plant'] = all_organs_df['plant'].astype(int)
all_hiddenzones_df[['plant', 'metamer']] = all_hiddenzones_df[['plant', 'metamer']].astype(int)
all_elements_df[['plant', 'metamer']] = all_elements_df[['plant', 'metamer']].astype(int)
all_plants_df.reset_index(drop=True, inplace=True)
all_axes_df.reset_index(drop=True, inplace=True)
all_phytomers_df.reset_index(drop=True, inplace=True)
all_organs_df.reset_index(drop=True, inplace=True)
all_hiddenzones_df.reset_index(drop=True, inplace=True)
all_elements_df.reset_index(drop=True, inplace=True)
if convert_soils_to_dataframe:
all_soils_df = pd.DataFrame(columns=SOILS_VARIABLES)
for soil_id, soil in soils.items():
append_row(soil, list(soil_id), simulation.Simulation.SOILS_RUN_VARIABLES, all_soils_df)
all_soils_df.sort_values(by=SOILS_VARIABLES, inplace=True)
all_soils_df['plant'] = all_soils_df['plant'].astype(int)
all_soils_df.reset_index(drop=True, inplace=True)
if convert_population_to_dataframes and convert_soils_to_dataframe:
return all_plants_df, all_axes_df, all_phytomers_df, all_organs_df, all_hiddenzones_df, all_elements_df, all_soils_df
elif convert_population_to_dataframes:
return all_plants_df, all_axes_df, all_phytomers_df, all_organs_df, all_hiddenzones_df, all_elements_df
else:
return all_soils_df