Source code for openalea.cnwgrass.integration.tools

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

import os
import warnings

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from itertools import cycle
import matplotlib.ticker as mtick
import statsmodels.api as sm

from openalea.adel.mtg import to_plantgl
from openalea.plantgl.all import Viewer, Vector3

"""
    integration.tools
    ~~~~~~~~~~~~~~~

    This module provides convenient tools needed by the facades.

"""

OUTPUTS_INDEXES = ['t', 'plant', 'axis', 'metamer', 'organ', 'element']  #: All the possible indexes

[docs] def combine_dataframes_inplace(model_dataframe, shared_column_indexes, shared_dataframe_to_update): """Combine `model_dataframe` and `shared_dataframe_to_update` in-place: * re-index `model_dataframe` and `shared_dataframe_to_update` by `shared_column_indexes`, * use method pd.DataFrame.combine_first(), * reset to the right types in `shared_dataframe_to_update`, * reorder the columns: first columns in `shared_column_indexes`, then others columns alphabetically, * and reset the index in `shared_dataframe_to_update`. :param pandas.DataFrame model_dataframe: dataframe to use for updating `shared_dataframe_to_update`. :param list shared_column_indexes: The indexes to re-index `model_dataframe` and `shared_dataframe_to_update` before combining them. :param pandas.DataFrame shared_dataframe_to_update: The dataframe to update. note:: `shared_dataframe_to_update` is updated in-place. Thus, `shared_dataframe_to_update` keeps the same object's memory address. """ # re-index the dataframes to have common indexes if len(shared_dataframe_to_update) == 0: shared_dataframe_to_update_reindexed = shared_dataframe_to_update else: shared_dataframe_to_update.sort_values(shared_column_indexes, inplace=True) shared_dataframe_to_update_reindexed = pd.DataFrame(shared_dataframe_to_update.values.tolist(), index=sorted(shared_dataframe_to_update.groupby(shared_column_indexes).groups.keys()), columns=shared_dataframe_to_update.columns) model_dataframe.sort_values(shared_column_indexes, inplace=True) model_dataframe_reindexed = pd.DataFrame(model_dataframe.values.tolist(), index=sorted(model_dataframe.groupby(shared_column_indexes).groups.keys()), columns=model_dataframe.columns) # combine model and shared re-indexed dataframes if model_dataframe_reindexed.empty and shared_dataframe_to_update.empty: new_shared_dataframe = model_dataframe_reindexed.copy() for new_header in shared_dataframe_to_update_reindexed.columns.difference(model_dataframe_reindexed.columns): new_shared_dataframe[new_header] = "" else: new_shared_dataframe = model_dataframe_reindexed.combine_first(shared_dataframe_to_update_reindexed) # reset to the right types in the combined dataframe dtypes = model_dataframe_reindexed.dtypes.combine_first(shared_dataframe_to_update_reindexed.dtypes) for column_name, data_type in dtypes.items(): if pd.api.types.is_integer_dtype(data_type) and new_shared_dataframe[column_name].isnull().values.any(): # Used to keep bool values data_type = float # will return an error if data_type is integer new_shared_dataframe[column_name] = new_shared_dataframe[column_name].astype(data_type) # reorder the columns new_shared_dataframe = new_shared_dataframe.reindex(shared_column_indexes + sorted(new_shared_dataframe.columns.difference(shared_column_indexes)), axis=1) # update the shared dataframe in-place shared_dataframe_to_update.drop(shared_dataframe_to_update.index, axis=0, inplace=True) shared_dataframe_to_update.drop(shared_dataframe_to_update.columns, axis=1, inplace=True) shared_dataframe_to_update['dataframe_to_update_index'] = new_shared_dataframe.index shared_dataframe_to_update.set_index('dataframe_to_update_index', inplace=True) for column in new_shared_dataframe.columns: shared_dataframe_to_update[column] = new_shared_dataframe[column] shared_dataframe_to_update.reset_index(0, drop=True, inplace=True)
[docs] def plot_outputs(outputs, x_name, y_name, x_label='', y_label='', x_lim=None, title=None, meteo_data=None, filters={}, plot_filepath=None, colors=[], linestyles=[], explicit_label=True, kwargs={}): """Plot `outputs`, with x=`x_name` and y=`y_name`. The general algorithm is: * find the scale of `outputs` and keep only the needed columns, * apply `filters` to `outputs` and make groups according to the scale, * plot each group as a new line, * save or display the plot. :param pandas.DataFrame outputs: The outputs of CN-Metabolism. :param str x_name: x-axis of the plot. :param str y_name: y-axis of the plot. :param str x_label: The x label of the plot. Default is ''. :param str or unicode y_label: The y label of the plot. Default is ''. :param float x_lim: the x-axis limit. :param str title: the title of the plot. If None (default), create a title which is the concatenation of `y_name` and each scales which cardinality is one. :param pandas.DataFrame meteo_data: the meteo dataframe having the mapping between t (hours) and calendar dates :param dict filters: A dictionary whose keys are the columns of `outputs` for which we want to apply a specific filter. These columns can be one or more element of :const:`OUTPUTS_INDEXES`. The value associated to each key is a criteria that the rows of `outputs` must satisfy to be plotted. The values can be either one value or a list of values. If no value is given for any column, then all rows are plotted (default). :param list colors: The colors for lines. If empty, let matplotlib default line colors. :param list linestyles: The styles for lines. If empty, let matplotlib default line styles. :param str plot_filepath: The file path to save the plot. If `None`, do not save the plot but display it. :param bool explicit_label: True: makes the line label from concatenation of each scale id (default). - False: makes the line label from concatenation of scales containing several distinct elements. :param dict kwargs: key arguments to be passed to matplolib Examples:: import pandas as pd cnmetabolism_output_df = pd.read_csv('cnmetabolism_output.csv') # in this example, 'cnmetabolism_output.csv' must contain at least the columns 't' and 'Conc_Sucrose'. plot(cnmetabolism_output_df, x_name = 't', y_name = 'Conc_Sucrose', x_label='Time (Hour)', y_label=u'[Sucrose] (µmol g$^{-1}$ mstruct)', title='{} = f({})'.format('Conc_Sucrose', 't'), filters={'plant': 1, 'axis': 'MS', 'organ': 'Lamina', 'element': 1}) """ # finds the scale of `outputs` group_keys = [key for key in OUTPUTS_INDEXES if key in outputs and key != x_name and key != y_name] # make a group_keys with first letter of each key in upper case group_keys_upper = [group_key[0].upper() + group_key[1:] for group_key in group_keys] # create a mapping to associate each key to its index in group_keys group_keys_mapping = dict([(key, index) for (index, key) in enumerate(group_keys)]) # keep only the needed columns (to make the grouping faster) outputs = outputs[group_keys + [x_name, y_name]] # apply filters to outputs for key, value in filters.items(): if key in outputs: # convert to list if needed try: _ = iter(value) except TypeError: values = [value] else: values = value # handle strings too if isinstance(values, str): values = [values] # select data from outputs outputs = outputs[outputs[key].isin(values)] # do not plot if there is nothing to plot if outputs[y_name].isnull().all(): return # compute the cardinality of each group keys and create the title if needed subtitle_groups = [] labels_groups = [] for i in range(len(group_keys)): group_key = group_keys[i] group_cardinality = outputs[group_key].nunique() if group_cardinality == 1: group_value = outputs[group_key][outputs.first_valid_index()] subtitle_groups.append('{}: {}'.format(group_keys_upper[i], group_value)) else: labels_groups.append(group_key) if title is None: # we need to create the title title = y_name + '\n' + ' - '.join(subtitle_groups) # makes groups according to the scale outputs_grouped = outputs.groupby(group_keys) # plots each group as a new line fig, ax = plt.subplots() matplot_colors_cycler = cycle(colors) matplot_linestyles_cycler = cycle(linestyles) for outputs_group_name, outputs_group in outputs_grouped: line_label_list = [] if explicit_label: # concatenate the keys of the group name line_label_list.extend(['{}: {}'.format(group_keys_upper[group_keys_mapping[output_group_name]], outputs_group_name) for output_group_name in outputs_group_name]) else: # construct a label with only the essential keys of the group name ; the essential keys are those for which cardinality is non-zero for label_group in labels_groups: label_group_index = group_keys_mapping[label_group] line_label_list.append('{}: {}'.format(group_keys_upper[label_group_index], outputs_group_name[label_group_index])) kwargs['label'] = ' - '.join(line_label_list) # apply user colors try: color = next(matplot_colors_cycler) except StopIteration: pass else: kwargs['color'] = color # apply user lines style try: linestyle = next(matplot_linestyles_cycler) except StopIteration: pass else: kwargs['linestyle'] = linestyle # plot the line ax.plot(outputs_group[x_name], outputs_group[y_name], **kwargs) if y_name not in ('water_potential', 'osmotic_water_potential'): ax.set_ylim(bottom=0.) if x_lim is not None: ax.set_xlim(left=0, right=x_lim) else: ax.set_xlim(left=0) if meteo_data is not None: meteo_data['Date'] = pd.to_datetime(meteo_data['Date'], format='%d/%m/%Y') ax2 = ax.twiny() ax2.set_xticks(ax.get_xticks()) ax2.set_xticklabels(meteo_data.loc[ax.get_xticks()]['Date'].dt.strftime('%d/%m')) ax2.xaxis.set_ticks_position('bottom') # set the position of the second x-axis to bottom ax2.xaxis.set_label_position('bottom') # set the position of the second x-axis to bottom ax2.spines['bottom'].set_position(('outward', 35)) ax.set_xlabel(x_label) ax.set_ylabel(y_label) if kwargs['label']: ax.legend(prop={'size': 6}, framealpha=0.5, loc='center left', bbox_to_anchor=(1, 0.815), borderaxespad=0.) ax.set_title(title) plt.tight_layout() if plot_filepath is None: # display the plot plt.show() else: # save the plot plt.savefig(plot_filepath, dpi=200, format='PNG', bbox_inches='tight') plt.close()
[docs] def additional_graphs(axes_outputs, hz_outputs, elements_outputs, axes_postprocessing, hz_postprocessing, elements_postprocessing, organs_postprocessing, plant_density, RER_max_param, GRAPHS_DIRPATH, data_obs): """ :param pandas.DataFrame axes_outputs: :param pandas hz_outputs: :param pandas.DataFrame elements_outputs: :param pandas.DataFrame axes_postprocessing: :param pandas.DataFrame hz_postprocessing: :param pandas.DataFrame elements_postprocessing: :param pandas.DataFrame organs_postprocessing: :param int plant_density: :param dict RER_max_param: :param str GRAPHS_DIRPATH: :param pandas.DataFrame data_obs: """ colors = ['blue', 'darkorange', 'green', 'red', 'darkviolet', 'gold', 'magenta', 'brown', 'darkcyan', 'grey', 'lime'] colors = colors + colors # 0) Phyllochron df_MS = axes_outputs[axes_outputs['axis'] == 'MS'] grouped_df = hz_postprocessing[hz_postprocessing['axis'] == 'MS'].groupby(['plant', 'metamer'])[['t', 'leaf_is_emerged']] grouped_iter = iter(grouped_df) next(grouped_iter, None) # ignoring the first leaf as we can't calculate its phyllochron leaf_emergence = {} for group_name, data in grouped_iter: plant, metamer = group_name[0], group_name[1] if True not in data['leaf_is_emerged'].unique(): continue leaf_emergence_t = data[data['leaf_is_emerged'] == True].iloc[0]['t'] leaf_emergence[(plant, metamer)] = leaf_emergence_t phyllochron = {'plant': [], 'metamer': [], 'phyllochron': []} for key, leaf_emergence_t in sorted(leaf_emergence.items()): plant, metamer = key[0], key[1] if (plant, metamer - 1) not in leaf_emergence.keys(): continue phyllochron['plant'].append(plant) phyllochron['metamer'].append(metamer) prev_leaf_emergence_t = leaf_emergence[(plant, metamer - 1)] if df_MS[(df_MS['t'] == leaf_emergence_t) | (df_MS['t'] == prev_leaf_emergence_t)].sum_TT.count() == 2: phyllo_DD = df_MS[(df_MS['t'] == leaf_emergence_t)].sum_TT.values[0] - df_MS[(df_MS['t'] == prev_leaf_emergence_t)].sum_TT.values[0] else: phyllo_DD = np.nan phyllochron['phyllochron'].append(phyllo_DD) if len(phyllochron['metamer']) > 0: fig, ax = plt.subplots() plt.xlim((int(min(phyllochron['metamer']) - 1), int(max(phyllochron['metamer']) + 1))) plt.ylim(ymin=0, ymax=150) ax.plot(phyllochron['metamer'], phyllochron['phyllochron'], color='b', marker='o') for i, j in zip(phyllochron['metamer'], phyllochron['phyllochron']): ax.annotate(str(int(round(j, 0))), xy=(i, j + 2), ha='center') ax.set_xlabel('Leaf number') ax.set_ylabel('Phyllochron (Degree Day)') ax.set_title('phyllochron') plt.savefig(os.path.join(GRAPHS_DIRPATH, 'phyllochron' + '.PNG')) plt.close() # 1) Comparison Dimensions with Ljutovac 2002 df_hz_filtered = hz_outputs[(hz_outputs['axis'] == 'MS') & (hz_outputs['plant'] == 1) & ~np.isnan(hz_outputs.leaf_Lmax)].copy() df_IN = df_hz_filtered[~ np.isnan(df_hz_filtered.internode_Lmax)] last_value_idx = df_hz_filtered.groupby(['metamer'])['t'].transform(max) == df_hz_filtered['t'] df_hz_filtered_end = df_hz_filtered[last_value_idx].copy() df_hz_filtered_end['lamina_Wmax'] = df_hz_filtered_end.leaf_Wmax df_hz_filtered_end['lamina_W_Lg'] = df_hz_filtered_end.leaf_Wmax / df_hz_filtered_end.lamina_Lmax bchmk = data_obs.loc[data_obs.metamer >= min(df_hz_filtered_end.metamer)] bchmk['lamina_W_Lg'] = bchmk.lamina_Wmax / bchmk.lamina_Lmax last_value_idx = df_IN.groupby(['metamer'])['t'].transform(max) == df_IN['t'] df_IN_last = df_IN[last_value_idx].copy() res = df_hz_filtered_end[['metamer', 'leaf_Lmax', 'lamina_Lmax', 'sheath_Lmax', 'lamina_Wmax', 'lamina_W_Lg', 'SSLW', 'LSSW']].merge(df_IN_last[['metamer', 'internode_Lmax']], left_on='metamer', right_on='metamer', how='outer').copy() var_list = ['leaf_Lmax', 'lamina_Lmax', 'sheath_Lmax', 'lamina_Wmax', 'internode_Lmax'] for var in list(var_list): fig, ax = plt.subplots() plt.xlim((int(min(res.metamer) - 1), int(max(res.metamer) + 1))) plt.ylim(ymin=0, ymax=np.nanmax(list(res[var] * 100 * 1.05) + list(bchmk[var] * 1.05))) tmp = res[['metamer', var]].drop_duplicates().sort_values('metamer').reset_index(drop=True) line1 = ax.plot(tmp.metamer, tmp[var] * 100, color='c', marker='o') line2 = ax.plot(bchmk.metamer, bchmk[var], color='orange', marker='o') ax.set_ylabel(var + ' (cm)') ax.set_title(var) ax.legend((line1[0], line2[0]), ('Simulation', 'Ljutovac 2002'), loc=2) plt.savefig(os.path.join(GRAPHS_DIRPATH, var + '.PNG')) plt.close() var = 'lamina_W_Lg' fig, ax = plt.subplots() plt.xlim((int(min(res.metamer) - 1), int(max(res.metamer) + 1))) plt.ylim(ymin=0, ymax=np.nanmax(list(res[var] * 1.05) + list(bchmk[var] * 1.05))) tmp = res[['metamer', var]].drop_duplicates().sort_values('metamer').reset_index(drop=True) line1 = ax.plot(tmp.metamer, tmp[var], color='c', marker='o') line2 = ax.plot(bchmk.metamer, bchmk[var], color='orange', marker='o') ax.set_ylabel(var) ax.set_title(var) ax.legend((line1[0], line2[0]), ('Simulation', 'Ljutovac 2002'), loc=2) plt.savefig(os.path.join(GRAPHS_DIRPATH, var + '.PNG')) plt.close() # 1bis) Comparison Structural Masses vs. adaptation from Bertheloot 2008 # SSLW Laminae bchmk = pd.DataFrame.from_dict({1: 15, 2: 23, 3: 25, 4: 18, 5: 22, 6: 25, 7: 20, 8: 23, 9: 26, 10: 28, 11: 31}, orient='index').rename(columns={0: 'SSLW'}) bchmk.index.name = 'metamer' bchmk = bchmk.reset_index() bchmk = bchmk[bchmk.metamer >= min(res.metamer)] fig, ax = plt.subplots() plt.xlim((int(min(res.metamer) - 1), int(max(res.metamer) + 1))) plt.ylim(ymin=0, ymax=50) tmp = res[['metamer', 'SSLW']].drop_duplicates().sort_values('metamer').reset_index(drop=True) line1 = ax.plot(tmp.metamer, tmp.SSLW, color='c', marker='o') line2 = ax.plot(bchmk.metamer, bchmk.SSLW, color='orange', marker='o') ax.set_ylabel('Structural Specific Lamina Weight (g.m-2)') ax.set_title('Structural Specific Lamina Weight') ax.legend((line1[0], line2[0]), ('Simulation', 'adapated from Bertheloot 2008'), loc=3) plt.savefig(os.path.join(GRAPHS_DIRPATH, 'SSLW.PNG')) plt.close() # LWS Sheaths bchmk = pd.DataFrame.from_dict({1: 0.08, 2: 0.09, 3: 0.11, 4: 0.18, 5: 0.17, 6: 0.21, 7: 0.24, 8: 0.4, 9: 0.5, 10: 0.55, 11: 0.65}, orient='index').rename(columns={0: 'LSSW'}) bchmk.index.name = 'metamer' bchmk = bchmk.reset_index() bchmk = bchmk[bchmk.metamer >= min(res.metamer)] fig, ax = plt.subplots() plt.xlim((int(min(res.metamer) - 1), int(max(res.metamer) + 1))) plt.ylim(ymin=0, ymax=0.8) tmp = res[['metamer', 'LSSW']].drop_duplicates().sort_values('metamer').reset_index(drop=True) line1 = ax.plot(tmp.metamer, tmp.LSSW, color='c', marker='o') line2 = ax.plot(bchmk.metamer, bchmk.LSSW, color='orange', marker='o') ax.set_ylabel('Lineic Structural Sheath Weight (g.m-1)') ax.set_title('Lineic Structural Sheath Weight') ax.legend((line1[0], line2[0]), ('Simulation', 'adapated from Bertheloot 2008'), loc=2) plt.savefig(os.path.join(GRAPHS_DIRPATH, 'LSSW.PNG')) plt.close() # 2) LAI elements_postprocessing['green_area_rep'] = elements_postprocessing.green_area * elements_postprocessing.nb_replications grouped_df = elements_postprocessing[(elements_postprocessing.axis == 'MS') & (elements_postprocessing.element == 'LeafElement1')].groupby(['t', 'plant']) LAI_dict = {'t': [], 'plant': [], 'LAI': []} for name, data in grouped_df: t, plant = name[0], name[1] LAI_dict['t'].append(t) LAI_dict['plant'].append(plant) LAI_dict['LAI'].append(data['green_area_rep'].sum() * plant_density) plot_outputs(pd.DataFrame(LAI_dict), 't', 'LAI', x_label='Time (Hour)', y_label='LAI', plot_filepath=os.path.join(GRAPHS_DIRPATH, 'LAI.PNG'), explicit_label=False) # 3) RER during the exponentiel-like phase # - RER parameters rer_param = dict((k, v) for k, v in RER_max_param) # - Simulated RER # import simulation outputs data_RER = hz_postprocessing[(hz_postprocessing.axis == 'MS') & (hz_postprocessing.metamer >= 1)].copy() data_RER.sort_values(['t', 'metamer'], inplace=True) # - Time previous leaf emergence tmp = data_RER[data_RER.leaf_is_emerged] leaf_em = tmp.groupby('metamer', as_index=False)['t'].min() leaf_em['t_em'] = leaf_em.t prev_leaf_em = leaf_em prev_leaf_em.metamer = leaf_em.metamer + 1 data_RER2 = pd.merge(data_RER, prev_leaf_em[['metamer', 't_em']], on='metamer') data_RER2 = data_RER2[data_RER2.t <= data_RER2.t_em] # - SumTimeEq df_MS['SumTimeEq'] = np.cumsum(df_MS.delta_teq) data_RER3 = pd.merge(data_RER2, df_MS[['t', 'SumTimeEq']], on='t') # - logL data_RER3['logL'] = np.log(data_RER3.leaf_L) # - Estimate RER RER_sim = {} for leaf in data_RER3.metamer.drop_duplicates(): Y = data_RER3.logL[data_RER3.metamer == leaf] X = data_RER3.SumTimeEq[data_RER3.metamer == leaf] X = sm.add_constant(X) mod = sm.OLS(Y, X) fit_RER = mod.fit() RER_sim[leaf] = fit_RER.params['SumTimeEq'] if len(RER_sim) != 0: # - Graph fig, ax1 = plt.subplots() ax1.yaxis.set_major_formatter(mtick.FormatStrFormatter('%.1e')) x, y = zip(*sorted(RER_sim.items())) ax1.plot(x, y, label=r'Simulated RER', linestyle='-', color='g') ax1.errorbar(data_obs.metamer, data_obs.RER, yerr=data_obs.RER_confint, marker='o', color='g', linestyle='', label="Observed RER", markersize=2) ax1.plot(list(rer_param.keys()), list(rer_param.values()), marker='*', color='k', linestyle='', label="Model parameters") # Formatting ax1.set_ylabel(u'Relative Elongation Rate at 12°C (s$^{-1}$)') ax1.legend(prop={'size': 12}, bbox_to_anchor=(0.05, .6, 0.9, .5), loc='upper center', ncol=3, mode="expand", borderaxespad=0.) ax1.legend(loc='upper left') ax1.set_xlabel('Phytomer rank') ax1.set_ylim(bottom=0., top=6e-6) ax1.set_xlim(left=2) plt.savefig(os.path.join(GRAPHS_DIRPATH, 'RER_comparison.PNG'), format='PNG', bbox_inches='tight', dpi=200) plt.close() # 4) Total C production vs. Root C allcoation df_roots = organs_postprocessing[organs_postprocessing['organ'] == 'roots'].copy() df_roots['day'] = df_roots['t'] // 24 + 1 df_roots['Unloading_Sucrose_tot'] = df_roots['Unloading_Sucrose'] * df_roots['mstruct'] Unloading_Sucrose_tot = df_roots.groupby(['day'])['Unloading_Sucrose_tot'].agg('sum') days = df_roots['day'].unique() axes_postprocessing['day'] = axes_postprocessing['t'] // 24 + 1 Total_Photosynthesis = axes_postprocessing.groupby(['day'])['Tillers_Photosynthesis'].agg('sum') elements_postprocessing['day'] = elements_postprocessing['t'] // 24 + 1 elements_postprocessing['sum_respi_tillers'] = elements_postprocessing['sum_respi'] * elements_postprocessing['nb_replications'] Shoot_respiration = elements_postprocessing.groupby(['day'])['sum_respi_tillers'].agg('sum') Net_Photosynthesis = Total_Photosynthesis - Shoot_respiration share_net_roots_live = Unloading_Sucrose_tot.to_numpy() / Net_Photosynthesis.replace(0, np.nan).to_numpy() * 100 fig, ax = plt.subplots() line1 = ax.plot(days, Net_Photosynthesis, label=u'Net_Photosynthesis') line2 = ax.plot(days, Unloading_Sucrose_tot, label=u'C unloading to roots') ax2 = ax.twinx() line3 = ax2.plot(days, share_net_roots_live, label=u'Net C Shoot production sent to roots (%)', color='red') lines = line1 + line2 + line3 labs = [line.get_label() for line in lines] ax.legend(lines, labs, loc='center left', prop={'size': 10}, framealpha=0.5, bbox_to_anchor=(1, 0.815), borderaxespad=0.) ax.set_xlabel('Days') ax2.set_ylim([0, 200]) ax.set_ylabel(u'C (µmol C.day$^{-1}$ )') ax2.set_ylabel(u'Ratio (%)') ax.set_title('C allocation to roots') plt.savefig(os.path.join(GRAPHS_DIRPATH, 'C_allocation.PNG'), dpi=200, format='PNG', bbox_inches='tight') # 5) C usages relative to Net Photosynthesis if not elements_postprocessing.empty: df_phloem = organs_postprocessing[organs_postprocessing['organ'] == 'phloem'].copy() df_phloem['day'] = df_phloem['t'] // 24 + 1 AMINO_ACIDS_C_RATIO = 4.15 #: Mean number of mol of C in 1 mol of the major amino acids of plants (Glu, Gln, Ser, Asp, Ala, Gly) AMINO_ACIDS_N_RATIO = 1.25 #: Mean number of mol of N in 1 mol of the major amino acids of plants (Glu, Gln, Ser, Asp, Ala, Gly) # Photosynthesis elements_postprocessing['Photosynthesis_tillers'] = elements_postprocessing['Photosynthesis'].fillna(0) * elements_postprocessing['nb_replications'].fillna(1.) Tillers_Photosynthesis_Ag = elements_postprocessing.groupby(['t'], as_index=False).agg({'Photosynthesis_tillers': 'sum'}) C_usages = pd.DataFrame({'t': Tillers_Photosynthesis_Ag['t']}) C_usages['C_produced'] = np.cumsum(Tillers_Photosynthesis_Ag.Photosynthesis_tillers) # Respiration C_usages['Respi_roots'] = np.cumsum(axes_postprocessing.C_respired_roots) C_usages['Respi_shoot'] = np.cumsum(axes_postprocessing.C_respired_shoot) # Exudation C_usages['exudation'] = np.cumsum(axes_postprocessing.C_exuded.fillna(0)) # Structural growth C_consumption_mstruct_roots = df_roots.sucrose_consumption_mstruct.fillna(0) + df_roots.AA_consumption_mstruct.fillna(0) * AMINO_ACIDS_C_RATIO / AMINO_ACIDS_N_RATIO C_usages['Structure_roots'] = np.cumsum(C_consumption_mstruct_roots.reset_index(drop=True)) hz_postprocessing['C_consumption_mstruct'] = hz_postprocessing.sucrose_consumption_mstruct.fillna(0) + hz_postprocessing.AA_consumption_mstruct.fillna(0) * AMINO_ACIDS_C_RATIO / AMINO_ACIDS_N_RATIO hz_postprocessing['C_consumption_mstruct_tillers'] = hz_postprocessing['C_consumption_mstruct'] * hz_postprocessing['nb_replications'] C_consumption_mstruct_shoot = hz_postprocessing.groupby(['t'])['C_consumption_mstruct_tillers'].sum() C_usages['Structure_shoot'] = np.cumsum(C_consumption_mstruct_shoot.reset_index(drop=True)).apply(float) # Non structural C df_phloem['C_NS'] = df_phloem.sucrose.fillna(0) + df_phloem.amino_acids.fillna(0) * AMINO_ACIDS_C_RATIO / AMINO_ACIDS_N_RATIO C_NS_phloem_init = df_phloem.C_NS - df_phloem.C_NS.iloc[0] C_usages['NS_phloem'] = C_NS_phloem_init.reset_index(drop=True) elements_postprocessing['C_NS'] = elements_postprocessing.sucrose.fillna(0) + elements_postprocessing.fructan.fillna(0) + elements_postprocessing.starch.fillna(0) + ( elements_postprocessing.amino_acids.fillna(0) + elements_postprocessing.proteins.fillna(0)) * AMINO_ACIDS_C_RATIO / AMINO_ACIDS_N_RATIO elements_postprocessing['C_NS_tillers'] = elements_postprocessing['C_NS'] * elements_postprocessing['nb_replications'].fillna(1.) C_elt = elements_postprocessing.groupby(['t']).agg({'C_NS_tillers': 'sum'}) hz_postprocessing['C_NS'] = hz_postprocessing.sucrose.fillna(0) + hz_postprocessing.fructan.fillna(0) + (hz_postprocessing.amino_acids.fillna(0) + hz_postprocessing.proteins.fillna(0)) * AMINO_ACIDS_C_RATIO / AMINO_ACIDS_N_RATIO hz_postprocessing['C_NS_tillers'] = hz_postprocessing['C_NS'] * hz_postprocessing['nb_replications'].fillna(1.) C_hz = hz_postprocessing.groupby(['t']).agg({'C_NS_tillers': 'sum'}) df_roots['C_NS'] = df_roots.sucrose.fillna(0) + df_roots.amino_acids.fillna(0) * AMINO_ACIDS_C_RATIO / AMINO_ACIDS_N_RATIO C_NS_autre = df_roots.set_index('t').C_NS.add(C_elt.C_NS_tillers, fill_value=0).add(C_hz.C_NS_tillers, fill_value=0) C_NS_autre_init = C_NS_autre - C_NS_autre[0] C_usages['NS_other'] = C_NS_autre_init.reset_index(drop=True) # Total C_usages['C_budget'] = (C_usages.Respi_roots + C_usages.Respi_shoot + C_usages.exudation + C_usages.Structure_roots + C_usages.Structure_shoot + C_usages.NS_phloem + C_usages.NS_other) / \ C_usages.C_produced.replace(0, np.nan).to_numpy() # ----- Graph fig, ax = plt.subplots() ax.plot(C_usages.t, C_usages.Structure_shoot / C_usages.C_produced * 100, label=u'Structural mass - Shoot', color='g') ax.plot(C_usages.t, C_usages.Structure_roots / C_usages.C_produced * 100, label=u'Structural mass - Roots', color='r') ax.plot(C_usages.t, (C_usages.NS_phloem + C_usages.NS_other) / C_usages.C_produced * 100, label=u'Non-structural C', color='darkorange') ax.plot(C_usages.t, (C_usages.Respi_roots + C_usages.Respi_shoot) / C_usages.C_produced.replace(0, np.nan).to_numpy() * 100, label=u'C loss by respiration', color='b') ax.plot(C_usages.t, C_usages.exudation / C_usages.C_produced.replace(0, np.nan).to_numpy() * 100, label=u'C loss by exudation', color='c') ax.legend(loc='center left', bbox_to_anchor=(1, 0.5)) ax.set_xlabel('Time (h)') ax.set_ylabel(u'Carbon usages : Photosynthesis (%)') ax.set_ylim(bottom=0, top=100.) fig.suptitle(u'Total cumulated usages are ' + str(round(C_usages.C_budget.tail(1) * 100, 0)) + u' % of Photosynthesis') plt.savefig(os.path.join(GRAPHS_DIRPATH, 'C_usages_cumulated.PNG'), format='PNG', bbox_inches='tight') plt.close() # 6) RUE if not elements_postprocessing.empty: elements_postprocessing['PARa_MJ'] = elements_postprocessing['PARa'] * elements_postprocessing['green_area'] * elements_postprocessing['nb_replications'] * 3600 / 4.6 * 10 ** -6 # Il faudrait idealement utiliser les calculcs green_area et PARa des talles elements_postprocessing['RGa_MJ'] = elements_postprocessing['PARa'] * elements_postprocessing['green_area'] * elements_postprocessing['nb_replications'] * 3600 / 2.02 * 10 ** -6 # Il faudrait idealement utiliser les calculcs green_area et PARa des talles PARa = elements_postprocessing.groupby(['day'])['PARa_MJ'].agg('sum') PARa_cum = np.cumsum(PARa) days = elements_postprocessing['day'].unique() sum_dry_mass_shoot = axes_postprocessing.groupby(['day'])['sum_dry_mass_shoot'].agg('max') sum_dry_mass = axes_postprocessing.groupby(['day'])['sum_dry_mass'].agg('max') # RUE_shoot = np.polyfit(PARa_cum, sum_dry_mass_shoot, 1)[0] sum_dry_mass_shoot_numeric = pd.to_numeric(sum_dry_mass_shoot, errors='coerce') sum_dry_mass_plant_numeric = pd.to_numeric(sum_dry_mass, errors='coerce') para_cum_series = pd.Series(PARa_cum, index=PARa.index) df_temp_shoot = pd.DataFrame({'X': para_cum_series, 'Y': sum_dry_mass_shoot_numeric}).dropna() df_temp_plant = pd.DataFrame({'X': para_cum_series, 'Y': sum_dry_mass_plant_numeric}).dropna() RUE_shoot = np.polyfit(df_temp_shoot['X'], df_temp_shoot['Y'], 1)[0] RUE_plant = np.polyfit(df_temp_plant['X'], df_temp_plant['Y'], 1)[0] fig, ax = plt.subplots() ax.plot(PARa_cum, sum_dry_mass_shoot.dropna(), label='Shoot dry mass (g)') ax.plot(PARa_cum, sum_dry_mass.dropna(), label='Plant dry mass (g)') ax.legend(prop={'size': 10}, framealpha=0.5, loc='center left', bbox_to_anchor=(1, 0.815), borderaxespad=0.) ax.set_xlabel('Cumulative absorbed PAR (MJ)') ax.set_ylabel('Dry mass (g)') ax.set_title('RUE') plt.text(max(PARa_cum) * 0.02, max(sum_dry_mass) * 0.95, 'RUE shoot : {0:.2f} , RUE plant : {1:.2f}'.format(round(RUE_shoot, 2), round(RUE_plant, 2))) plt.savefig(os.path.join(GRAPHS_DIRPATH, 'RUE.PNG'), dpi=200, format='PNG', bbox_inches='tight') fig, ax = plt.subplots() ax.plot(days, sum_dry_mass_shoot.dropna(), label='Shoot dry mass (g)') ax.plot(days, sum_dry_mass.dropna(), label='Plant dry mass (g)') ax.plot(days, PARa_cum, label='Cumulative absorbed PAR (MJ)') ax.legend(prop={'size': 10}, framealpha=0.5, loc='center left', bbox_to_anchor=(1, 0.815), borderaxespad=0.) ax.set_xlabel('Days') ax.set_title('RUE investigations') plt.savefig(os.path.join(GRAPHS_DIRPATH, 'RUE2.PNG'), dpi=200, format='PNG', bbox_inches='tight') # 7) Sum thermal time fig, ax = plt.subplots() ax.plot(df_MS['t'], df_MS['sum_TT']) ax.set_xlabel('Hours') ax.set_ylabel('Thermal Time') ax.set_title('Thermal Time') plt.savefig(os.path.join(GRAPHS_DIRPATH, 'SumTT.PNG'), dpi=200, format='PNG', bbox_inches='tight') # 7) Residual N : ratio_N_mstruct_max df_elt_MS = elements_outputs.loc[elements_outputs.axis == 'MS'] df_elt_MS = df_elt_MS.loc[df_elt_MS.mstruct != 0] df_elt_MS['N_content_total'] = df_elt_MS['N_content_total'] * 100 x_name = 't' x_label = 'Time (Hour)' graph_variables_ph_elements = {'N_content_total': u'N content in green + senesced tissues (% mstruct)'} for org_ph in (['blade'], ['sheath'], ['internode'], ['peduncle', 'ear']): for variable_name, variable_label in graph_variables_ph_elements.items(): graph_name = variable_name + '_' + '_'.join(org_ph) + '.PNG' plot_outputs(elements_outputs, x_name=x_name, y_name=variable_name, x_label=x_label, y_label=variable_label, colors=[colors[i - 1] for i in elements_outputs.metamer.unique().tolist()], filters={'organ': org_ph}, plot_filepath=os.path.join(GRAPHS_DIRPATH, graph_name), explicit_label=False)
[docs] def color_MTG_Nitrogen(g, df, t, SCREENSHOT_DIRPATH): def color_map(N): if 0 <= N <= 0.5: # TODO: organe senescent (prendre prop) vid_colors = [150, 100, 0] elif 0.5 < N < 5: # Fvertes vid_colors = [int(255 - N*51), int(255 - N * 20), 50] else: vid_colors = [0, 155, 0] return vid_colors def calculate_Total_Organic_Nitrogen(amino_acids, proteins, Nstruct): """Total amount of organic N (amino acids + proteins + Nstruct). :param float amino_acids: Amount of amino acids (µmol N) :param float proteins: Amount of proteins (µmol N) :param float Nstruct: Structural N mass (g) :return: Total amount of organic N (mg) :rtype: float """ return (amino_acids + proteins) * 14E-3 + Nstruct * 1E3 colors = {} groups_df = df.groupby(['plant', 'axis', 'metamer', 'organ', 'element']) for vid in g.components_at_scale(g.root, scale=5): pid = int(g.index(g.complex_at_scale(vid, scale=1))) axid = g.property('label')[g.complex_at_scale(vid, scale=2)] mid = int(g.index(g.complex_at_scale(vid, scale=3))) org = g.property('label')[g.complex_at_scale(vid, scale=4)] elid = g.property('label')[vid] id_map = (pid, axid, mid, org, elid) if id_map in groups_df.groups.keys(): N = (g.property('proteins')[vid] * 14E-3) / groups_df.get_group(id_map)['mstruct'].iloc[0] # N = (calculate_Total_Organic_Nitrogen(g.property('amino_acids')[vid], g.property('proteins')[vid], g.property('Nstruct')[vid])) / g.property('mstruct')[vid] colors[vid] = color_map(N) else: g.property('geometry')[vid] = None # plantgl s = to_plantgl(g, colors=colors)[0] Viewer.add(s) Viewer.camera.setPosition(Vector3(83.883, 12.3239, 93.4706)) Viewer.camera.lookAt(Vector3(0., 0, 50)) Viewer.saveSnapshot(os.path.join(SCREENSHOT_DIRPATH, 'Day_{}.png'.format(t/24+1)))
[docs] def compare_actual_to_desired(data_dirpath, actual_data_df, desired_data_filename, actual_data_filename=None, precision=4, overwrite_desired_data=False): """Compare difference = actual_data_df - desired_data_df to tolerance = 10**-precision * (1 + abs(desired_data_df)) where desired_data_df = pd.read_csv(os.path.join(data_dirpath, desired_data_filename)) If difference > tolerance, then raise an AssertionError. :param str data_dirpath: The path of the directory where to find the data to compare. :param pandas.DataFrame actual_data_df: The computed data. :param str desired_data_filename: The file name of the expected data. :param str actual_data_filename: If not None, save the computed data to `actual_data_filename`, in directory `data_dirpath`. Default is None. :param int precision: The precision to use for the comparison. Default is `4`. :param bool overwrite_desired_data: If True the comparison between actual and desired data is not run. Instead, the desired data will be overwritten using actual data. To be used with caution. """ relative_tolerance = 10 ** -precision absolute_tolerance = relative_tolerance # read desired data desired_data_filepath = os.path.join(data_dirpath, desired_data_filename) desired_data_df = pd.read_csv(desired_data_filepath) if actual_data_filename is not None: # save actual outputs to CSV file actual_data_filepath = os.path.join(data_dirpath, actual_data_filename) actual_data_df.to_csv(actual_data_filepath, na_rep='NA', index=False, float_format='%.{}f'.format(precision)) if overwrite_desired_data: warnings.warn('!!! Unit test is running with overwrite_desired_data !!!') desired_data_filepath = os.path.join(data_dirpath, desired_data_filename) actual_data_df.to_csv(desired_data_filepath, na_rep='NA', index=False) else: # keep only numerical data (np.testing can compare only numerical data) for column in ('axis', 'organ', 'element', 'is_growing'): if column in desired_data_df.columns: del desired_data_df[column] del actual_data_df[column] # convert the actual outputs to floats actual_data_df = actual_data_df.astype(np.float) # compare actual data to desired data np.testing.assert_allclose(actual_data_df.values, desired_data_df.values, relative_tolerance, absolute_tolerance)