From 2c35c255681a1df57583e78b4d2395bc5db21350 Mon Sep 17 00:00:00 2001 From: pradal Date: Thu, 4 Jun 2026 09:44:34 +0200 Subject: [PATCH 01/35] First version for openalea configuration --- src/openalea/core/config.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/openalea/core/config.py diff --git a/src/openalea/core/config.py b/src/openalea/core/config.py new file mode 100644 index 0000000..02ad891 --- /dev/null +++ b/src/openalea/core/config.py @@ -0,0 +1,24 @@ +# -*- python -*- +# +# OpenAlea.Core +# +# Copyright 2006-2026 CIRAD - INRAE - inria +# +# File author(s): Leticia Napolitano +# Christophe Pradal +# Fabrice Bauget +# +# Distributed under the Cecill-C License. +# See accompanying file LICENSE.txt or copy at +# http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html +# +# OpenAlea WebSite : http://openalea.gforge.inria.fr +# +############################################################################## +"""Configuration of OpenAlea models""" + +__license__ = "Cecill-C" + +from openalea.core.singleton import Singleton +from openalea.core.observer import Observed + From 37d32826a10743ac07669bcb0479585d084af271 Mon Sep 17 00:00:00 2001 From: pradal Date: Thu, 4 Jun 2026 10:03:32 +0200 Subject: [PATCH 02/35] First try --- src/openalea/core/config.py | 66 +++++++++++++++++++++++++++++++++++++ test/test_config.py | 23 +++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 test/test_config.py diff --git a/src/openalea/core/config.py b/src/openalea/core/config.py index 02ad891..9488f7d 100644 --- a/src/openalea/core/config.py +++ b/src/openalea/core/config.py @@ -19,6 +19,72 @@ __license__ = "Cecill-C" + from openalea.core.singleton import Singleton from openalea.core.observer import Observed +class Config: + """Configuration of OpenAlea models + + TODO : Documentation to write + """ + def __init__(self, model_unit_configs): + """Initialize the configuration with a list of unit configurations.""" + self.model_unit_configs = model_unit_configs + + @staticmethod + def load(self, filename: str): + """Load configuration from a file. + + Dispatch method based on file extension (YAML, JSON, etc.). + """ + return Config() + + def dump(self, filename: str): + """Dump configuration to a file.""" + + def _load_yml(self, filename: str): + """Load configuration from a YAML file.""" + + def _dump_yml(self, filename: str): + """Dump configuration to a YAML file.""" + + def _load_json(self, filename: str): + """Load configuration from a JSON file.""" + + def _dump_json(self, filename: str): + """Dump configuration to a JSON file.""" + + +# update() +# to_dict() +# build_model() --create a model object +# generate() + +# class ModelUnitConfig +# [Parameters] +# name +# uid +# uri +# description +# validate() --validation des sections +# to_dict() + +# class Parameter +# name +# value +# unit +# type +# description +# default value +# uid +# uri + +# class ModelConfig +# [ModelUnitConfig] +# name +# def MyModel() +# p = params() --recover parameters +# c = Config(p) +# c.generate('config.yml') +# return Model(c) \ No newline at end of file diff --git a/test/test_config.py b/test/test_config.py new file mode 100644 index 0000000..07ddb55 --- /dev/null +++ b/test/test_config.py @@ -0,0 +1,23 @@ +from __future__ import absolute_import +import os + +from openalea.core.config import Config + +class MyModelUnit: + def __init__(self, name, params={}): + self.name = name + + +def test_config1(): + + + unit1 = MyModelUnit('unit1', dict(p1=1, p2='2')) + unit2 = MyModelUnit('unit2', dict(p1=1, p2='2') + + config = Config([unit1, unit2]) + config.dump('toto.yml') + + assert(len(config) == 2) + assert(len(config['unit1'])==2) + + \ No newline at end of file From 7b5315c66f2690fea3483fdb1f6a3242323fc8b2 Mon Sep 17 00:00:00 2001 From: pradal Date: Thu, 4 Jun 2026 10:06:57 +0200 Subject: [PATCH 03/35] Fix syntax error --- test/test_config.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/test_config.py b/test/test_config.py index 07ddb55..c1b2d91 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -12,7 +12,7 @@ def test_config1(): unit1 = MyModelUnit('unit1', dict(p1=1, p2='2')) - unit2 = MyModelUnit('unit2', dict(p1=1, p2='2') + unit2 = MyModelUnit('unit2', dict(p1=1, p2='2')) config = Config([unit1, unit2]) config.dump('toto.yml') @@ -20,4 +20,3 @@ def test_config1(): assert(len(config) == 2) assert(len(config['unit1'])==2) - \ No newline at end of file From 73d4c67e82ba3e46db3be022e03d048e110e6c13 Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Thu, 4 Jun 2026 15:30:11 +0200 Subject: [PATCH 04/35] i add 2 news classes Parameter and MyModelUnit and many functions in config.py --- src/openalea/core/config.py | 69 ++++++++++++++++++++++++++++++------- test/test_config.py | 56 +++++++++++++++++++++++++++--- test/toto.json | 10 ++++++ 3 files changed, 119 insertions(+), 16 deletions(-) create mode 100644 test/toto.json diff --git a/src/openalea/core/config.py b/src/openalea/core/config.py index 9488f7d..0e986d8 100644 --- a/src/openalea/core/config.py +++ b/src/openalea/core/config.py @@ -19,41 +19,86 @@ __license__ = "Cecill-C" - +import yaml +import json from openalea.core.singleton import Singleton from openalea.core.observer import Observed +from pathlib import Path + +def _load_json(filename: str): + """Load configuration from a JSON file.""" + + file = Path(filename) + with file.open() as f: + data = json.load(f) + return data + +def _load_yml(filename: str): + """Load configuration from a YAML file.""" + + file = Path(filename) + with file.open() as f: + data = yaml.load(f, Loader=yaml.SafeLoader) + return data + +def _dump_yml(data, filename: str): + """Dump configuration to a YAML file.""" + + file = Path(filename) + with file.open("w") as f: + yaml.dump(data, f) + +def _dump_json(data, filename: str): + """Dump configuration to a JSON file.""" + + file = Path(filename) + with file.open("w") as f: + json.dump(data, f) + class Config: """Configuration of OpenAlea models TODO : Documentation to write """ - def __init__(self, model_unit_configs): + def __init__(self, model_unit_configs: dict): """Initialize the configuration with a list of unit configurations.""" self.model_unit_configs = model_unit_configs - @staticmethod + def __str__(self): + return f"{self.model_unit_configs}" + + #@staticmethod def load(self, filename: str): """Load configuration from a file. Dispatch method based on file extension (YAML, JSON, etc.). """ - return Config() + + extension = filename.split(".")[-1] + + if extension in ("yml", "yaml"): + data = _load_yml(filename) + + elif extension == "json": + data = _load_json(filename) + + print(data) + + return Config(data) def dump(self, filename: str): """Dump configuration to a file.""" - def _load_yml(self, filename: str): - """Load configuration from a YAML file.""" + extension = filename.split(".")[-1] - def _dump_yml(self, filename: str): - """Dump configuration to a YAML file.""" + if extension in ("yml", "yaml"): + _dump_yml(self.model_unit_configs, filename) - def _load_json(self, filename: str): - """Load configuration from a JSON file.""" + elif extension == "json": + _dump_json(self.model_unit_configs, filename) - def _dump_json(self, filename: str): - """Dump configuration to a JSON file.""" + # update() diff --git a/test/test_config.py b/test/test_config.py index c1b2d91..a53892a 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -3,20 +3,68 @@ from openalea.core.config import Config +import yaml +import json + +class Parameter: + def __init__(self, name, value=None, unit=None, param_type=None, description="", uid=None, uri=None): + self.name = name + self.value = value + self.unit = unit + self.param_type = param_type + self.description = description + self.uid = uid + self.uri = uri + + def to_dict(self): + return { + "name":self.name, + "value": self.value, + "unit": self.unit, + "param_type": self.param_type, + "description": self.description, + "uid": self.uid, + "uri": self.uri + } + class MyModelUnit: - def __init__(self, name, params={}): + def __init__(self, name, parameters: dict): self.name = name + self.parameters=parameters + + def to_dict(self): + return { + self.name : self.parameters + } + + def __str__(self): + return f"name={self.name}, parameters={self.parameters}" def test_config1(): - unit1 = MyModelUnit('unit1', dict(p1=1, p2='2')) unit2 = MyModelUnit('unit2', dict(p1=1, p2='2')) - config = Config([unit1, unit2]) - config.dump('toto.yml') + p1= Parameter("numerical", 12, "cm", "cm", "mesurer", 234, 1223) + unit3=MyModelUnit('unit3', p1.to_dict()) + print(unit3) + + config1 = Config(unit3.to_dict()) + print(config1) + config1.dump("tototo.json") + + #config1 = Config([unit1, unit2]) + #config1.dump("toto.json") + config2 = Config.load(unit1, "toto.json") + + ''' assert(len(config) == 2) assert(len(config['unit1'])==2) + ''' + +test_config1() + + diff --git a/test/toto.json b/test/toto.json new file mode 100644 index 0000000..6b3b979 --- /dev/null +++ b/test/toto.json @@ -0,0 +1,10 @@ +{ + "unit1": { + "p1": 1, + "p2": "2" + }, + "unit2": { + "p1": 3, + "p2": "4" + } +} From 542175dd85d93aad8c59e11011f0057be1e7dfce Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Fri, 5 Jun 2026 12:37:52 +0200 Subject: [PATCH 05/35] adding pyyaml --- conda/meta.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conda/meta.yaml b/conda/meta.yaml index 91a6421..10e51da 100644 --- a/conda/meta.yaml +++ b/conda/meta.yaml @@ -25,12 +25,14 @@ build: requirements: host: - python + - pyyaml {% for dep in build_deps %} - {{ dep }} {% endfor %} run: - python + - pyyaml {% for dep in deps + conda_deps %} - {{ dep }} {% endfor %} From 2c3e8e112dc42378991b201e0b06fcf389e314fa Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Fri, 5 Jun 2026 12:48:19 +0200 Subject: [PATCH 06/35] Update meta.yaml adding pyyaml --- conda/meta.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conda/meta.yaml b/conda/meta.yaml index 91a6421..10e51da 100644 --- a/conda/meta.yaml +++ b/conda/meta.yaml @@ -25,12 +25,14 @@ build: requirements: host: - python + - pyyaml {% for dep in build_deps %} - {{ dep }} {% endfor %} run: - python + - pyyaml {% for dep in deps + conda_deps %} - {{ dep }} {% endfor %} From f60d764c36e85500fcfdd109d20f9ba32b8932f2 Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Fri, 5 Jun 2026 12:58:25 +0200 Subject: [PATCH 07/35] i add pyyaml in dependencies --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 63bfc18..58da666 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,7 @@ dependencies = [ "configobj", "six", "setuptools", + "pyyaml", ] [tool.setuptools.dynamic] From 2b5091d57664d1fb9d5ad9a7b683dc0e059638ea Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Fri, 5 Jun 2026 14:37:31 +0200 Subject: [PATCH 08/35] i made some test to create a params.json file with one example of HydroShoot params and the format is the same --- test/test_config.py | 281 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 264 insertions(+), 17 deletions(-) diff --git a/test/test_config.py b/test/test_config.py index a53892a..10398c1 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -18,24 +18,22 @@ def __init__(self, name, value=None, unit=None, param_type=None, description="", def to_dict(self): return { - "name":self.name, - "value": self.value, - "unit": self.unit, - "param_type": self.param_type, - "description": self.description, - "uid": self.uid, - "uri": self.uri + self.name:self.value } + def __str__(self): + return f"name={self.name}, value={self.value}, unit={self.unit}, param_type={self.param_type}, description={self.description}, uid={self.uid}, uri={self.uri}" + class MyModelUnit: def __init__(self, name, parameters: dict): self.name = name self.parameters=parameters - + def to_dict(self): return { self.name : self.parameters } + def __str__(self): return f"name={self.name}, parameters={self.parameters}" @@ -43,21 +41,270 @@ def __str__(self): def test_config1(): - unit1 = MyModelUnit('unit1', dict(p1=1, p2='2')) - unit2 = MyModelUnit('unit2', dict(p1=1, p2='2')) + #unit1 = MyModelUnit('unit1', dict(p1=1, p2='2')) + #unit2 = MyModelUnit('unit2', dict(p1=1, p2='2')) - p1= Parameter("numerical", 12, "cm", "cm", "mesurer", 234, 1223) + #p1= Parameter("numerical", 12, "cm", "cm", "mesurer", 234, 1223) - unit3=MyModelUnit('unit3', p1.to_dict()) - print(unit3) + #unit3=MyModelUnit('unit3', p1.to_dict()) + #print(unit3) - config1 = Config(unit3.to_dict()) - print(config1) - config1.dump("tototo.json") + #config1 = Config(unit3.to_dict()) + #print(config1) + #config1.dump("tototo.json") #config1 = Config([unit1, unit2]) #config1.dump("toto.json") - config2 = Config.load(unit1, "toto.json") + #config2 = Config.load(unit1, "toto.json") + + print("Test avec params.json de HydroShoot\n") + + # Creation of the simulation section + + p_sdate = Parameter("sdate", "2012-08-01 00:00:00") + p_edate = Parameter("edate", "2012-08-04 23:00:00") + p_lat = Parameter("latitude", 43.61) + p_longitude = Parameter("longitude", 3.87) + p_elevation = Parameter("elevation", 44.0) + p_tzone = Parameter("tzone", "Europe/Paris") + p_output_index = Parameter("output_index", "") + p_unit_scene_length = Parameter("unit_scene_length", "cm") + p_hydraulic_structure = Parameter("hydraulic_structure", True) + p_negligible_shoot_resistance = Parameter("negligible_shoot_resistance", False) + p_energy_budget = Parameter("energy_budget", True) + + + d1 = {} + d1.update(p_sdate.to_dict()) + d1.update(p_edate.to_dict()) + d1.update(p_lat.to_dict()) + d1.update(p_longitude.to_dict()) + d1.update(p_elevation.to_dict()) + d1.update(p_tzone.to_dict()) + d1.update(p_output_index.to_dict()) + d1.update(p_unit_scene_length.to_dict()) + d1.update(p_hydraulic_structure.to_dict()) + d1.update(p_negligible_shoot_resistance.to_dict()) + d1.update(p_energy_budget.to_dict()) + + + + unit_simulation = MyModelUnit('simulation', d1) + + # Creation of the planting section + + p_spacing_between_rows = Parameter("spacing_between_rows", 3.6) + p_spacing_on_row = Parameter("spacing_on_row", 1) + p_row_angle_with_south = Parameter("row_angle_with_south", 140.0) + + d2={} + d2.update(p_spacing_between_rows.to_dict()) + d2.update(p_spacing_on_row.to_dict()) + d2.update(p_row_angle_with_south.to_dict()) + + unit_planting_section = MyModelUnit('planting', d2) + + #Creation of the phenology section + + p_emdate = Parameter("emdate", "2012-04-01 00:00:00") + p_t_base = Parameter("t_base", 10.0) + + d3 = {} + d3.update(p_emdate.to_dict()) + d3.update(p_t_base.to_dict()) + + unit_phenology = MyModelUnit("phenology", d3) + + # Creation of the MTG API section + + p_collar_label = Parameter("collar_label", "inT") + p_leaf_lbl_prefix = Parameter("leaf_lbl_prefix", "L") + p_stem_lbl_prefix = Parameter("stem_lbl_prefix", ["in", "Pet", "cx"]) + + d4 = {} + d4.update(p_collar_label.to_dict()) + d4.update(p_leaf_lbl_prefix.to_dict()) + d4.update(p_stem_lbl_prefix.to_dict()) + + unit_mtg_api = MyModelUnit("mtg_api", d4) + + #Creation of the numerical resolution section + + p_max_iter = Parameter("max_iter", 100) + p_psi_step = Parameter("psi_step", 1.0) + p_psi_error_threshold = Parameter("psi_error_threshold", 0.05) + p_t_step = Parameter("t_step", 1.0) + p_t_error_threshold = Parameter("t_error_threshold", 0.02) + + d5 = {} + d5.update(p_max_iter.to_dict()) + d5.update(p_psi_step.to_dict()) + d5.update(p_psi_error_threshold.to_dict()) + d5.update(p_t_step.to_dict()) + d5.update(p_t_error_threshold.to_dict()) + + unit_numerical_resolution = MyModelUnit("numerical_resolution", d5) + + #Creation of the irradiance section + + p_E_type = Parameter("E_type", "Rg_Watt/m2") + p_E_type2 = Parameter("E_type2", "Eabs") + + p_opt_prop = Parameter("opt_prop", { + "SW": { + "leaf": [0.06, 0.07], + "stem": [0.13], + "other": [0.06, 0.07] + }, + "LW": { + "leaf": [0.04, 0.07], + "stem": [0.13], + "other": [0.06, 0.07] + } + }) + + p_turtle_format = Parameter("turtle_format", "soc") + p_turtle_sectors = Parameter("turtle_sectors", "46") + p_icosphere_level = Parameter("icosphere_level", None) + + d6 = {} + d6.update(p_E_type.to_dict()) + d6.update(p_E_type2.to_dict()) + d6.update(p_opt_prop.to_dict()) + d6.update(p_turtle_format.to_dict()) + d6.update(p_turtle_sectors.to_dict()) + d6.update(p_icosphere_level.to_dict()) + + unit_irradiance = MyModelUnit("irradiance", d6) + + #Creation of the energy section + + + p_solo = Parameter("solo", True) + p_t_cloud = Parameter("t_cloud", 2.0) + p_t_sky = Parameter("t_sky", -20.0) + + d7 = {} + d7.update(p_solo.to_dict()) + d7.update(p_t_cloud.to_dict()) + d7.update(p_t_sky.to_dict()) + + unit_energy = MyModelUnit("energy", d7) + + #Creation of the hydraulic section + + p_psi_min = Parameter("psi_min", -3.0) + + p_Kx_dict = Parameter("Kx_dict", { + "a": 1.6, + "b": 2.0, + "min_kmax": 0.000111 + }) + + p_par_K_vul = Parameter("par_K_vul", { + "model": "misson", + "fifty_cent": -0.76, + "sig_slope": 1.0 + }) + + d8 = {} + d8.update(p_psi_min.to_dict()) + d8.update(p_Kx_dict.to_dict()) + d8.update(p_par_K_vul.to_dict()) + + unit_hydraulic = MyModelUnit("hydraulic", d8) + + #Creation of the exchange section + + p_rbt = Parameter("rbt", 0.6667) + + p_Na_dict = Parameter("Na_dict", { + "aN": -0.0008, + "bN": 3.3, + "aM": 6.471, + "bM": 56.635 + }) + + p_par_gs = Parameter("par_gs", { + "model": "misson", + "g0": 0.0, + "m0": 7.3, + "psi0": -0.65, + "D0": 1.0, + "n": 4.0 + }) + + p_par_photo = Parameter("par_photo", { + "alpha": 0.24, + "Kc25": 404.9, + "Ko25": 278.4, + "Tx25": 42.75, + "ds": 0.635, + "dHd": 200.0, + "RespT_Kc": {"c": 38.05, "deltaHa": 79.43}, + "RespT_Ko": {"c": 20.30, "deltaHa": 36.38}, + "RespT_Vcm": {"c": 26.35, "deltaHa": 65.33}, + "RespT_Jm": {"c": 17.57, "deltaHa": 43.54}, + "RespT_TPU": {"c": 21.46, "deltaHa": 53.1}, + "RespT_Rd": {"c": 18.72, "deltaHa": 46.39}, + "RespT_Tx": {"c": 19.02, "deltaHa": 37.83} + }) + + p_par_photo_N = Parameter("par_photo_N", { + "Vcm25_N": [34.02, -3.13], + "Jm25_N": [78.27, -17.3], + "Rd_N": [0.42, -0.01], + "TPU25_N": [6.24, -1.92] + }) + + d9 = {} + d9.update(p_rbt.to_dict()) + d9.update(p_Na_dict.to_dict()) + d9.update(p_par_gs.to_dict()) + d9.update(p_par_photo.to_dict()) + d9.update(p_par_photo_N.to_dict()) + + unit_exchange = MyModelUnit("exchange", d9) + + #Creation of the soil section + + p_soil_class = Parameter("soil_class", "Sandy_Loam") + + p_soil_dimensions = Parameter("soil_dimensions", { + "width": 3.6, + "length": 1.0, + "depth": 1.2 + }) + + p_rhyzo_coeff = Parameter("rhyzo_coeff", 0.75) + + d10 = {} + d10.update(p_soil_class.to_dict()) + d10.update(p_soil_dimensions.to_dict()) + d10.update(p_rhyzo_coeff.to_dict()) + + unit_soil = MyModelUnit("soil", d10) + + + #Configuration + + config_dict = {} + + config_dict.update(unit_simulation.to_dict()) + config_dict.update(unit_planting_section.to_dict()) + config_dict.update(unit_phenology.to_dict()) + config_dict.update(unit_mtg_api.to_dict()) + config_dict.update(unit_numerical_resolution.to_dict()) + config_dict.update(unit_irradiance.to_dict()) + config_dict.update(unit_energy.to_dict()) + config_dict.update(unit_hydraulic.to_dict()) + config_dict.update(unit_exchange.to_dict()) + config_dict.update(unit_soil.to_dict()) + + + config3 = Config(config_dict) + print(config3) + config3.dump("params.json") ''' assert(len(config) == 2) From 9d61cae28cbeeac7ea8aea0554770d848edc4e04 Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Mon, 8 Jun 2026 11:29:18 +0200 Subject: [PATCH 09/35] [no ci] modification of the classes and some test from a params.json example --- test/test_config.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/test_config.py b/test/test_config.py index 10398c1..88a0e75 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -126,6 +126,9 @@ def test_config1(): d4.update(p_leaf_lbl_prefix.to_dict()) d4.update(p_stem_lbl_prefix.to_dict()) + # + + unit_mtg_api = MyModelUnit("mtg_api", d4) #Creation of the numerical resolution section From f355e7d54e373df355acc827d76495b6b38fdaaa Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Mon, 8 Jun 2026 15:09:23 +0200 Subject: [PATCH 10/35] modification of the indexation --- src/openalea/core/config.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/openalea/core/config.py b/src/openalea/core/config.py index 0e986d8..8857d05 100644 --- a/src/openalea/core/config.py +++ b/src/openalea/core/config.py @@ -41,23 +41,25 @@ def _load_yml(filename: str): data = yaml.load(f, Loader=yaml.SafeLoader) return data -def _dump_yml(data, filename: str): +def _dump_yml(data, filename: str, sort_keys=False): """Dump configuration to a YAML file.""" file = Path(filename) with file.open("w") as f: - yaml.dump(data, f) + yaml.dump(data, f, sort_keys=sort_keys) def _dump_json(data, filename: str): """Dump configuration to a JSON file.""" file = Path(filename) with file.open("w") as f: - json.dump(data, f) + json.dump(data, f, indent=4) class Config: """Configuration of OpenAlea models + + TODO : Documentation to write """ From d14b98725cb8b8736ae27ef02fb31c471fd23add Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Mon, 8 Jun 2026 15:10:14 +0200 Subject: [PATCH 11/35] I do some test with yml --- test/test_config.py | 49 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/test/test_config.py b/test/test_config.py index 88a0e75..7316b75 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -57,6 +57,8 @@ def test_config1(): #config1.dump("toto.json") #config2 = Config.load(unit1, "toto.json") + '''TEST WITH JSON''' + print("Test avec params.json de HydroShoot\n") # Creation of the simulation section @@ -127,7 +129,7 @@ def test_config1(): d4.update(p_stem_lbl_prefix.to_dict()) # - + unit_mtg_api = MyModelUnit("mtg_api", d4) @@ -308,7 +310,52 @@ def test_config1(): config3 = Config(config_dict) print(config3) config3.dump("params.json") + + '''TEST WITH YML''' + + config4 = Config(config_dict) + config4.dump("params.yml") + + # Creation of the simulation section + + p_sdate1 = Parameter("sdate", "2012-08-01 00:00:00") + p_edate1 = Parameter("edate", "2012-08-04 23:00:00") + p_lat1 = Parameter("latitude", 43.61) + p_longitude1 = Parameter("longitude", 3.87) + p_elevation1 = Parameter("elevation", 44.0) + + d11 = {} + d11.update(p_sdate.to_dict()) + d11.update(p_edate.to_dict()) + d11.update(p_lat.to_dict()) + d11.update(p_longitude.to_dict()) + d11.update(p_elevation.to_dict()) + unit_simulation1 = MyModelUnit('simulation', d11) + + # Creation of the planting section + + p_spacing_between_rows1 = Parameter("spacing_between_rows", 3.6) + p_spacing_on_row1 = Parameter("spacing_on_row", 1) + p_row_angle_with_south1 = Parameter("row_angle_with_south", 140.0) + + d12={} + d12.update(p_spacing_between_rows.to_dict()) + d12.update(p_spacing_on_row.to_dict()) + d12.update(p_row_angle_with_south.to_dict()) + + unit_planting_section1 = MyModelUnit('planting', d12) + + #Configuration + + config_dict1 = {} + + config_dict1.update(unit_simulation1.to_dict()) + config_dict1.update(unit_planting_section1.to_dict()) + + config5 = Config(config_dict1) + config5.dump("paramss.yml") + ''' assert(len(config) == 2) assert(len(config['unit1'])==2) From 486ac6bb5a69503c495e09880361d590d78b3a13 Mon Sep 17 00:00:00 2001 From: pradal Date: Wed, 10 Jun 2026 13:27:15 +0200 Subject: [PATCH 12/35] Update the tests --- test/test_config.py | 77 +++++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/test/test_config.py b/test/test_config.py index 7316b75..3bde203 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -25,20 +25,47 @@ def __str__(self): return f"name={self.name}, value={self.value}, unit={self.unit}, param_type={self.param_type}, description={self.description}, uid={self.uid}, uri={self.uri}" class MyModelUnit: - def __init__(self, name, parameters: dict): + def __init__(self, name, parameters: list): self.name = name self.parameters=parameters def to_dict(self): + params = {k : v for param in self.parameters for k, v in param.to_dict().items()} return { - self.name : self.parameters + self.name : params } def __str__(self): - return f"name={self.name}, parameters={self.parameters}" + return f"name={self.name}, parameters={self.to_dict()}" + +def hydroshoot_simulation_config(): + + p_sdate = Parameter("sdate", "2012-08-01 00:00:00") + p_edate = Parameter("edate", "2012-08-04 23:00:00") + p_lat = Parameter("latitude", 43.61) + p_longitude = Parameter("longitude", 3.87) + p_elevation = Parameter("elevation", 44.0) + p_tzone = Parameter("tzone", "Europe/Paris") + p_output_index = Parameter("output_index", "") + p_unit_scene_length = Parameter("unit_scene_length", "cm") + p_hydraulic_structure = Parameter("hydraulic_structure", True) + p_negligible_shoot_resistance = Parameter("negligible_shoot_resistance", False) + p_energy_budget = Parameter("energy_budget", True) + parameters = [p_sdate, p_edate, p_lat, p_longitude, p_elevation, p_tzone, p_output_index, p_unit_scene_length, p_hydraulic_structure, p_negligible_shoot_resistance, p_energy_budget] + + simulation = MyModelUnit('simulation', parameters) + return simulation + +def test_config_hs_simu(): + unit = hydroshoot_simulation_config() + config = Config([unit]) + + # tests + assert(len(config) == 1) + assert(len(config['simulation'])==11) + - def test_config1(): #unit1 = MyModelUnit('unit1', dict(p1=1, p2='2')) @@ -59,63 +86,38 @@ def test_config1(): '''TEST WITH JSON''' - print("Test avec params.json de HydroShoot\n") + print("Test params.json of HydroShoot\n") # Creation of the simulation section - p_sdate = Parameter("sdate", "2012-08-01 00:00:00") - p_edate = Parameter("edate", "2012-08-04 23:00:00") - p_lat = Parameter("latitude", 43.61) - p_longitude = Parameter("longitude", 3.87) - p_elevation = Parameter("elevation", 44.0) - p_tzone = Parameter("tzone", "Europe/Paris") - p_output_index = Parameter("output_index", "") - p_unit_scene_length = Parameter("unit_scene_length", "cm") - p_hydraulic_structure = Parameter("hydraulic_structure", True) - p_negligible_shoot_resistance = Parameter("negligible_shoot_resistance", False) - p_energy_budget = Parameter("energy_budget", True) - - d1 = {} - d1.update(p_sdate.to_dict()) - d1.update(p_edate.to_dict()) - d1.update(p_lat.to_dict()) - d1.update(p_longitude.to_dict()) - d1.update(p_elevation.to_dict()) - d1.update(p_tzone.to_dict()) - d1.update(p_output_index.to_dict()) - d1.update(p_unit_scene_length.to_dict()) - d1.update(p_hydraulic_structure.to_dict()) - d1.update(p_negligible_shoot_resistance.to_dict()) - d1.update(p_energy_budget.to_dict()) + - unit_simulation = MyModelUnit('simulation', d1) + unit_simulation = hydroshoot_simulation_config() # Creation of the planting section p_spacing_between_rows = Parameter("spacing_between_rows", 3.6) p_spacing_on_row = Parameter("spacing_on_row", 1) p_row_angle_with_south = Parameter("row_angle_with_south", 140.0) + parameters2 = [p_spacing_between_rows, p_spacing_on_row, p_row_angle_with_south] - d2={} - d2.update(p_spacing_between_rows.to_dict()) - d2.update(p_spacing_on_row.to_dict()) - d2.update(p_row_angle_with_south.to_dict()) - - unit_planting_section = MyModelUnit('planting', d2) + + unit_planting_section = MyModelUnit('planting', parameters2) #Creation of the phenology section p_emdate = Parameter("emdate", "2012-04-01 00:00:00") p_t_base = Parameter("t_base", 10.0) + p3 = [p_emdate, p_t_base] d3 = {} d3.update(p_emdate.to_dict()) d3.update(p_t_base.to_dict()) - unit_phenology = MyModelUnit("phenology", d3) + unit_phenology = MyModelUnit("phenology", p3) # Creation of the MTG API section @@ -123,6 +125,7 @@ def test_config1(): p_leaf_lbl_prefix = Parameter("leaf_lbl_prefix", "L") p_stem_lbl_prefix = Parameter("stem_lbl_prefix", ["in", "Pet", "cx"]) + p4 = [p_collar_label, p_leaf_lbl_prefix, p_stem_lbl_prefix] d4 = {} d4.update(p_collar_label.to_dict()) d4.update(p_leaf_lbl_prefix.to_dict()) From eaae10c694f71e693b915fc26a4ea02c1488563b Mon Sep 17 00:00:00 2001 From: pradal Date: Wed, 10 Jun 2026 13:39:26 +0200 Subject: [PATCH 13/35] Update the tests --- test/test_config.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/test_config.py b/test/test_config.py index 3bde203..b3a70ea 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -364,7 +364,11 @@ def test_config1(): assert(len(config['unit1'])==2) ''' -test_config1() +def test_read_cnofig(): + config = load_config("params.json") + config.dump("params1.json") + # compare both + print(config) From b55124706347e853230c7a395b9414243142d4b7 Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Wed, 10 Jun 2026 15:26:37 +0200 Subject: [PATCH 14/35] i add new functions and made some test --- test/test_config.py | 75 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 14 deletions(-) diff --git a/test/test_config.py b/test/test_config.py index b3a70ea..a2d06f6 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -35,7 +35,6 @@ def to_dict(self): self.name : params } - def __str__(self): return f"name={self.name}, parameters={self.to_dict()}" @@ -55,17 +54,73 @@ def hydroshoot_simulation_config(): parameters = [p_sdate, p_edate, p_lat, p_longitude, p_elevation, p_tzone, p_output_index, p_unit_scene_length, p_hydraulic_structure, p_negligible_shoot_resistance, p_energy_budget] simulation = MyModelUnit('simulation', parameters) + return simulation -def test_config_hs_simu(): +def hydroshoot_planting_config(): + p_spacing_between_rows = Parameter("spacing_between_rows", 3.6) + p_spacing_on_row = Parameter("spacing_on_row", 1) + p_row_angle_with_south = Parameter("row_angle_with_south", 140.0) + + parameters = [p_spacing_between_rows, p_spacing_on_row, p_row_angle_with_south] + planting = MyModelUnit('planting', parameters) + + return planting + +def hydroshoot_phenology_config(): + p_emdate = Parameter("emdate", "2012-04-01 00:00:00") + p_t_base = Parameter("t_base", 10.0) + + parameters = [p_emdate, p_t_base] + phenology = MyModelUnit('phenology', parameters) + + return phenology + +def test_config_hs(): unit = hydroshoot_simulation_config() config = Config([unit]) + config.dump("params3.json") # tests + assert(len(config) == 1) assert(len(config['simulation'])==11) + planting = hydroshoot_planting_config() + config.add_section(planting) + config.dump("params3.json") + + assert len(config) == 2 + assert len(config["planting"]) == 3 + + phenology = hydroshoot_phenology_config() + config.add_section(phenology) + config.dump("params3.json") + + #assert len(config) == 3 + #assert len(config["planting"]) == 2 + + + + + + +'''def test_config_hs_simu_plant(): + simu = hydroshoot_simulation_config() + planting = hydroshoot_planting_config() + config = Config([simu, planting]) + config.dump("params2.json") + + assert len(config) == 2 + assert len(config["simulation"]) == 11 + assert len(config["planting"]) == 3''' + + +test_config_hs() +#test_config_hs_simu_plant() + +''' def test_config1(): #unit1 = MyModelUnit('unit1', dict(p1=1, p2='2')) @@ -84,17 +139,12 @@ def test_config1(): #config1.dump("toto.json") #config2 = Config.load(unit1, "toto.json") - '''TEST WITH JSON''' + print("Test params.json of HydroShoot\n") # Creation of the simulation section - - - - - unit_simulation = hydroshoot_simulation_config() # Creation of the planting section @@ -314,8 +364,6 @@ def test_config1(): print(config3) config3.dump("params.json") - '''TEST WITH YML''' - config4 = Config(config_dict) config4.dump("params.yml") @@ -359,10 +407,7 @@ def test_config1(): config5 = Config(config_dict1) config5.dump("paramss.yml") - ''' - assert(len(config) == 2) - assert(len(config['unit1'])==2) - ''' + def test_read_cnofig(): config = load_config("params.json") @@ -370,5 +415,7 @@ def test_read_cnofig(): # compare both print(config) + ''' + From 59273ee546a176c294cb28394efa59e85fa709a5 Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Wed, 10 Jun 2026 15:27:34 +0200 Subject: [PATCH 15/35] i add 3 new functions and dict --- src/openalea/core/config.py | 61 +++++++++++++------------------------ 1 file changed, 21 insertions(+), 40 deletions(-) diff --git a/src/openalea/core/config.py b/src/openalea/core/config.py index 8857d05..42fc97b 100644 --- a/src/openalea/core/config.py +++ b/src/openalea/core/config.py @@ -58,25 +58,39 @@ def _dump_json(data, filename: str): class Config: """Configuration of OpenAlea models - - TODO : Documentation to write """ - def __init__(self, model_unit_configs: dict): + def __init__(self, model_unit_configs: list): """Initialize the configuration with a list of unit configurations.""" self.model_unit_configs = model_unit_configs + def to_dict(self): + dict = {} + for unit in self.model_unit_configs: + dict.update(unit.to_dict()) + return dict + + def __len__(self): + return len(self.model_unit_configs) + + def __getitem__(self, key): + return self.to_dict()[key] + def __str__(self): return f"{self.model_unit_configs}" + + def add_section(self, unit): + self.model_unit_configs.append(unit) + #@staticmethod def load(self, filename: str): """Load configuration from a file. Dispatch method based on file extension (YAML, JSON, etc.). """ - + extension = filename.split(".")[-1] if extension in ("yml", "yaml"): @@ -93,45 +107,12 @@ def dump(self, filename: str): """Dump configuration to a file.""" extension = filename.split(".")[-1] + data = self.to_dict() if extension in ("yml", "yaml"): - _dump_yml(self.model_unit_configs, filename) + _dump_yml(data, filename) elif extension == "json": - _dump_json(self.model_unit_configs, filename) - - + _dump_json(data, filename) -# update() -# to_dict() -# build_model() --create a model object -# generate() - -# class ModelUnitConfig -# [Parameters] -# name -# uid -# uri -# description -# validate() --validation des sections -# to_dict() - -# class Parameter -# name -# value -# unit -# type -# description -# default value -# uid -# uri - -# class ModelConfig -# [ModelUnitConfig] -# name -# def MyModel() -# p = params() --recover parameters -# c = Config(p) -# c.generate('config.yml') -# return Model(c) \ No newline at end of file From 8a5899e973e2250dd1c2081b02b3c2f1b3df0743 Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Tue, 16 Jun 2026 15:06:07 +0200 Subject: [PATCH 16/35] ajout de nouveaux parametres --- test/test_config.py | 52 ++++++++------------------------------------- 1 file changed, 9 insertions(+), 43 deletions(-) diff --git a/test/test_config.py b/test/test_config.py index a2d06f6..750534c 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -1,43 +1,13 @@ from __future__ import absolute_import import os -from openalea.core.config import Config +#from openalea.core.config import Config +from openalea.core.config import Parameter, MyModelUnit, Config + import yaml import json -class Parameter: - def __init__(self, name, value=None, unit=None, param_type=None, description="", uid=None, uri=None): - self.name = name - self.value = value - self.unit = unit - self.param_type = param_type - self.description = description - self.uid = uid - self.uri = uri - - def to_dict(self): - return { - self.name:self.value - } - - def __str__(self): - return f"name={self.name}, value={self.value}, unit={self.unit}, param_type={self.param_type}, description={self.description}, uid={self.uid}, uri={self.uri}" - -class MyModelUnit: - def __init__(self, name, parameters: list): - self.name = name - self.parameters=parameters - - def to_dict(self): - params = {k : v for param in self.parameters for k, v in param.to_dict().items()} - return { - self.name : params - } - - def __str__(self): - return f"name={self.name}, parameters={self.to_dict()}" - def hydroshoot_simulation_config(): p_sdate = Parameter("sdate", "2012-08-01 00:00:00") @@ -79,7 +49,9 @@ def hydroshoot_phenology_config(): def test_config_hs(): unit = hydroshoot_simulation_config() config = Config([unit]) - config.dump("params3.json") + config.dump("params3.yml") + print("test") + config.load("params3.json") # tests @@ -89,7 +61,7 @@ def test_config_hs(): planting = hydroshoot_planting_config() config.add_section(planting) config.dump("params3.json") - + assert len(config) == 2 assert len(config["planting"]) == 3 @@ -97,14 +69,8 @@ def test_config_hs(): config.add_section(phenology) config.dump("params3.json") - #assert len(config) == 3 - #assert len(config["planting"]) == 2 - - - - - - + assert len(config) == 3 + assert len(config["planting"]) == 3 '''def test_config_hs_simu_plant(): simu = hydroshoot_simulation_config() From bb66dc6025094f9c73fd6cccc7f2818cfbffa48b Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Tue, 16 Jun 2026 15:06:59 +0200 Subject: [PATCH 17/35] modification of the load function --- src/openalea/core/config.py | 74 +++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 4 deletions(-) diff --git a/src/openalea/core/config.py b/src/openalea/core/config.py index 42fc97b..9162f05 100644 --- a/src/openalea/core/config.py +++ b/src/openalea/core/config.py @@ -56,6 +56,46 @@ def _dump_json(data, filename: str): json.dump(data, f, indent=4) +class Parameter: + def __init__(self, name, value=None, unit=None, param_type=None, description="", uid=None, uri=None): + self.name = name + self.value = value + self.unit = unit + self.param_type = param_type + self.description = description + self.uid = uid + self.uri = uri + + def to_dict(self): + return { + self.name: { + "value": self.value, + "unit": self.unit, + "description": self.description, + "type": self.param_type, + "uid": self.uid, + "uri": self.uri + } + } + + def __str__(self): + return f"name={self.name}, value={self.value}, unit={self.unit}, param_type={self.param_type}, description={self.description}, uid={self.uid}, uri={self.uri}" + +class MyModelUnit: + def __init__(self, name, parameters: list): + self.name = name + self.parameters=parameters + + def to_dict(self): + params = {k : v for param in self.parameters for k, v in param.to_dict().items()} + return { + self.name : params + } + + def __str__(self): + return f"name={self.name}, parameters={self.to_dict()}" + + class Config: """Configuration of OpenAlea models @@ -99,9 +139,35 @@ def load(self, filename: str): elif extension == "json": data = _load_json(filename) - print(data) - - return Config(data) + units = [] + + ''' + for unit_name, params in data.items(): + parameters = [Parameter(name=k, value=v) for k, v in params.items()] + unit = MyModelUnit(unit_name, parameters) + units.append(unit) + ''' + for unit_name, params in data.items(): + parameters = [ + Parameter( + name=name, + value=params[name]["value"], + unit=params[name].get("unit"), + description=params[name].get("description"), + param_type=params[name].get("type"), + uid=params[name].get("uid"), + uri=params[name].get("uri") + ) + for name in params + ] + + unit = MyModelUnit(unit_name, parameters) + units.append(unit) + + + #print(data) + + return Config(units) def dump(self, filename: str): """Dump configuration to a file.""" @@ -115,4 +181,4 @@ def dump(self, filename: str): elif extension == "json": _dump_json(data, filename) - + From dc55b88b4500f874262aa9e26a9ee9580c598544 Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Wed, 17 Jun 2026 10:52:19 +0200 Subject: [PATCH 18/35] modification of the dump function --- test/test_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_config.py b/test/test_config.py index 750534c..ac64907 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -51,7 +51,7 @@ def test_config_hs(): config = Config([unit]) config.dump("params3.yml") print("test") - config.load("params3.json") + config.load("params3.yml") # tests From ba618be490e73eaea4b9044f6eeb581a5c611466 Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Fri, 19 Jun 2026 14:46:22 +0200 Subject: [PATCH 19/35] i just add some test --- test/test_config.py | 331 +++----------------------------------------- 1 file changed, 16 insertions(+), 315 deletions(-) diff --git a/test/test_config.py b/test/test_config.py index ac64907..4c904d2 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -1,16 +1,14 @@ from __future__ import absolute_import import os - -#from openalea.core.config import Config -from openalea.core.config import Parameter, MyModelUnit, Config - - +from openalea.core.config import Parameter, ModelUnit, Config import yaml import json def hydroshoot_simulation_config(): p_sdate = Parameter("sdate", "2012-08-01 00:00:00") + print("t") + print(p_sdate) p_edate = Parameter("edate", "2012-08-04 23:00:00") p_lat = Parameter("latitude", 43.61) p_longitude = Parameter("longitude", 3.87) @@ -22,8 +20,8 @@ def hydroshoot_simulation_config(): p_negligible_shoot_resistance = Parameter("negligible_shoot_resistance", False) p_energy_budget = Parameter("energy_budget", True) parameters = [p_sdate, p_edate, p_lat, p_longitude, p_elevation, p_tzone, p_output_index, p_unit_scene_length, p_hydraulic_structure, p_negligible_shoot_resistance, p_energy_budget] - - simulation = MyModelUnit('simulation', parameters) + simulation = ModelUnit('simulation', parameters) + print(simulation) return simulation @@ -33,7 +31,7 @@ def hydroshoot_planting_config(): p_row_angle_with_south = Parameter("row_angle_with_south", 140.0) parameters = [p_spacing_between_rows, p_spacing_on_row, p_row_angle_with_south] - planting = MyModelUnit('planting', parameters) + planting = ModelUnit('planting', parameters) return planting @@ -42,35 +40,36 @@ def hydroshoot_phenology_config(): p_t_base = Parameter("t_base", 10.0) parameters = [p_emdate, p_t_base] - phenology = MyModelUnit('phenology', parameters) + phenology = ModelUnit('phenology', parameters) return phenology def test_config_hs(): unit = hydroshoot_simulation_config() config = Config([unit]) - config.dump("params3.yml") - print("test") - config.load("params3.yml") + config.dump("params4.yml") + #print(len(config)) + #print(config) - # tests + # Test assert(len(config) == 1) assert(len(config['simulation'])==11) - + planting = hydroshoot_planting_config() config.add_section(planting) - config.dump("params3.json") - + config.dump("params4.yml") + assert len(config) == 2 assert len(config["planting"]) == 3 phenology = hydroshoot_phenology_config() config.add_section(phenology) - config.dump("params3.json") + config.dump("params4.yml") assert len(config) == 3 assert len(config["planting"]) == 3 + '''def test_config_hs_simu_plant(): simu = hydroshoot_simulation_config() @@ -86,302 +85,4 @@ def test_config_hs(): test_config_hs() #test_config_hs_simu_plant() -''' -def test_config1(): - - #unit1 = MyModelUnit('unit1', dict(p1=1, p2='2')) - #unit2 = MyModelUnit('unit2', dict(p1=1, p2='2')) - - #p1= Parameter("numerical", 12, "cm", "cm", "mesurer", 234, 1223) - - #unit3=MyModelUnit('unit3', p1.to_dict()) - #print(unit3) - - #config1 = Config(unit3.to_dict()) - #print(config1) - #config1.dump("tototo.json") - - #config1 = Config([unit1, unit2]) - #config1.dump("toto.json") - #config2 = Config.load(unit1, "toto.json") - - - - print("Test params.json of HydroShoot\n") - - # Creation of the simulation section - - unit_simulation = hydroshoot_simulation_config() - - # Creation of the planting section - - p_spacing_between_rows = Parameter("spacing_between_rows", 3.6) - p_spacing_on_row = Parameter("spacing_on_row", 1) - p_row_angle_with_south = Parameter("row_angle_with_south", 140.0) - parameters2 = [p_spacing_between_rows, p_spacing_on_row, p_row_angle_with_south] - - - unit_planting_section = MyModelUnit('planting', parameters2) - - #Creation of the phenology section - - p_emdate = Parameter("emdate", "2012-04-01 00:00:00") - p_t_base = Parameter("t_base", 10.0) - - p3 = [p_emdate, p_t_base] - d3 = {} - d3.update(p_emdate.to_dict()) - d3.update(p_t_base.to_dict()) - - unit_phenology = MyModelUnit("phenology", p3) - - # Creation of the MTG API section - - p_collar_label = Parameter("collar_label", "inT") - p_leaf_lbl_prefix = Parameter("leaf_lbl_prefix", "L") - p_stem_lbl_prefix = Parameter("stem_lbl_prefix", ["in", "Pet", "cx"]) - - p4 = [p_collar_label, p_leaf_lbl_prefix, p_stem_lbl_prefix] - d4 = {} - d4.update(p_collar_label.to_dict()) - d4.update(p_leaf_lbl_prefix.to_dict()) - d4.update(p_stem_lbl_prefix.to_dict()) - - # - - - unit_mtg_api = MyModelUnit("mtg_api", d4) - - #Creation of the numerical resolution section - - p_max_iter = Parameter("max_iter", 100) - p_psi_step = Parameter("psi_step", 1.0) - p_psi_error_threshold = Parameter("psi_error_threshold", 0.05) - p_t_step = Parameter("t_step", 1.0) - p_t_error_threshold = Parameter("t_error_threshold", 0.02) - - d5 = {} - d5.update(p_max_iter.to_dict()) - d5.update(p_psi_step.to_dict()) - d5.update(p_psi_error_threshold.to_dict()) - d5.update(p_t_step.to_dict()) - d5.update(p_t_error_threshold.to_dict()) - - unit_numerical_resolution = MyModelUnit("numerical_resolution", d5) - - #Creation of the irradiance section - - p_E_type = Parameter("E_type", "Rg_Watt/m2") - p_E_type2 = Parameter("E_type2", "Eabs") - - p_opt_prop = Parameter("opt_prop", { - "SW": { - "leaf": [0.06, 0.07], - "stem": [0.13], - "other": [0.06, 0.07] - }, - "LW": { - "leaf": [0.04, 0.07], - "stem": [0.13], - "other": [0.06, 0.07] - } - }) - - p_turtle_format = Parameter("turtle_format", "soc") - p_turtle_sectors = Parameter("turtle_sectors", "46") - p_icosphere_level = Parameter("icosphere_level", None) - - d6 = {} - d6.update(p_E_type.to_dict()) - d6.update(p_E_type2.to_dict()) - d6.update(p_opt_prop.to_dict()) - d6.update(p_turtle_format.to_dict()) - d6.update(p_turtle_sectors.to_dict()) - d6.update(p_icosphere_level.to_dict()) - - unit_irradiance = MyModelUnit("irradiance", d6) - - #Creation of the energy section - - - p_solo = Parameter("solo", True) - p_t_cloud = Parameter("t_cloud", 2.0) - p_t_sky = Parameter("t_sky", -20.0) - - d7 = {} - d7.update(p_solo.to_dict()) - d7.update(p_t_cloud.to_dict()) - d7.update(p_t_sky.to_dict()) - - unit_energy = MyModelUnit("energy", d7) - - #Creation of the hydraulic section - - p_psi_min = Parameter("psi_min", -3.0) - - p_Kx_dict = Parameter("Kx_dict", { - "a": 1.6, - "b": 2.0, - "min_kmax": 0.000111 - }) - - p_par_K_vul = Parameter("par_K_vul", { - "model": "misson", - "fifty_cent": -0.76, - "sig_slope": 1.0 - }) - - d8 = {} - d8.update(p_psi_min.to_dict()) - d8.update(p_Kx_dict.to_dict()) - d8.update(p_par_K_vul.to_dict()) - - unit_hydraulic = MyModelUnit("hydraulic", d8) - - #Creation of the exchange section - - p_rbt = Parameter("rbt", 0.6667) - - p_Na_dict = Parameter("Na_dict", { - "aN": -0.0008, - "bN": 3.3, - "aM": 6.471, - "bM": 56.635 - }) - - p_par_gs = Parameter("par_gs", { - "model": "misson", - "g0": 0.0, - "m0": 7.3, - "psi0": -0.65, - "D0": 1.0, - "n": 4.0 - }) - - p_par_photo = Parameter("par_photo", { - "alpha": 0.24, - "Kc25": 404.9, - "Ko25": 278.4, - "Tx25": 42.75, - "ds": 0.635, - "dHd": 200.0, - "RespT_Kc": {"c": 38.05, "deltaHa": 79.43}, - "RespT_Ko": {"c": 20.30, "deltaHa": 36.38}, - "RespT_Vcm": {"c": 26.35, "deltaHa": 65.33}, - "RespT_Jm": {"c": 17.57, "deltaHa": 43.54}, - "RespT_TPU": {"c": 21.46, "deltaHa": 53.1}, - "RespT_Rd": {"c": 18.72, "deltaHa": 46.39}, - "RespT_Tx": {"c": 19.02, "deltaHa": 37.83} - }) - - p_par_photo_N = Parameter("par_photo_N", { - "Vcm25_N": [34.02, -3.13], - "Jm25_N": [78.27, -17.3], - "Rd_N": [0.42, -0.01], - "TPU25_N": [6.24, -1.92] - }) - - d9 = {} - d9.update(p_rbt.to_dict()) - d9.update(p_Na_dict.to_dict()) - d9.update(p_par_gs.to_dict()) - d9.update(p_par_photo.to_dict()) - d9.update(p_par_photo_N.to_dict()) - - unit_exchange = MyModelUnit("exchange", d9) - - #Creation of the soil section - - p_soil_class = Parameter("soil_class", "Sandy_Loam") - - p_soil_dimensions = Parameter("soil_dimensions", { - "width": 3.6, - "length": 1.0, - "depth": 1.2 - }) - - p_rhyzo_coeff = Parameter("rhyzo_coeff", 0.75) - - d10 = {} - d10.update(p_soil_class.to_dict()) - d10.update(p_soil_dimensions.to_dict()) - d10.update(p_rhyzo_coeff.to_dict()) - - unit_soil = MyModelUnit("soil", d10) - - - #Configuration - - config_dict = {} - - config_dict.update(unit_simulation.to_dict()) - config_dict.update(unit_planting_section.to_dict()) - config_dict.update(unit_phenology.to_dict()) - config_dict.update(unit_mtg_api.to_dict()) - config_dict.update(unit_numerical_resolution.to_dict()) - config_dict.update(unit_irradiance.to_dict()) - config_dict.update(unit_energy.to_dict()) - config_dict.update(unit_hydraulic.to_dict()) - config_dict.update(unit_exchange.to_dict()) - config_dict.update(unit_soil.to_dict()) - - - config3 = Config(config_dict) - print(config3) - config3.dump("params.json") - - config4 = Config(config_dict) - config4.dump("params.yml") - - # Creation of the simulation section - - p_sdate1 = Parameter("sdate", "2012-08-01 00:00:00") - p_edate1 = Parameter("edate", "2012-08-04 23:00:00") - p_lat1 = Parameter("latitude", 43.61) - p_longitude1 = Parameter("longitude", 3.87) - p_elevation1 = Parameter("elevation", 44.0) - - d11 = {} - d11.update(p_sdate.to_dict()) - d11.update(p_edate.to_dict()) - d11.update(p_lat.to_dict()) - d11.update(p_longitude.to_dict()) - d11.update(p_elevation.to_dict()) - - unit_simulation1 = MyModelUnit('simulation', d11) - - # Creation of the planting section - - p_spacing_between_rows1 = Parameter("spacing_between_rows", 3.6) - p_spacing_on_row1 = Parameter("spacing_on_row", 1) - p_row_angle_with_south1 = Parameter("row_angle_with_south", 140.0) - - d12={} - d12.update(p_spacing_between_rows.to_dict()) - d12.update(p_spacing_on_row.to_dict()) - d12.update(p_row_angle_with_south.to_dict()) - - unit_planting_section1 = MyModelUnit('planting', d12) - - #Configuration - - config_dict1 = {} - - config_dict1.update(unit_simulation1.to_dict()) - config_dict1.update(unit_planting_section1.to_dict()) - - config5 = Config(config_dict1) - config5.dump("paramss.yml") - - - -def test_read_cnofig(): - config = load_config("params.json") - config.dump("params1.json") - # compare both - print(config) - - ''' - - From c343804fff26e484efb7e230e97e5f6017249745 Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Fri, 19 Jun 2026 14:47:01 +0200 Subject: [PATCH 20/35] i made config a dict and dataclass of parameter --- src/openalea/core/config.py | 110 +++++++++++++++++++++--------------- 1 file changed, 66 insertions(+), 44 deletions(-) diff --git a/src/openalea/core/config.py b/src/openalea/core/config.py index 9162f05..e4bbb1b 100644 --- a/src/openalea/core/config.py +++ b/src/openalea/core/config.py @@ -24,6 +24,7 @@ from openalea.core.singleton import Singleton from openalea.core.observer import Observed from pathlib import Path +from dataclasses import dataclass, asdict def _load_json(filename: str): """Load configuration from a JSON file.""" @@ -43,7 +44,7 @@ def _load_yml(filename: str): def _dump_yml(data, filename: str, sort_keys=False): """Dump configuration to a YAML file.""" - + file = Path(filename) with file.open("w") as f: yaml.dump(data, f, sort_keys=sort_keys) @@ -56,73 +57,99 @@ def _dump_json(data, filename: str): json.dump(data, f, indent=4) +@dataclass class Parameter: - def __init__(self, name, value=None, unit=None, param_type=None, description="", uid=None, uri=None): - self.name = name - self.value = value - self.unit = unit - self.param_type = param_type - self.description = description - self.uid = uid - self.uri = uri + name: str + value: any = None + unit: str = None + param_type: str = None + description: str = "" + uid: str = None + uri: str = None + + def __to_dict__(self): + d = asdict(self) + name = d.pop("name") + return {name: d} + + ''' + def to_commented_yaml(self): + cm = CommentedMap() + cm["value"] = self.value - def to_dict(self): - return { - self.name: { - "value": self.value, - "unit": self.unit, - "description": self.description, - "type": self.param_type, - "uid": self.uid, - "uri": self.uri - } - } + for field in ["unit", "param_type", "description", "uid", "uri"]: + value = getattr(self, field) + cm._yaml_add_comment(f"{field}: {value}", key="value") + + return cm + ''' + + def to_commented_yaml(self): + cm = CommentedMap() + cm["value"] = self.value + + for field in ["unit", "param_type", "description", "uid", "uri"]: + value = getattr(self, field) + comment = [[], [f"{field}: {value}"]] + cm._yaml_add_comment(comment, key="value") + + return cm def __str__(self): - return f"name={self.name}, value={self.value}, unit={self.unit}, param_type={self.param_type}, description={self.description}, uid={self.uid}, uri={self.uri}" + return ( + f"name={self.name}, value={self.value}, unit={self.unit}, " + f"param_type={self.param_type}, description={self.description}, " + f"uid={self.uid}, uri={self.uri}" + ) + -class MyModelUnit: +class ModelUnit(dict): def __init__(self, name, parameters: list): + + params_dict = {} + for p in parameters: + params_dict.update(p.__to_dict__()) + + super().__init__({name: params_dict}) self.name = name self.parameters=parameters - + + ''' def to_dict(self): - params = {k : v for param in self.parameters for k, v in param.to_dict().items()} + params = {k : v for param in self.parameters for k, v in param.__to_dict__().items()} return { self.name : params } - + ''' + def __str__(self): - return f"name={self.name}, parameters={self.to_dict()}" + return f"name={self.name}, parameters={dict(self)}" -class Config: +class Config(dict): """Configuration of OpenAlea models TODO : Documentation to write """ + def __init__(self, model_unit_configs: list): """Initialize the configuration with a list of unit configurations.""" - self.model_unit_configs = model_unit_configs - def to_dict(self): - dict = {} - for unit in self.model_unit_configs: - dict.update(unit.to_dict()) - return dict + super().__init__() + self.model_unit_configs = model_unit_configs + for unit in model_unit_configs: + self.update(unit) def __len__(self): return len(self.model_unit_configs) - def __getitem__(self, key): - return self.to_dict()[key] def __str__(self): return f"{self.model_unit_configs}" - def add_section(self, unit): self.model_unit_configs.append(unit) + self.update(unit) #@staticmethod def load(self, filename: str): @@ -161,11 +188,7 @@ def load(self, filename: str): for name in params ] - unit = MyModelUnit(unit_name, parameters) - units.append(unit) - - - #print(data) + units.append(ModelUnit(unit_name, parameters)) return Config(units) @@ -173,12 +196,11 @@ def dump(self, filename: str): """Dump configuration to a file.""" extension = filename.split(".")[-1] - data = self.to_dict() if extension in ("yml", "yaml"): - _dump_yml(data, filename) + _dump_yml(dict(self), filename) elif extension == "json": - _dump_json(data, filename) + _dump_json(dict(self), filename) From 44a3063788bf75475639018190a10b8e2d02aae6 Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Tue, 23 Jun 2026 16:15:29 +0200 Subject: [PATCH 21/35] after several attempts, I found a function to generate a function but I need to improve it --- src/openalea/core/config.py | 64 ++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/openalea/core/config.py b/src/openalea/core/config.py index e4bbb1b..d87b58c 100644 --- a/src/openalea/core/config.py +++ b/src/openalea/core/config.py @@ -20,11 +20,15 @@ __license__ = "Cecill-C" import yaml +from ruamel.yaml import YAML +from ruamel.yaml.comments import CommentedMap import json from openalea.core.singleton import Singleton from openalea.core.observer import Observed from pathlib import Path from dataclasses import dataclass, asdict +import io +from ruamel.yaml import YAML def _load_json(filename: str): """Load configuration from a JSON file.""" @@ -56,6 +60,25 @@ def _dump_json(data, filename: str): with file.open("w") as f: json.dump(data, f, indent=4) +def to_commented_map(obj): + if isinstance(obj, dict): + cm = CommentedMap() + for k, v in obj.items(): + cm[k] = to_commented_map(v) + return cm + else: + return obj + + +def add_comment(cm): + + for name, param in cm["simulation"].items(): + for key, value in param.items(): + param.yaml_set_comment_before_after_key( + key, + before=f"{key}: {value}" + ) + @dataclass class Parameter: @@ -72,29 +95,6 @@ def __to_dict__(self): name = d.pop("name") return {name: d} - ''' - def to_commented_yaml(self): - cm = CommentedMap() - cm["value"] = self.value - - for field in ["unit", "param_type", "description", "uid", "uri"]: - value = getattr(self, field) - cm._yaml_add_comment(f"{field}: {value}", key="value") - - return cm - ''' - - def to_commented_yaml(self): - cm = CommentedMap() - cm["value"] = self.value - - for field in ["unit", "param_type", "description", "uid", "uri"]: - value = getattr(self, field) - comment = [[], [f"{field}: {value}"]] - cm._yaml_add_comment(comment, key="value") - - return cm - def __str__(self): return ( f"name={self.name}, value={self.value}, unit={self.unit}, " @@ -113,14 +113,6 @@ def __init__(self, name, parameters: list): super().__init__({name: params_dict}) self.name = name self.parameters=parameters - - ''' - def to_dict(self): - params = {k : v for param in self.parameters for k, v in param.__to_dict__().items()} - return { - self.name : params - } - ''' def __str__(self): return f"name={self.name}, parameters={dict(self)}" @@ -197,8 +189,16 @@ def dump(self, filename: str): extension = filename.split(".")[-1] + #add_comment(self) + if extension in ("yml", "yaml"): - _dump_yml(dict(self), filename) + yaml = YAML() + cm = to_commented_map(self) + add_comment(cm) + + with open(filename, "w") as f: + yaml.dump(cm, f) + #_dump_yml(dict(self), filename) elif extension == "json": _dump_json(dict(self), filename) From 1b816818e4690c5f215f61aeaf5eec8bfa975a1b Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Wed, 24 Jun 2026 11:20:38 +0200 Subject: [PATCH 22/35] test with comment --- test/test_config.py | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/test/test_config.py b/test/test_config.py index 4c904d2..420bbc2 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -48,10 +48,6 @@ def test_config_hs(): unit = hydroshoot_simulation_config() config = Config([unit]) config.dump("params4.yml") - #print(len(config)) - #print(config) - - # Test assert(len(config) == 1) assert(len(config['simulation'])==11) @@ -65,24 +61,20 @@ def test_config_hs(): phenology = hydroshoot_phenology_config() config.add_section(phenology) + config.custom_comments["sdate"] = "date of simulation" + config.custom_comments["latitude"] = [ + "param_type : float", + "unit : degrees" +] config.dump("params4.yml") + config.load("params4.yml") + assert len(config) == 3 assert len(config["planting"]) == 3 -'''def test_config_hs_simu_plant(): - simu = hydroshoot_simulation_config() - planting = hydroshoot_planting_config() - config = Config([simu, planting]) - config.dump("params2.json") - - assert len(config) == 2 - assert len(config["simulation"]) == 11 - assert len(config["planting"]) == 3''' - - test_config_hs() -#test_config_hs_simu_plant() + From 454023af62d2abe62556fdd8e3d21b9ccf9de2c3 Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Wed, 24 Jun 2026 11:21:13 +0200 Subject: [PATCH 23/35] new function for adding comment --- src/openalea/core/config.py | 73 +++++++++++++------------------------ 1 file changed, 26 insertions(+), 47 deletions(-) diff --git a/src/openalea/core/config.py b/src/openalea/core/config.py index d87b58c..df45603 100644 --- a/src/openalea/core/config.py +++ b/src/openalea/core/config.py @@ -69,37 +69,34 @@ def to_commented_map(obj): else: return obj +def add_comment(cm, custom_comments=None): + if custom_comments is None: + custom_comments = {} -def add_comment(cm): - - for name, param in cm["simulation"].items(): - for key, value in param.items(): - param.yaml_set_comment_before_after_key( - key, - before=f"{key}: {value}" - ) + for section_name, section in cm.items(): + for param_name, param_value in section.items(): + if param_name in custom_comments: + comment = custom_comments[param_name] + if isinstance(comment, list): + comment = "\n".join(comment) + + section.yaml_set_comment_before_after_key( + param_name, + before=comment + ) @dataclass class Parameter: name: str value: any = None - unit: str = None - param_type: str = None - description: str = "" - uid: str = None - uri: str = None def __to_dict__(self): - d = asdict(self) - name = d.pop("name") - return {name: d} + return {self.name: self.value} def __str__(self): return ( - f"name={self.name}, value={self.value}, unit={self.unit}, " - f"param_type={self.param_type}, description={self.description}, " - f"uid={self.uid}, uri={self.uri}" + f"name={self.name}, value={self.value}" ) @@ -129,6 +126,7 @@ def __init__(self, model_unit_configs: list): super().__init__() self.model_unit_configs = model_unit_configs + self.custom_comments = {} for unit in model_unit_configs: self.update(unit) @@ -143,62 +141,43 @@ def add_section(self, unit): self.model_unit_configs.append(unit) self.update(unit) - #@staticmethod - def load(self, filename: str): - """Load configuration from a file. - - Dispatch method based on file extension (YAML, JSON, etc.). - """ + def load(self, filename: str): extension = filename.split(".")[-1] if extension in ("yml", "yaml"): - data = _load_yml(filename) + yaml = YAML() + with open(filename, "r") as f: + data = yaml.load(f) elif extension == "json": data = _load_json(filename) units = [] - ''' - for unit_name, params in data.items(): - parameters = [Parameter(name=k, value=v) for k, v in params.items()] - unit = MyModelUnit(unit_name, parameters) - units.append(unit) - ''' for unit_name, params in data.items(): parameters = [ - Parameter( - name=name, - value=params[name]["value"], - unit=params[name].get("unit"), - description=params[name].get("description"), - param_type=params[name].get("type"), - uid=params[name].get("uid"), - uri=params[name].get("uri") - ) - for name in params - ] + Parameter(name=param_name, value=param_value) + for param_name, param_value in params.items() + ] units.append(ModelUnit(unit_name, parameters)) return Config(units) + def dump(self, filename: str): """Dump configuration to a file.""" extension = filename.split(".")[-1] - #add_comment(self) - if extension in ("yml", "yaml"): yaml = YAML() cm = to_commented_map(self) - add_comment(cm) + add_comment(cm, self.custom_comments) with open(filename, "w") as f: yaml.dump(cm, f) - #_dump_yml(dict(self), filename) elif extension == "json": _dump_json(dict(self), filename) From b7a756266ff08c7bdda70b2133f02e25c94eddc1 Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Wed, 24 Jun 2026 11:31:26 +0200 Subject: [PATCH 24/35] add ruamel.yaml --- conda/meta.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conda/meta.yaml b/conda/meta.yaml index 10e51da..aecb512 100644 --- a/conda/meta.yaml +++ b/conda/meta.yaml @@ -25,6 +25,7 @@ build: requirements: host: - python + - ruamel.yaml - pyyaml {% for dep in build_deps %} - {{ dep }} @@ -32,6 +33,7 @@ requirements: run: - python + - ruamel.yaml - pyyaml {% for dep in deps + conda_deps %} - {{ dep }} From 6db511e6d78267d240202ed63565f324d1acbf02 Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Thu, 25 Jun 2026 15:22:23 +0200 Subject: [PATCH 25/35] Tutorial of the configuration --- Tutorial.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 Tutorial.md diff --git a/Tutorial.md b/Tutorial.md new file mode 100644 index 0000000..fd6e5bf --- /dev/null +++ b/Tutorial.md @@ -0,0 +1,56 @@ +# Tutorial of the configuration + +This tutorial explains how the configuration work on the model. User, modeler and developer interact with the configuration. The user update the file, the modeler build the file and the developer use config to init the model and run it. + +## User + +The user interacts only with the configuration file, not with Python code. +The user can modify the YAML and JSON file. He can change the values, the parameters and also comment. +The file can be opened with a text editor or on Visual Studio Code. +The configuration file is written in YAML or JSON and contains sections, parameters inside each section. + +For example, in HydroRoot with simulation and planting as sections with their parameters: +simulation: + sdate: '2012-08-01 00:00:00' + edate: '2012-08-04 23:00:00' + latitude: 43.61 + longitude: 3.87 + elevation: 44.0 + tzone: Europe/Paris + output_index: '' + unit_scene_length: cm + hydraulic_structure: true + negligible_shoot_resistance: false + energy_budget: true +planting: + spacing_between_rows: 3.6 + spacing_on_row: 1 + row_angle_with_south: 140.0 + +The user can modify only the values, never the parameter names but can never change the name of parameters. He can change latitude 43.61 to 41 for example. The user can also comment, comments begin with #. JSON doesn't support comment, if the user want to comment, he have to use YAML. + +## Modeler + +The modeler constructs the configuration. A Config is a Python dictionary that stores sections and parameters. A Config is built from a list of ModelUnit objects. + +For example: +p_sdate = Parameter("sdate", "2012-08-01 00:00:00") +p_edate = Parameter("edate", "2012-08-04 23:00:00") +p_lat = Parameter("latitude", 43.61) +p_longitude = Parameter("longitude", 3.87) +p_elevation = Parameter("elevation", 44.0) +p_tzone = Parameter("tzone", "Europe/Paris") +p_output_index = Parameter("output_index", "") +p_unit_scene_length = Parameter("unit_scene_length", "cm") +p_hydraulic_structure = Parameter("hydraulic_structure", True) +p_negligible_shoot_resistance = Parameter("negligible_shoot_resistance", False) +p_energy_budget = Parameter("energy_budget", True) +parameters = [p_sdate, p_edate, p_lat, p_longitude, p_elevation, p_tzone, p_output_index, p_unit_scene_length, p_hydraulic_structure, p_negligible_shoot_resistance, p_energy_budget] +simulation = ModelUnit('simulation', parameters) +config = Config([simulation]) + +The modeler can also load and dump a configuration file as config.dump("params.yml") or config.dump("params.json"). The modeler can also add new sections, for example *config.add_section(planting)*. + +## Developer + +The developer receives a config object. Config unherite from a dict, the developer can access to the parameters value. He can initialize the model and call run(). \ No newline at end of file From 032b4bc8a0ab10387eb70a836da2af3775635b17 Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Fri, 26 Jun 2026 15:32:37 +0200 Subject: [PATCH 26/35] I made some test and add new comments, in some parameters and sections, the test work good --- test/test_config.py | 67 +++++++++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 21 deletions(-) diff --git a/test/test_config.py b/test/test_config.py index 420bbc2..df0388d 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -6,17 +6,15 @@ def hydroshoot_simulation_config(): - p_sdate = Parameter("sdate", "2012-08-01 00:00:00") - print("t") - print(p_sdate) - p_edate = Parameter("edate", "2012-08-04 23:00:00") - p_lat = Parameter("latitude", 43.61) - p_longitude = Parameter("longitude", 3.87) - p_elevation = Parameter("elevation", 44.0) - p_tzone = Parameter("tzone", "Europe/Paris") + p_sdate = Parameter("sdate", "2012-08-01 00:00:00", "Start date of the simulation") + p_edate = Parameter("edate", "2012-08-04 23:00:00", "End date of the simulation") + p_lat = Parameter("latitude", 43.61, None, "degrees", "float") + p_longitude = Parameter("longitude", 3.87, "Longitude of the simulation", "degrees", "float") + p_elevation = Parameter("elevation", 44.0, None, "meters", "float") + p_tzone = Parameter("tzone", "Europe/Paris", "Time zone", None, "string") p_output_index = Parameter("output_index", "") p_unit_scene_length = Parameter("unit_scene_length", "cm") - p_hydraulic_structure = Parameter("hydraulic_structure", True) + p_hydraulic_structure = Parameter("hydraulic_structure", True, None, None, "boolean") p_negligible_shoot_resistance = Parameter("negligible_shoot_resistance", False) p_energy_budget = Parameter("energy_budget", True) parameters = [p_sdate, p_edate, p_lat, p_longitude, p_elevation, p_tzone, p_output_index, p_unit_scene_length, p_hydraulic_structure, p_negligible_shoot_resistance, p_energy_budget] @@ -26,9 +24,9 @@ def hydroshoot_simulation_config(): return simulation def hydroshoot_planting_config(): - p_spacing_between_rows = Parameter("spacing_between_rows", 3.6) - p_spacing_on_row = Parameter("spacing_on_row", 1) - p_row_angle_with_south = Parameter("row_angle_with_south", 140.0) + p_spacing_between_rows = Parameter("spacing_between_rows", 3.6, "Distance between planting rows", "meters", "float") + p_spacing_on_row = Parameter("spacing_on_row", 1, None, "meters", "float") + p_row_angle_with_south = Parameter("row_angle_with_south", 140.0, None, "degrees", "float") parameters = [p_spacing_between_rows, p_spacing_on_row, p_row_angle_with_south] planting = ModelUnit('planting', parameters) @@ -36,14 +34,37 @@ def hydroshoot_planting_config(): return planting def hydroshoot_phenology_config(): - p_emdate = Parameter("emdate", "2012-04-01 00:00:00") - p_t_base = Parameter("t_base", 10.0) + p_emdate = Parameter("emdate", "2012-04-01 00:00:00", None, None, "datetime") + p_t_base = Parameter("t_base", 10.0, None, "degrees", "float") parameters = [p_emdate, p_t_base] phenology = ModelUnit('phenology', parameters) return phenology +def hydroshoot_mtg_api_config(): + p_collar_label = Parameter("collar_label", "inT") + p_leaf_lbl_prefix = Parameter("leaf_lbl_prefix", "L") + p_stem_lbl_prefix = Parameter("stem_lbl_prefix", ["in", "Pet", "cx"]) + + parameters = [p_collar_label, p_leaf_lbl_prefix, p_stem_lbl_prefix] + mtg_api = ModelUnit("mtg_api", parameters) + + return mtg_api + +def hydroshoot_numerical_resolution_config(): + p_max_iter = Parameter("max_iter", 100, None, None, "integer") + p_psi_step = Parameter("psi_step", 1.0, None, None, "float") + p_psi_error_threshold = Parameter("psi_error_threshold", 0.05, None, "seconds") + p_t_step = Parameter("t_step", 1.0) + p_t_error_threshold = Parameter("t_error_threshold", 0.02) + + parameters = [p_max_iter, p_psi_step, p_psi_error_threshold, p_t_step, p_t_error_threshold] + numerical_resolution = ModelUnit("numerical_resolution", parameters) + + return numerical_resolution + + def test_config_hs(): unit = hydroshoot_simulation_config() config = Config([unit]) @@ -51,7 +72,7 @@ def test_config_hs(): assert(len(config) == 1) assert(len(config['simulation'])==11) - + config.section_comments["simulation"] = "Simulation" planting = hydroshoot_planting_config() config.add_section(planting) config.dump("params4.yml") @@ -61,16 +82,20 @@ def test_config_hs(): phenology = hydroshoot_phenology_config() config.add_section(phenology) - config.custom_comments["sdate"] = "date of simulation" - config.custom_comments["latitude"] = [ - "param_type : float", - "unit : degrees" -] + #config.custom_comments["sdate"] = "date of simulation" + #config.custom_comments["latitude"] = ["param_type : float", "unit : degrees"] + mtg_api = hydroshoot_mtg_api_config() + config.add_section(mtg_api) + config.dump("params4.yml") + + numerical_resolution = hydroshoot_numerical_resolution_config() + config.add_section(numerical_resolution) config.dump("params4.yml") + config.dump("params4.json") config.load("params4.yml") - assert len(config) == 3 + assert len(config) == 5 assert len(config["planting"]) == 3 From 5eefc997cf253e89e5ba569d34e5f637323a4c1c Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Fri, 26 Jun 2026 15:34:40 +0200 Subject: [PATCH 27/35] I add 2 functons for adding comments before each section and parameters. My class Parameter has now new elements (uid, param_type, description, unit, type), and I made some modification of ModelUnit class --- src/openalea/core/config.py | 76 +++++++++++++++++++++++++++++++++---- 1 file changed, 69 insertions(+), 7 deletions(-) diff --git a/src/openalea/core/config.py b/src/openalea/core/config.py index df45603..2baf09a 100644 --- a/src/openalea/core/config.py +++ b/src/openalea/core/config.py @@ -69,6 +69,31 @@ def to_commented_map(obj): else: return obj +def add_comment2(cm, model_unit_configs): + + param_index = {} + for unit in model_unit_configs: + param_index[unit.name] = {} + for p in unit.parameters: + param_index[unit.name][p.name] = p + + for section_name, section in cm.items(): + for param_name in section: + param = param_index.get(section_name, {}).get(param_name) + fields = ["description", "unit", "param_type", "uid", "uri"] + comments = [] + if param: + for field in fields: + value = getattr(param, field) + if value is not None: + comments.append(f"{field}: {value}") + if comments: + section.yaml_set_comment_before_after_key( + param_name, + before="\n".join(comments) + ) + + def add_comment(cm, custom_comments=None): if custom_comments is None: custom_comments = {} @@ -86,10 +111,30 @@ def add_comment(cm, custom_comments=None): before=comment ) + +def add_section_comments(cm, section_comments): + for section_name in cm.keys(): + if section_name in section_comments: + comment = section_comments[section_name] + + if isinstance(comment, list): + comment = "\n".join(comment) + + cm.yaml_set_comment_before_after_key( + section_name, + before=comment + ) + @dataclass class Parameter: name: str value: any = None + description : any = None + unit : any = None + param_type : any = None + uid : any = None + uri : any = None + def __to_dict__(self): return {self.name: self.value} @@ -99,7 +144,7 @@ def __str__(self): f"name={self.name}, value={self.value}" ) - +''' class ModelUnit(dict): def __init__(self, name, parameters: list): @@ -113,6 +158,16 @@ def __init__(self, name, parameters: list): def __str__(self): return f"name={self.name}, parameters={dict(self)}" +''' + +class ModelUnit(dict): + def __init__(self, name, parameters: list): + super().__init__({name: {p.name: p.value for p in parameters}}) + self.name = name + self.parameters = parameters + + def __str__(self): + return f"name={self.name}, parameters={dict(self)}" class Config(dict): @@ -126,7 +181,9 @@ def __init__(self, model_unit_configs: list): super().__init__() self.model_unit_configs = model_unit_configs - self.custom_comments = {} + self.params_comments = {} + self.section_comments = {} + for unit in model_unit_configs: self.update(unit) @@ -156,10 +213,10 @@ def load(self, filename: str): units = [] for unit_name, params in data.items(): - parameters = [ - Parameter(name=param_name, value=param_value) - for param_name, param_value in params.items() - ] + parameters = [] + for param_name, param_value in params.items(): + p = Parameter(name=param_name, value=param_value) + parameters.append(p) units.append(ModelUnit(unit_name, parameters)) @@ -174,7 +231,12 @@ def dump(self, filename: str): if extension in ("yml", "yaml"): yaml = YAML() cm = to_commented_map(self) - add_comment(cm, self.custom_comments) + + + add_comment(cm, self.params_comments) + add_comment2(cm, self.model_unit_configs) + add_section_comments(cm, self.section_comments) + with open(filename, "w") as f: yaml.dump(cm, f) From 8f0460b5662787052bcf002c487e62a8b74d27b8 Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Mon, 29 Jun 2026 15:45:33 +0200 Subject: [PATCH 28/35] New documentation --- src/openalea/core/config.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/openalea/core/config.py b/src/openalea/core/config.py index 2baf09a..c91ba5a 100644 --- a/src/openalea/core/config.py +++ b/src/openalea/core/config.py @@ -70,6 +70,11 @@ def to_commented_map(obj): return obj def add_comment2(cm, model_unit_configs): + '''Add automatic new comments on Parameters (description, unit, param_type, uid and uri). + parameters: -cm (CommentedMap) that contains sections and values from parameters. + -model_unit_configs: is a list of ModelUnit + If the value is None, this function don't add a new comment. If the value is not None, we add the comment. + ''' param_index = {} for unit in model_unit_configs: @@ -95,6 +100,7 @@ def add_comment2(cm, model_unit_configs): def add_comment(cm, custom_comments=None): + '''Add personalized new comment for each parameter.''' if custom_comments is None: custom_comments = {} @@ -113,6 +119,7 @@ def add_comment(cm, custom_comments=None): def add_section_comments(cm, section_comments): + '''Add personalized new comment for each section.''' for section_name in cm.keys(): if section_name in section_comments: comment = section_comments[section_name] @@ -127,6 +134,8 @@ def add_section_comments(cm, section_comments): @dataclass class Parameter: + '''The dataclass Parameter has two mandatory parameters name and value, the others parameters are optional to the config, if + we add them this would be commented, if we don't add them, we don't have comment. We also convert this class in dictionary.''' name: str value: any = None description : any = None @@ -161,6 +170,7 @@ def __str__(self): ''' class ModelUnit(dict): + '''ModelUnit is a dictionary with a section name and a list of parameters ''' def __init__(self, name, parameters: list): super().__init__({name: {p.name: p.value for p in parameters}}) self.name = name @@ -172,6 +182,12 @@ def __str__(self): class Config(dict): """Configuration of OpenAlea models + Config is a dictionary that contains a list of model units. With the config can: + -Load a new JSON or YAML file. + -Dump a JSON or YAML file. + -Add new sections. + -Add new comments. + -The quantity of units. TODO : Documentation to write """ @@ -200,6 +216,7 @@ def add_section(self, unit): def load(self, filename: str): + """Load configuration to a file.""" extension = filename.split(".")[-1] if extension in ("yml", "yaml"): From ea244568cbfaf47a6bbb9f9297fb751fe292a41c Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Tue, 30 Jun 2026 15:18:36 +0200 Subject: [PATCH 29/35] Tutorial and documentation about configuration on Jupyternotebook --- Documentation.ipynb | 364 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 364 insertions(+) create mode 100644 Documentation.ipynb diff --git a/Documentation.ipynb b/Documentation.ipynb new file mode 100644 index 0000000..704eae4 --- /dev/null +++ b/Documentation.ipynb @@ -0,0 +1,364 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "16c62968-51e4-4d0a-8305-874f666d5180", + "metadata": {}, + "source": [ + "# Tutorial" + ] + }, + { + "cell_type": "markdown", + "id": "04b358d7-3d1d-4c05-b03e-c431732b960d", + "metadata": {}, + "source": [ + "## User" + ] + }, + { + "cell_type": "markdown", + "id": "12ef5489-ac5c-431a-9c6b-695c998f35fc", + "metadata": {}, + "source": [ + "The user interacts only with the configuration file, not with Python code.\n", + "\n", + "1) Open the configuration file, in YAML or JSON.\n", + " \n", + "The user can open the file on Visual Studio Code or other text editor. For example:\n", + "```yaml\n", + "simulation:\n", + " sdate: '2012-08-01 00:00:00'\n", + " edate: '2012-08-04 23:00:00'\n", + " latitude: 43.61\n", + " longitude: 3.87\n", + " # unit: meters\n", + " # param_type: float\n", + " elevation: 44.0\n", + " tzone: Europe/Paris\n", + " output_index: ''\n", + " unit_scene_length: cm\n", + " # param_type: boolean\n", + " hydraulic_structure: true\n", + " negligible_shoot_resistance: false\n", + " energy_budget: true\n", + "planting:\n", + " spacing_between_rows: 3.6\n", + " spacing_on_row: 1\n", + " row_angle_with_south: 140.0\n", + " ```\n", + "\n", + "simulation and planting are sections, sdate, edate, latitude and all belows to simulation and planting are parameters. And all the lines that start with # are comments." + ] + }, + { + "cell_type": "markdown", + "id": "20e21ed0-70ba-4d5a-aa69-399abefe6ccf", + "metadata": {}, + "source": [ + "2) Modify a value\n", + "\n", + "The user can change any value, he can change it directly on the JSON or YML file. \n", + "\n", + "3) Add a new parameter in the section, the name has to be unique in the section.\n", + "\n", + "```yaml\n", + "planting:\n", + " spacing_between_rows: 3.6\n", + " spacing_on_row: 1\n", + " row_angle_with_south: 140.0\n", + " new_parameter: 45\n", + " ```\n", + "\n", + "4) Add a new section, for example, I add numerical_resolution with their parameters:\n", + "\n", + "```yaml\n", + "simulation:\n", + " sdate: '2012-08-01 00:00:00'\n", + " edate: '2012-08-04 23:00:00'\n", + " latitude: 43.61\n", + " longitude: 3.87\n", + " # unit: meters\n", + " # param_type: float\n", + " elevation: 44.0\n", + " tzone: Europe/Paris\n", + " output_index: ''\n", + " unit_scene_length: cm\n", + " # param_type: boolean\n", + " hydraulic_structure: true\n", + " negligible_shoot_resistance: false\n", + " energy_budget: true\n", + "planting:\n", + " spacing_between_rows: 3.6\n", + " spacing_on_row: 1\n", + " row_angle_with_south: 140.0\n", + "numerical_resolution:\n", + "# param_type: integer\n", + " max_iter: 100\n", + "# param_type: float\n", + " psi_step: 1.0\n", + "# unit: seconds\n", + " psi_error_threshold: 0.05\n", + " t_step: 1.0\n", + " t_error_threshold: 0.02\n", + " ```\n", + "\n", + "5) Add new comment, only in YAML, comment start with #.\n", + "\n", + "6) Delete a parameter, section or comment.\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "58b1b25d-4682-48e9-9290-c861dbb99fb2", + "metadata": {}, + "source": [ + "## Modeler" + ] + }, + { + "cell_type": "markdown", + "id": "4c397ddd-3f07-47dd-8e22-92ed0e89f6fd", + "metadata": {}, + "source": [ + "The modeler constructs the configuration. A Config is a Python dictionary that stores sections and parameters. A Config is built from a list of ModelUnit objects.\n" + ] + }, + { + "cell_type": "markdown", + "id": "6d68dfd5-305b-4c53-ab42-d2ed14a9cad4", + "metadata": {}, + "source": [ + "First, we have 3 classes: Parameter, ModelUnit and Config. " + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "dd4c69a0-dc54-4473-9e4f-8227e1a4e766", + "metadata": {}, + "outputs": [], + "source": [ + "@dataclass \n", + "class Parameter:\n", + " '''The dataclass Parameter has two mandatory parameters name and value, the others parameters are optional to the config, if\n", + " we add them this would be commented, if we don't add them, we don't have comment. We also convert this class in dictionary.'''\n", + " name: str\n", + " value: any = None\n", + " description : any = None\n", + " unit : any = None\n", + " param_type : any = None\n", + " uid : any = None\n", + " uri : any = None\n", + "\n", + "\n", + " def __to_dict__(self):\n", + " return {self.name: self.value}\n", + " \n", + " def __str__(self):\n", + " return (\n", + " f\"name={self.name}, value={self.value}\"\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "3fea1168-04f1-48ac-a807-6f711bfc5133", + "metadata": {}, + "outputs": [], + "source": [ + "class ModelUnit(dict):\n", + " '''ModelUnit is a dictionary with a section name and a list of parameters '''\n", + " def __init__(self, name, parameters: list):\n", + " super().__init__({name: {p.name: p.value for p in parameters}})\n", + " self.name = name\n", + " self.parameters = parameters\n", + "\n", + " def __str__(self):\n", + " return f\"name={self.name}, parameters={dict(self)}\"" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "0e1d4f73-b9b3-40fe-b816-83807abe71f3", + "metadata": {}, + "outputs": [], + "source": [ + "class Config(dict):\n", + " \"\"\"Configuration of OpenAlea models\n", + " Config is a dictionary that contains a list of model units. With the config can:\n", + " -Load a new JSON or YAML file.\n", + " -Dump a JSON or YAML file.\n", + " -Add new sections.\n", + " -Add new comments.\n", + " -The quantity of units.\n", + " \n", + " TODO : Documentation to write\n", + " \"\"\"\n", + "\n", + " def __init__(self, model_unit_configs: list):\n", + " \"\"\"Initialize the configuration with a list of unit configurations.\"\"\"\n", + "\n", + " super().__init__()\n", + " self.model_unit_configs = model_unit_configs\n", + " self.params_comments = {}\n", + " self.section_comments = {}\n", + "\n", + " for unit in model_unit_configs:\n", + " self.update(unit)\n", + "\n", + " def add_section(self, unit):\n", + " self.model_unit_configs.append(unit)\n", + " self.update(unit)\n", + "\n", + " def dump(self, filename: str):\n", + " \"\"\"Dump configuration to a file.\"\"\"\n", + "\n", + " extension = filename.split(\".\")[-1]\n", + "\n", + " if extension in (\"yml\", \"yaml\"):\n", + " yaml = YAML()\n", + " cm = to_commented_map(self)\n", + " \n", + "\n", + " add_comment(cm, self.params_comments)\n", + " add_comment2(cm, self.model_unit_configs)\n", + " add_section_comments(cm, self.section_comments)\n", + "\n", + "\n", + " with open(filename, \"w\") as f:\n", + " yaml.dump(cm, f)\n", + "\n", + " elif extension == \"json\":\n", + " _dump_json(dict(self), filename)\n" + ] + }, + { + "cell_type": "markdown", + "id": "437eeae0-46fd-42e0-8298-c91bfcc2b484", + "metadata": {}, + "source": [ + "The first thing the Modeler do is generate new parameters with an instance of the Parameter class. And an instance of the ModelUnit class, ModelUnit is a dictionary that contains a list of Parameters. And the config contains a list of model units and can generate a new config. A Config is built from a list of ModelUnit objects." + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "597d2dce-4f57-4f9e-b78e-0555ebcca90e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'simulation': {'sdate': '2012-08-01 00:00:00', 'edate': '2012-08-04 23:00:00', 'latitude': 43.61, 'longitude': 3.87, 'elevation': 44.0, 'tzone': 'Europe/Paris', 'output_index': '', 'unit_scene_length': 'cm', 'hydraulic_structure': True, 'negligible_shoot_resistance': False, 'energy_budget': True}}\n" + ] + } + ], + "source": [ + "p_sdate = Parameter(\"sdate\", \"2012-08-01 00:00:00\", \"Start date of the simulation\")\n", + "p_edate = Parameter(\"edate\", \"2012-08-04 23:00:00\", \"End date of the simulation\")\n", + "p_lat = Parameter(\"latitude\", 43.61, None, \"degrees\", \"float\")\n", + "p_longitude = Parameter(\"longitude\", 3.87, \"Longitude of the simulation\", \"degrees\", \"float\")\n", + "p_elevation = Parameter(\"elevation\", 44.0, None, \"meters\", \"float\")\n", + "p_tzone = Parameter(\"tzone\", \"Europe/Paris\", \"Time zone\", None, \"string\")\n", + "p_output_index = Parameter(\"output_index\", \"\")\n", + "p_unit_scene_length = Parameter(\"unit_scene_length\", \"cm\")\n", + "p_hydraulic_structure = Parameter(\"hydraulic_structure\", True, None, None, \"boolean\")\n", + "p_negligible_shoot_resistance = Parameter(\"negligible_shoot_resistance\", False)\n", + "p_energy_budget = Parameter(\"energy_budget\", True)\n", + "parameters = [p_sdate, p_edate, p_lat, p_longitude, p_elevation, p_tzone, p_output_index, p_unit_scene_length, p_hydraulic_structure, p_negligible_shoot_resistance, p_energy_budget]\n", + "simulation = ModelUnit('simulation', parameters)\n", + "config = Config([simulation])\n", + "print(config)" + ] + }, + { + "cell_type": "markdown", + "id": "01de7c15-870a-4aaa-af1b-5115c842a577", + "metadata": {}, + "source": [ + "The modeler can also load and dump a configuration file as config.dump(\"params.yml\") or config.dump(\"params.json\"). The modeler can also add new sections, for example *config.add_section(planting)*. In this example, I can add new section." + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "54e7bc9d-13c6-4cf6-883a-e2fc2cb43f8d", + "metadata": {}, + "outputs": [], + "source": [ + "p_spacing_between_rows = Parameter(\"spacing_between_rows\", 3.6, \"Distance between planting rows\", \"meters\", \"float\")\n", + "p_spacing_on_row = Parameter(\"spacing_on_row\", 1, None, \"meters\", \"float\")\n", + "p_row_angle_with_south = Parameter(\"row_angle_with_south\", 140.0, None, \"degrees\", \"float\")\n", + "\n", + "parameters = [p_spacing_between_rows, p_spacing_on_row, p_row_angle_with_south]\n", + "planting = ModelUnit('planting', parameters)\n", + "config.add_section(planting)" + ] + }, + { + "cell_type": "markdown", + "id": "eeecb6c8-317e-49a2-a34b-d750db0b94f8", + "metadata": {}, + "source": [ + "## Developer" + ] + }, + { + "cell_type": "markdown", + "id": "8e72f695-9284-44e6-be1a-45bf0ad02747", + "metadata": {}, + "source": [ + "The developer receives a config object. Config unherite from a dict, the developer can access to the parameters value. He can initialize the model and call run()." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fe154355-a5d1-4880-8f25-7f1e44b1b8f0", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b7051bf1-cb7c-4154-8b68-e6270cc6d286", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 5d4c27fc78ad40057ede3b56497ab179e2cdc950 Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Thu, 2 Jul 2026 16:39:33 +0200 Subject: [PATCH 30/35] Modification of comment section and ModelUnit class --- src/openalea/core/config.py | 42 +++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/src/openalea/core/config.py b/src/openalea/core/config.py index c91ba5a..f5c448b 100644 --- a/src/openalea/core/config.py +++ b/src/openalea/core/config.py @@ -69,6 +69,29 @@ def to_commented_map(obj): else: return obj +def add_unit_comment(cm, model_unit_configs): + """ + Add automatic comments above each ModelUnit section + using the optional fields of ModelUnit (description, unit, param_type, uid, uri). + """ + + fields = ["description", "unit", "param_type", "uid", "uri"] + + for unit in model_unit_configs: + comments = [] + + for field in fields: + value = getattr(unit, field, None) + if value is not None: + comments.append(f"{field}: {value}") + + if comments: + cm.yaml_set_comment_before_after_key( + unit.name, + before="\n".join(comments) + ) + + def add_comment2(cm, model_unit_configs): '''Add automatic new comments on Parameters (description, unit, param_type, uid and uri). parameters: -cm (CommentedMap) that contains sections and values from parameters. @@ -98,9 +121,9 @@ def add_comment2(cm, model_unit_configs): before="\n".join(comments) ) - +''' def add_comment(cm, custom_comments=None): - '''Add personalized new comment for each parameter.''' + if custom_comments is None: custom_comments = {} @@ -119,7 +142,7 @@ def add_comment(cm, custom_comments=None): def add_section_comments(cm, section_comments): - '''Add personalized new comment for each section.''' + for section_name in cm.keys(): if section_name in section_comments: comment = section_comments[section_name] @@ -131,6 +154,7 @@ def add_section_comments(cm, section_comments): section_name, before=comment ) +''' @dataclass class Parameter: @@ -171,10 +195,15 @@ def __str__(self): class ModelUnit(dict): '''ModelUnit is a dictionary with a section name and a list of parameters ''' - def __init__(self, name, parameters: list): + def __init__(self, name, parameters: list, description=None, unit=None, param_type=None, uid=None, uri=None): super().__init__({name: {p.name: p.value for p in parameters}}) self.name = name self.parameters = parameters + self.description = description + self.unit = unit + self.param_type = param_type + self.uid = uid + self.uri = uri def __str__(self): return f"name={self.name}, parameters={dict(self)}" @@ -250,9 +279,10 @@ def dump(self, filename: str): cm = to_commented_map(self) - add_comment(cm, self.params_comments) + #add_comment(cm, self.params_comments) add_comment2(cm, self.model_unit_configs) - add_section_comments(cm, self.section_comments) + add_unit_comment(cm, self.model_unit_configs) + #add_section_comments(cm, self.section_comments) with open(filename, "w") as f: From 1522c19597a25c4c3ca1c16389b1de2cb198b652 Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Fri, 3 Jul 2026 14:26:46 +0200 Subject: [PATCH 31/35] Delete Documentation.ipynb --- Documentation.ipynb | 364 -------------------------------------------- 1 file changed, 364 deletions(-) delete mode 100644 Documentation.ipynb diff --git a/Documentation.ipynb b/Documentation.ipynb deleted file mode 100644 index 704eae4..0000000 --- a/Documentation.ipynb +++ /dev/null @@ -1,364 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "16c62968-51e4-4d0a-8305-874f666d5180", - "metadata": {}, - "source": [ - "# Tutorial" - ] - }, - { - "cell_type": "markdown", - "id": "04b358d7-3d1d-4c05-b03e-c431732b960d", - "metadata": {}, - "source": [ - "## User" - ] - }, - { - "cell_type": "markdown", - "id": "12ef5489-ac5c-431a-9c6b-695c998f35fc", - "metadata": {}, - "source": [ - "The user interacts only with the configuration file, not with Python code.\n", - "\n", - "1) Open the configuration file, in YAML or JSON.\n", - " \n", - "The user can open the file on Visual Studio Code or other text editor. For example:\n", - "```yaml\n", - "simulation:\n", - " sdate: '2012-08-01 00:00:00'\n", - " edate: '2012-08-04 23:00:00'\n", - " latitude: 43.61\n", - " longitude: 3.87\n", - " # unit: meters\n", - " # param_type: float\n", - " elevation: 44.0\n", - " tzone: Europe/Paris\n", - " output_index: ''\n", - " unit_scene_length: cm\n", - " # param_type: boolean\n", - " hydraulic_structure: true\n", - " negligible_shoot_resistance: false\n", - " energy_budget: true\n", - "planting:\n", - " spacing_between_rows: 3.6\n", - " spacing_on_row: 1\n", - " row_angle_with_south: 140.0\n", - " ```\n", - "\n", - "simulation and planting are sections, sdate, edate, latitude and all belows to simulation and planting are parameters. And all the lines that start with # are comments." - ] - }, - { - "cell_type": "markdown", - "id": "20e21ed0-70ba-4d5a-aa69-399abefe6ccf", - "metadata": {}, - "source": [ - "2) Modify a value\n", - "\n", - "The user can change any value, he can change it directly on the JSON or YML file. \n", - "\n", - "3) Add a new parameter in the section, the name has to be unique in the section.\n", - "\n", - "```yaml\n", - "planting:\n", - " spacing_between_rows: 3.6\n", - " spacing_on_row: 1\n", - " row_angle_with_south: 140.0\n", - " new_parameter: 45\n", - " ```\n", - "\n", - "4) Add a new section, for example, I add numerical_resolution with their parameters:\n", - "\n", - "```yaml\n", - "simulation:\n", - " sdate: '2012-08-01 00:00:00'\n", - " edate: '2012-08-04 23:00:00'\n", - " latitude: 43.61\n", - " longitude: 3.87\n", - " # unit: meters\n", - " # param_type: float\n", - " elevation: 44.0\n", - " tzone: Europe/Paris\n", - " output_index: ''\n", - " unit_scene_length: cm\n", - " # param_type: boolean\n", - " hydraulic_structure: true\n", - " negligible_shoot_resistance: false\n", - " energy_budget: true\n", - "planting:\n", - " spacing_between_rows: 3.6\n", - " spacing_on_row: 1\n", - " row_angle_with_south: 140.0\n", - "numerical_resolution:\n", - "# param_type: integer\n", - " max_iter: 100\n", - "# param_type: float\n", - " psi_step: 1.0\n", - "# unit: seconds\n", - " psi_error_threshold: 0.05\n", - " t_step: 1.0\n", - " t_error_threshold: 0.02\n", - " ```\n", - "\n", - "5) Add new comment, only in YAML, comment start with #.\n", - "\n", - "6) Delete a parameter, section or comment.\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "id": "58b1b25d-4682-48e9-9290-c861dbb99fb2", - "metadata": {}, - "source": [ - "## Modeler" - ] - }, - { - "cell_type": "markdown", - "id": "4c397ddd-3f07-47dd-8e22-92ed0e89f6fd", - "metadata": {}, - "source": [ - "The modeler constructs the configuration. A Config is a Python dictionary that stores sections and parameters. A Config is built from a list of ModelUnit objects.\n" - ] - }, - { - "cell_type": "markdown", - "id": "6d68dfd5-305b-4c53-ab42-d2ed14a9cad4", - "metadata": {}, - "source": [ - "First, we have 3 classes: Parameter, ModelUnit and Config. " - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "id": "dd4c69a0-dc54-4473-9e4f-8227e1a4e766", - "metadata": {}, - "outputs": [], - "source": [ - "@dataclass \n", - "class Parameter:\n", - " '''The dataclass Parameter has two mandatory parameters name and value, the others parameters are optional to the config, if\n", - " we add them this would be commented, if we don't add them, we don't have comment. We also convert this class in dictionary.'''\n", - " name: str\n", - " value: any = None\n", - " description : any = None\n", - " unit : any = None\n", - " param_type : any = None\n", - " uid : any = None\n", - " uri : any = None\n", - "\n", - "\n", - " def __to_dict__(self):\n", - " return {self.name: self.value}\n", - " \n", - " def __str__(self):\n", - " return (\n", - " f\"name={self.name}, value={self.value}\"\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "3fea1168-04f1-48ac-a807-6f711bfc5133", - "metadata": {}, - "outputs": [], - "source": [ - "class ModelUnit(dict):\n", - " '''ModelUnit is a dictionary with a section name and a list of parameters '''\n", - " def __init__(self, name, parameters: list):\n", - " super().__init__({name: {p.name: p.value for p in parameters}})\n", - " self.name = name\n", - " self.parameters = parameters\n", - "\n", - " def __str__(self):\n", - " return f\"name={self.name}, parameters={dict(self)}\"" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "id": "0e1d4f73-b9b3-40fe-b816-83807abe71f3", - "metadata": {}, - "outputs": [], - "source": [ - "class Config(dict):\n", - " \"\"\"Configuration of OpenAlea models\n", - " Config is a dictionary that contains a list of model units. With the config can:\n", - " -Load a new JSON or YAML file.\n", - " -Dump a JSON or YAML file.\n", - " -Add new sections.\n", - " -Add new comments.\n", - " -The quantity of units.\n", - " \n", - " TODO : Documentation to write\n", - " \"\"\"\n", - "\n", - " def __init__(self, model_unit_configs: list):\n", - " \"\"\"Initialize the configuration with a list of unit configurations.\"\"\"\n", - "\n", - " super().__init__()\n", - " self.model_unit_configs = model_unit_configs\n", - " self.params_comments = {}\n", - " self.section_comments = {}\n", - "\n", - " for unit in model_unit_configs:\n", - " self.update(unit)\n", - "\n", - " def add_section(self, unit):\n", - " self.model_unit_configs.append(unit)\n", - " self.update(unit)\n", - "\n", - " def dump(self, filename: str):\n", - " \"\"\"Dump configuration to a file.\"\"\"\n", - "\n", - " extension = filename.split(\".\")[-1]\n", - "\n", - " if extension in (\"yml\", \"yaml\"):\n", - " yaml = YAML()\n", - " cm = to_commented_map(self)\n", - " \n", - "\n", - " add_comment(cm, self.params_comments)\n", - " add_comment2(cm, self.model_unit_configs)\n", - " add_section_comments(cm, self.section_comments)\n", - "\n", - "\n", - " with open(filename, \"w\") as f:\n", - " yaml.dump(cm, f)\n", - "\n", - " elif extension == \"json\":\n", - " _dump_json(dict(self), filename)\n" - ] - }, - { - "cell_type": "markdown", - "id": "437eeae0-46fd-42e0-8298-c91bfcc2b484", - "metadata": {}, - "source": [ - "The first thing the Modeler do is generate new parameters with an instance of the Parameter class. And an instance of the ModelUnit class, ModelUnit is a dictionary that contains a list of Parameters. And the config contains a list of model units and can generate a new config. A Config is built from a list of ModelUnit objects." - ] - }, - { - "cell_type": "code", - "execution_count": 46, - "id": "597d2dce-4f57-4f9e-b78e-0555ebcca90e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'simulation': {'sdate': '2012-08-01 00:00:00', 'edate': '2012-08-04 23:00:00', 'latitude': 43.61, 'longitude': 3.87, 'elevation': 44.0, 'tzone': 'Europe/Paris', 'output_index': '', 'unit_scene_length': 'cm', 'hydraulic_structure': True, 'negligible_shoot_resistance': False, 'energy_budget': True}}\n" - ] - } - ], - "source": [ - "p_sdate = Parameter(\"sdate\", \"2012-08-01 00:00:00\", \"Start date of the simulation\")\n", - "p_edate = Parameter(\"edate\", \"2012-08-04 23:00:00\", \"End date of the simulation\")\n", - "p_lat = Parameter(\"latitude\", 43.61, None, \"degrees\", \"float\")\n", - "p_longitude = Parameter(\"longitude\", 3.87, \"Longitude of the simulation\", \"degrees\", \"float\")\n", - "p_elevation = Parameter(\"elevation\", 44.0, None, \"meters\", \"float\")\n", - "p_tzone = Parameter(\"tzone\", \"Europe/Paris\", \"Time zone\", None, \"string\")\n", - "p_output_index = Parameter(\"output_index\", \"\")\n", - "p_unit_scene_length = Parameter(\"unit_scene_length\", \"cm\")\n", - "p_hydraulic_structure = Parameter(\"hydraulic_structure\", True, None, None, \"boolean\")\n", - "p_negligible_shoot_resistance = Parameter(\"negligible_shoot_resistance\", False)\n", - "p_energy_budget = Parameter(\"energy_budget\", True)\n", - "parameters = [p_sdate, p_edate, p_lat, p_longitude, p_elevation, p_tzone, p_output_index, p_unit_scene_length, p_hydraulic_structure, p_negligible_shoot_resistance, p_energy_budget]\n", - "simulation = ModelUnit('simulation', parameters)\n", - "config = Config([simulation])\n", - "print(config)" - ] - }, - { - "cell_type": "markdown", - "id": "01de7c15-870a-4aaa-af1b-5115c842a577", - "metadata": {}, - "source": [ - "The modeler can also load and dump a configuration file as config.dump(\"params.yml\") or config.dump(\"params.json\"). The modeler can also add new sections, for example *config.add_section(planting)*. In this example, I can add new section." - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "id": "54e7bc9d-13c6-4cf6-883a-e2fc2cb43f8d", - "metadata": {}, - "outputs": [], - "source": [ - "p_spacing_between_rows = Parameter(\"spacing_between_rows\", 3.6, \"Distance between planting rows\", \"meters\", \"float\")\n", - "p_spacing_on_row = Parameter(\"spacing_on_row\", 1, None, \"meters\", \"float\")\n", - "p_row_angle_with_south = Parameter(\"row_angle_with_south\", 140.0, None, \"degrees\", \"float\")\n", - "\n", - "parameters = [p_spacing_between_rows, p_spacing_on_row, p_row_angle_with_south]\n", - "planting = ModelUnit('planting', parameters)\n", - "config.add_section(planting)" - ] - }, - { - "cell_type": "markdown", - "id": "eeecb6c8-317e-49a2-a34b-d750db0b94f8", - "metadata": {}, - "source": [ - "## Developer" - ] - }, - { - "cell_type": "markdown", - "id": "8e72f695-9284-44e6-be1a-45bf0ad02747", - "metadata": {}, - "source": [ - "The developer receives a config object. Config unherite from a dict, the developer can access to the parameters value. He can initialize the model and call run()." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fe154355-a5d1-4880-8f25-7f1e44b1b8f0", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b7051bf1-cb7c-4154-8b68-e6270cc6d286", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.13" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} From 8dd9c4aec50142a56f3f26443c972486aa4bb027 Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Fri, 3 Jul 2026 15:13:42 +0200 Subject: [PATCH 32/35] Add description to the sections --- test/test_config.py | 70 +++++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/test/test_config.py b/test/test_config.py index df0388d..e089049 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -1,66 +1,65 @@ -from __future__ import absolute_import -import os from openalea.core.config import Parameter, ModelUnit, Config import yaml import json def hydroshoot_simulation_config(): - p_sdate = Parameter("sdate", "2012-08-01 00:00:00", "Start date of the simulation") - p_edate = Parameter("edate", "2012-08-04 23:00:00", "End date of the simulation") + p_sdate = Parameter("sdate", "2012-08-01 00:00:00", "Start date of the simulation", None, "datetime") + p_edate = Parameter("edate", "2012-08-04 23:00:00", "End date of the simulation", None, "datetime") p_lat = Parameter("latitude", 43.61, None, "degrees", "float") p_longitude = Parameter("longitude", 3.87, "Longitude of the simulation", "degrees", "float") - p_elevation = Parameter("elevation", 44.0, None, "meters", "float") + p_elevation = Parameter("elevation", 44.0, "Elevation of the simulation", "meters", "float") p_tzone = Parameter("tzone", "Europe/Paris", "Time zone", None, "string") - p_output_index = Parameter("output_index", "") - p_unit_scene_length = Parameter("unit_scene_length", "cm") - p_hydraulic_structure = Parameter("hydraulic_structure", True, None, None, "boolean") - p_negligible_shoot_resistance = Parameter("negligible_shoot_resistance", False) - p_energy_budget = Parameter("energy_budget", True) + p_output_index = Parameter("output_index", "", "Output index", None, "string") + p_unit_scene_length = Parameter("unit_scene_length", "cm", "Unit of scene length", None, "string") + p_hydraulic_structure = Parameter("hydraulic_structure", True, "Hydraulic structure", None, "boolean") + p_negligible_shoot_resistance = Parameter("negligible_shoot_resistance", False, "Negligible short resistance", None, "boolean") + p_energy_budget = Parameter("energy_budget", True, "Energy budget", None, "boolean") parameters = [p_sdate, p_edate, p_lat, p_longitude, p_elevation, p_tzone, p_output_index, p_unit_scene_length, p_hydraulic_structure, p_negligible_shoot_resistance, p_energy_budget] - simulation = ModelUnit('simulation', parameters) + simulation = ModelUnit("simulation", parameters, "Simulations parameters") + print(simulation) return simulation def hydroshoot_planting_config(): p_spacing_between_rows = Parameter("spacing_between_rows", 3.6, "Distance between planting rows", "meters", "float") - p_spacing_on_row = Parameter("spacing_on_row", 1, None, "meters", "float") - p_row_angle_with_south = Parameter("row_angle_with_south", 140.0, None, "degrees", "float") + p_spacing_on_row = Parameter("spacing_on_row", 1, "Spacing on the row", "meters", "float") + p_row_angle_with_south = Parameter("row_angle_with_south", 140.0, "Row angle with south", "degrees", "float") parameters = [p_spacing_between_rows, p_spacing_on_row, p_row_angle_with_south] - planting = ModelUnit('planting', parameters) + planting = ModelUnit('planting', parameters, "Planting section") return planting def hydroshoot_phenology_config(): - p_emdate = Parameter("emdate", "2012-04-01 00:00:00", None, None, "datetime") - p_t_base = Parameter("t_base", 10.0, None, "degrees", "float") + p_emdate = Parameter("emdate", "2012-04-01 00:00:00", "Emergence date", None, "datetime") + p_t_base = Parameter("t_base", 10.0, "Base temperature", "degrees", "float") parameters = [p_emdate, p_t_base] - phenology = ModelUnit('phenology', parameters) + phenology = ModelUnit('phenology', parameters, "Phenology section") return phenology def hydroshoot_mtg_api_config(): - p_collar_label = Parameter("collar_label", "inT") - p_leaf_lbl_prefix = Parameter("leaf_lbl_prefix", "L") - p_stem_lbl_prefix = Parameter("stem_lbl_prefix", ["in", "Pet", "cx"]) + p_collar_label = Parameter("collar_label", "inT", "Collar label", None, "string") + p_leaf_lbl_prefix = Parameter("leaf_lbl_prefix", "L", "Leaf label prefix", None, "string") + p_stem_lbl_prefix = Parameter("stem_lbl_prefix", ["in", "Pet", "cx"], "Stem label prefix", None, "list") parameters = [p_collar_label, p_leaf_lbl_prefix, p_stem_lbl_prefix] - mtg_api = ModelUnit("mtg_api", parameters) + mtg_api = ModelUnit("mtg_api", parameters, "Mtg api section") return mtg_api def hydroshoot_numerical_resolution_config(): - p_max_iter = Parameter("max_iter", 100, None, None, "integer") - p_psi_step = Parameter("psi_step", 1.0, None, None, "float") - p_psi_error_threshold = Parameter("psi_error_threshold", 0.05, None, "seconds") - p_t_step = Parameter("t_step", 1.0) - p_t_error_threshold = Parameter("t_error_threshold", 0.02) + p_max_iter = Parameter("max_iter", 100, "Maximum number of iterations", None, "integer") + p_psi_step = Parameter("psi_step", 1.0, "Psi step", None, "float") + p_psi_error_threshold = Parameter("psi_error_threshold", 0.05, "Psi error threshold", "seconds") + p_t_step = Parameter("t_step", 1.0, "Time step", None, "float") + p_t_error_threshold = Parameter("t_error_threshold", 0.02, "Time error threshold", "float") parameters = [p_max_iter, p_psi_step, p_psi_error_threshold, p_t_step, p_t_error_threshold] - numerical_resolution = ModelUnit("numerical_resolution", parameters) + numerical_resolution = ModelUnit("numerical_resolution", parameters, "Numerical section") return numerical_resolution @@ -75,28 +74,31 @@ def test_config_hs(): config.section_comments["simulation"] = "Simulation" planting = hydroshoot_planting_config() config.add_section(planting) - config.dump("params4.yml") + config.dump("params.yml") assert len(config) == 2 assert len(config["planting"]) == 3 phenology = hydroshoot_phenology_config() config.add_section(phenology) - #config.custom_comments["sdate"] = "date of simulation" - #config.custom_comments["latitude"] = ["param_type : float", "unit : degrees"] mtg_api = hydroshoot_mtg_api_config() config.add_section(mtg_api) - config.dump("params4.yml") + config.dump("params.yml") numerical_resolution = hydroshoot_numerical_resolution_config() config.add_section(numerical_resolution) - config.dump("params4.yml") - config.dump("params4.json") + config.dump("params.yml") + config.dump("params.json") - config.load("params4.yml") + config.load("params.yml") assert len(config) == 5 assert len(config["planting"]) == 3 + value = config["simulation"]["sdate"] + print('value ' + value) + section = config["simulation"] + print('section') + print(section) test_config_hs() From 14d3e88a23bc733a44d09f64babf34daa4bf4136 Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Fri, 3 Jul 2026 15:17:03 +0200 Subject: [PATCH 33/35] Json and yml file --- test/params.json | 40 +++++++++++++++++++++ test/params.yml | 91 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 test/params.json create mode 100644 test/params.yml diff --git a/test/params.json b/test/params.json new file mode 100644 index 0000000..9d76a63 --- /dev/null +++ b/test/params.json @@ -0,0 +1,40 @@ +{ + "simulation": { + "sdate": "2012-08-01 00:00:00", + "edate": "2012-08-04 23:00:00", + "latitude": 43.61, + "longitude": 3.87, + "elevation": 44.0, + "tzone": "Europe/Paris", + "output_index": "", + "unit_scene_length": "cm", + "hydraulic_structure": true, + "negligible_shoot_resistance": false, + "energy_budget": true + }, + "planting": { + "spacing_between_rows": 3.6, + "spacing_on_row": 1, + "row_angle_with_south": 140.0 + }, + "phenology": { + "emdate": "2012-04-01 00:00:00", + "t_base": 10.0 + }, + "mtg_api": { + "collar_label": "inT", + "leaf_lbl_prefix": "L", + "stem_lbl_prefix": [ + "in", + "Pet", + "cx" + ] + }, + "numerical_resolution": { + "max_iter": 100, + "psi_step": 1.0, + "psi_error_threshold": 0.05, + "t_step": 1.0, + "t_error_threshold": 0.02 + } +} \ No newline at end of file diff --git a/test/params.yml b/test/params.yml new file mode 100644 index 0000000..adf5415 --- /dev/null +++ b/test/params.yml @@ -0,0 +1,91 @@ +# description: Simulations parameters +simulation: +# description: Start date of the simulation +# param_type: datetime + sdate: '2012-08-01 00:00:00' +# description: End date of the simulation +# param_type: datetime + edate: '2012-08-04 23:00:00' +# unit: degrees +# param_type: float + latitude: 43.61 +# description: Longitude of the simulation +# unit: degrees +# param_type: float + longitude: 3.87 +# description: Elevation of the simulation +# unit: meters +# param_type: float + elevation: 44.0 +# description: Time zone +# param_type: string + tzone: Europe/Paris +# description: Output index +# param_type: string + output_index: '' +# description: Unit of scene length +# param_type: string + unit_scene_length: cm +# description: Hydraulic structure +# param_type: boolean + hydraulic_structure: true +# description: Negligible short resistance +# param_type: boolean + negligible_shoot_resistance: false +# description: Energy budget +# param_type: boolean + energy_budget: true +# description: Planting section +planting: +# description: Distance between planting rows +# unit: meters +# param_type: float + spacing_between_rows: 3.6 +# description: Spacing on the row +# unit: meters +# param_type: float + spacing_on_row: 1 +# description: Row angle with south +# unit: degrees +# param_type: float + row_angle_with_south: 140.0 +# description: Phenology section +phenology: +# description: Emergence date +# param_type: datetime + emdate: '2012-04-01 00:00:00' +# description: Base temperature +# unit: degrees +# param_type: float + t_base: 10.0 +# description: Mtg api section +mtg_api: +# description: Collar label +# param_type: string + collar_label: inT +# description: Leaf label prefix +# param_type: string + leaf_lbl_prefix: L +# description: Stem label prefix +# param_type: list + stem_lbl_prefix: + - in + - Pet + - cx +# description: Numerical section +numerical_resolution: +# description: Maximum number of iterations +# param_type: integer + max_iter: 100 +# description: Psi step +# param_type: float + psi_step: 1.0 +# description: Psi error threshold +# unit: seconds + psi_error_threshold: 0.05 +# description: Time step +# param_type: float + t_step: 1.0 +# description: Time error threshold +# unit: float + t_error_threshold: 0.02 From 6e94dba76959361b382d2ef0db7c8c70ef9529ad Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Fri, 3 Jul 2026 15:18:48 +0200 Subject: [PATCH 34/35] last version of config, new parameters in modelUnit --- src/openalea/core/config.py | 165 ++++++++++++++++++------------------ 1 file changed, 82 insertions(+), 83 deletions(-) diff --git a/src/openalea/core/config.py b/src/openalea/core/config.py index f5c448b..d0e5686 100644 --- a/src/openalea/core/config.py +++ b/src/openalea/core/config.py @@ -31,7 +31,10 @@ from ruamel.yaml import YAML def _load_json(filename: str): - """Load configuration from a JSON file.""" + """ + Load a configuration from a JSON file. + :param filename: the JSON configuration file. + """ file = Path(filename) with file.open() as f: @@ -39,7 +42,10 @@ def _load_json(filename: str): return data def _load_yml(filename: str): - """Load configuration from a YAML file.""" + """ + Load a configuration from a YAML file. + :param filename: the YAML configuration file. + """ file = Path(filename) with file.open() as f: @@ -47,20 +53,34 @@ def _load_yml(filename: str): return data def _dump_yml(data, filename: str, sort_keys=False): - """Dump configuration to a YAML file.""" + """ + Dump configuration to a YAML file. + :param data: configuration data to write. + :param filename: YAML file path. + :param sort_keys: sort keys. + """ file = Path(filename) with file.open("w") as f: yaml.dump(data, f, sort_keys=sort_keys) def _dump_json(data, filename: str): - """Dump configuration to a JSON file.""" + """ + Dump configuration to a JSON file. + :param data: configuration data to write. + :param filename: JSON file path. + """ file = Path(filename) with file.open("w") as f: json.dump(data, f, indent=4) def to_commented_map(obj): + """ + Convert a Python dictionary into a ruamel.yaml CommentedMap. + :param obj: dictionary. + """ + if isinstance(obj, dict): cm = CommentedMap() for k, v in obj.items(): @@ -71,8 +91,10 @@ def to_commented_map(obj): def add_unit_comment(cm, model_unit_configs): """ - Add automatic comments above each ModelUnit section - using the optional fields of ModelUnit (description, unit, param_type, uid, uri). + Add comments above each ModelUnit section. + The comments are generated from optional ModelUnit fields: description, unit, param_type, uid, and uri. + :param cm: CommentedMap. + :param model_unit_configs: List of ModelUnit objects. """ fields = ["description", "unit", "param_type", "uid", "uri"] @@ -93,11 +115,13 @@ def add_unit_comment(cm, model_unit_configs): def add_comment2(cm, model_unit_configs): - '''Add automatic new comments on Parameters (description, unit, param_type, uid and uri). - parameters: -cm (CommentedMap) that contains sections and values from parameters. - -model_unit_configs: is a list of ModelUnit - If the value is None, this function don't add a new comment. If the value is not None, we add the comment. - ''' + """ + Add comments to each parameter. Comments are generated from optional Parameter fields: + description, unit, param_type, uid, and uri. + If the value is None, this function doesn't add a new comment. If the value is not None, we add the comment. + :param cm: CommentedMap. + :param model_unit_configs: List of ModelUnit objects. + """ param_index = {} for unit in model_unit_configs: @@ -121,54 +145,23 @@ def add_comment2(cm, model_unit_configs): before="\n".join(comments) ) -''' -def add_comment(cm, custom_comments=None): - - if custom_comments is None: - custom_comments = {} - - for section_name, section in cm.items(): - for param_name, param_value in section.items(): - if param_name in custom_comments: - comment = custom_comments[param_name] - - if isinstance(comment, list): - comment = "\n".join(comment) - - section.yaml_set_comment_before_after_key( - param_name, - before=comment - ) - - -def add_section_comments(cm, section_comments): - - for section_name in cm.keys(): - if section_name in section_comments: - comment = section_comments[section_name] - - if isinstance(comment, list): - comment = "\n".join(comment) - - cm.yaml_set_comment_before_after_key( - section_name, - before=comment - ) -''' @dataclass class Parameter: - '''The dataclass Parameter has two mandatory parameters name and value, the others parameters are optional to the config, if - we add them this would be commented, if we don't add them, we don't have comment. We also convert this class in dictionary.''' + """ + Represent a configuration parameter. + The dataclass Parameter has two mandatory parameters name and value. The others parameters are optional to the configuration, if + we add them this would be commented. + The parameter can be converted to dictionary. + """ name: str - value: any = None + value: any description : any = None unit : any = None param_type : any = None uid : any = None uri : any = None - def __to_dict__(self): return {self.name: self.value} @@ -177,31 +170,19 @@ def __str__(self): f"name={self.name}, value={self.value}" ) -''' -class ModelUnit(dict): - def __init__(self, name, parameters: list): - - params_dict = {} - for p in parameters: - params_dict.update(p.__to_dict__()) - - super().__init__({name: params_dict}) - self.name = name - self.parameters=parameters - - def __str__(self): - return f"name={self.name}, parameters={dict(self)}" -''' class ModelUnit(dict): - '''ModelUnit is a dictionary with a section name and a list of parameters ''' + """ + Represent a configuration sectiion with a name a list of parameters. + :param name: Name of the section. + :param parameters: List of Parameter objects. + :param description: Optional description of the section. + """ def __init__(self, name, parameters: list, description=None, unit=None, param_type=None, uid=None, uri=None): super().__init__({name: {p.name: p.value for p in parameters}}) self.name = name self.parameters = parameters self.description = description - self.unit = unit - self.param_type = param_type self.uid = uid self.uri = uri @@ -210,19 +191,17 @@ def __str__(self): class Config(dict): - """Configuration of OpenAlea models - Config is a dictionary that contains a list of model units. With the config can: - -Load a new JSON or YAML file. - -Dump a JSON or YAML file. - -Add new sections. - -Add new comments. - -The quantity of units. - - TODO : Documentation to write + """ + Configuration of OpenAlea models. + A Config object is a dictionary that contains a list of ModelUnit. A config can load, dump, add new sections, new sections, + the quantity of units. + :param model_unit_configs: List of ModelUnit objects. """ def __init__(self, model_unit_configs: list): - """Initialize the configuration with a list of unit configurations.""" + """ + Initialize the configuration with a list of unit configurations. + """ super().__init__() self.model_unit_configs = model_unit_configs @@ -245,7 +224,11 @@ def add_section(self, unit): def load(self, filename: str): - """Load configuration to a file.""" + """ + Load a configuration file (YAML or JSON) and return a new Config. + :param filename: configuration file. + """ + extension = filename.split(".")[-1] if extension in ("yml", "yaml"): @@ -270,7 +253,10 @@ def load(self, filename: str): def dump(self, filename: str): - """Dump configuration to a file.""" + """ + Dump the configuration to a YAML or JSON file. + :param filename: filename path. + """ extension = filename.split(".")[-1] @@ -278,12 +264,8 @@ def dump(self, filename: str): yaml = YAML() cm = to_commented_map(self) - - #add_comment(cm, self.params_comments) add_comment2(cm, self.model_unit_configs) add_unit_comment(cm, self.model_unit_configs) - #add_section_comments(cm, self.section_comments) - with open(filename, "w") as f: yaml.dump(cm, f) @@ -291,4 +273,21 @@ def dump(self, filename: str): elif extension == "json": _dump_json(dict(self), filename) + def get_sections(self): + """ + Return the list of section names in the configuration. + """ + + return list(self.keys()) + + def get_parameters(self, section_name): + """ + Return the list of parameter names of a section. + :param section_name: Name of the section. + """ + + if section_name not in self: + raise KeyError(f"Section '{section_name}' doesn't exist.") + return list(self[section_name].keys()) + From 2d92d103be4e1c40817a170ca8a78df79a7ac735 Mon Sep 17 00:00:00 2001 From: LeticiaNapolitano-34 Date: Fri, 3 Jul 2026 15:19:10 +0200 Subject: [PATCH 35/35] The tutorial in Jupyter notebook --- src/openalea/core/Documentation.ipynb | 420 ++++++++++++++++++++++++++ 1 file changed, 420 insertions(+) create mode 100644 src/openalea/core/Documentation.ipynb diff --git a/src/openalea/core/Documentation.ipynb b/src/openalea/core/Documentation.ipynb new file mode 100644 index 0000000..aca80a2 --- /dev/null +++ b/src/openalea/core/Documentation.ipynb @@ -0,0 +1,420 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "16c62968-51e4-4d0a-8305-874f666d5180", + "metadata": {}, + "source": [ + "# Tutorial" + ] + }, + { + "cell_type": "markdown", + "id": "04b358d7-3d1d-4c05-b03e-c431732b960d", + "metadata": {}, + "source": [ + "## User" + ] + }, + { + "cell_type": "markdown", + "id": "12ef5489-ac5c-431a-9c6b-695c998f35fc", + "metadata": {}, + "source": [ + "The user interacts only with the configuration file, not with Python code.\n", + "\n", + "1) Open the configuration file, in YAML or JSON format.\n", + " \n", + "The user can open the file in Visual Studio Code or any other text editor. For example:\n", + "```yaml\n", + "# description: Simulations parameters\n", + "simulation:\n", + "# description: Start date of the simulation\n", + "# param_type: datetime\n", + " sdate: '2012-08-01 00:00:00'\n", + "# description: End date of the simulation\n", + "# param_type: datetime\n", + " edate: '2012-08-04 23:00:00'\n", + "# unit: degrees\n", + "# param_type: float\n", + " latitude: 43.61\n", + "# description: Longitude of the simulation\n", + "# unit: degrees\n", + "# param_type: float\n", + " longitude: 3.87\n", + "# description: Elevation of the simulation\n", + "# unit: meters\n", + "# param_type: float\n", + " elevation: 44.0\n", + "# description: Time zone\n", + "# param_type: string\n", + " tzone: Europe/Paris\n", + "# description: Output index\n", + "# param_type: string\n", + " output_index: ''\n", + "# description: Unit of scene length\n", + "# param_type: string\n", + " unit_scene_length: cm\n", + "# description: Hydraulic structure\n", + "# param_type: boolean\n", + " hydraulic_structure: true\n", + "# description: Negligible short resistance\n", + "# param_type: boolean\n", + " negligible_shoot_resistance: false\n", + "# description: Energy budget\n", + "# param_type: boolean\n", + " energy_budget: true\n", + "# description: Planting section\n", + "planting:\n", + "# description: Distance between planting rows\n", + "# unit: meters\n", + "# param_type: float\n", + " spacing_between_rows: 3.6\n", + "# description: Spacing on the row\n", + "# unit: meters\n", + "# param_type: float\n", + " spacing_on_row: 1\n", + "# description: Row angle with south\n", + "# unit: degrees\n", + "# param_type: float\n", + " row_angle_with_south: 140.0\n", + "# description: Phenology section\n", + "phenology:\n", + "# description: Emergence date\n", + "# param_type: datetime\n", + " emdate: '2012-04-01 00:00:00'\n", + "# description: Base temperature\n", + "# unit: degrees\n", + "# param_type: float\n", + " t_base: 10.0\n", + "# description: Mtg api section\n", + "mtg_api:\n", + "# description: Collar label\n", + "# param_type: string\n", + " collar_label: inT\n", + "# description: Leaf label prefix\n", + "# param_type: string\n", + " leaf_lbl_prefix: L\n", + "# description: Stem label prefix\n", + "# param_type: list\n", + " stem_lbl_prefix:\n", + " - in\n", + " - Pet\n", + " - cx\n", + "# description: Numerical section\n", + "numerical_resolution:\n", + "# description: Maximum number of iterations\n", + "# param_type: integer\n", + " max_iter: 100\n", + "# description: Psi step\n", + "# param_type: float\n", + " psi_step: 1.0\n", + "# description: Psi error threshold\n", + "# unit: seconds\n", + " psi_error_threshold: 0.05\n", + "# description: Time step\n", + "# param_type: float\n", + " t_step: 1.0\n", + "# description: Time error threshold\n", + "# unit: float\n", + " t_error_threshold: 0.02\n", + "\n", + " ```\n", + "\n", + "Simulation and planting are sections. *sdate, edate, latitude* and all the parameters under the simulation and planting sections are parameters. All lines starting with # are comments.\n", + "In the description, we should explain what the parameter and the section do. Each parameter can include optional comments such as unit and param_type." + ] + }, + { + "cell_type": "markdown", + "id": "20e21ed0-70ba-4d5a-aa69-399abefe6ccf", + "metadata": {}, + "source": [ + "2) Modify a value.\n", + "\n", + "The user can change any value directly in the JSON or YAML file. \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "58b1b25d-4682-48e9-9290-c861dbb99fb2", + "metadata": {}, + "source": [ + "## Modeler" + ] + }, + { + "cell_type": "markdown", + "id": "4c397ddd-3f07-47dd-8e22-92ed0e89f6fd", + "metadata": {}, + "source": [ + "The modeler define the configuration object from submodels. A model is composed of sub-models, each sub-model return a ModelUnitConfig. Each modelUnitConfig contains a list of parameters. A Config is a Python dictionary that stores sections and their parameters. A Config is built from a list of ModelUnit objects. \n" + ] + }, + { + "cell_type": "markdown", + "id": "6d68dfd5-305b-4c53-ab42-d2ed14a9cad4", + "metadata": {}, + "source": [ + "In the config, we have 3 classes: Parameter, ModelUnit and Config. " + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "dd4c69a0-dc54-4473-9e4f-8227e1a4e766", + "metadata": {}, + "outputs": [], + "source": [ + "from dataclasses import dataclass\n", + "from openalea.core.config import Config, ModelUnit, Parameter" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "0e1d4f73-b9b3-40fe-b816-83807abe71f3", + "metadata": {}, + "outputs": [], + "source": [ + "from ruamel.yaml import YAML\n", + "from config import to_commented_map, add_unit_comment, add_comment2\n" + ] + }, + { + "cell_type": "markdown", + "id": "437eeae0-46fd-42e0-8298-c91bfcc2b484", + "metadata": {}, + "source": [ + "The Modeler can create new parameters using instances of the Parameter class. A ModelUnit is a dictionary that contains a list of Parameter instances.\n", + "For example:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "597d2dce-4f57-4f9e-b78e-0555ebcca90e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[{'simulation': {'sdate': '2012-08-01 00:00:00', 'edate': '2012-08-04 23:00:00', 'latitude': 43.61, 'longitude': 3.87, 'elevation': 44.0, 'tzone': 'Europe/Paris', 'output_index': '', 'unit_scene_length': 'cm', 'hydraulic_structure': True, 'negligible_shoot_resistance': False, 'energy_budget': True}}]\n" + ] + } + ], + "source": [ + "p_sdate = Parameter(\"sdate\", \"2012-08-01 00:00:00\", \"Start date of the simulation\", None, \"datetime\")\n", + "p_edate = Parameter(\"edate\", \"2012-08-04 23:00:00\", \"End date of the simulation\", None, \"datetime\")\n", + "p_lat = Parameter(\"latitude\", 43.61, None, \"degrees\", \"float\")\n", + "p_longitude = Parameter(\"longitude\", 3.87, \"Longitude of the simulation\", \"degrees\", \"float\")\n", + "p_elevation = Parameter(\"elevation\", 44.0, \"Elevation of the simulation\", \"meters\", \"float\")\n", + "p_tzone = Parameter(\"tzone\", \"Europe/Paris\", \"Time zone\", None, \"string\")\n", + "p_output_index = Parameter(\"output_index\", \"\", \"Output index\", None, \"string\")\n", + "p_unit_scene_length = Parameter(\"unit_scene_length\", \"cm\", \"Unit of scene length\", None, \"string\")\n", + "p_hydraulic_structure = Parameter(\"hydraulic_structure\", True, \"Hydraulic structure\", None, \"boolean\")\n", + "p_negligible_shoot_resistance = Parameter(\"negligible_shoot_resistance\", False, \"Negligible short resistance\", None, \"boolean\")\n", + "p_energy_budget = Parameter(\"energy_budget\", True, \"Energy budget\", None, \"boolean\")\n", + "parameters = [p_sdate, p_edate, p_lat, p_longitude, p_elevation, p_tzone, p_output_index, p_unit_scene_length, p_hydraulic_structure, p_negligible_shoot_resistance, p_energy_budget]\n", + "simulation = ModelUnit(\"simulation\", parameters, \"Simulations parameters\")\n", + "config = Config([simulation])\n", + "print(config)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "54e7bc9d-13c6-4cf6-883a-e2fc2cb43f8d", + "metadata": {}, + "outputs": [], + "source": [ + "p_spacing_between_rows = Parameter(\"spacing_between_rows\", 3.6, \"Distance between planting rows\", \"meters\", \"float\")\n", + "p_spacing_on_row = Parameter(\"spacing_on_row\", 1, \"Spacing on the row\", \"meters\", \"float\")\n", + "p_row_angle_with_south = Parameter(\"row_angle_with_south\", 140.0, \"Row angle with south\", \"degrees\", \"float\")\n", + "parameters = [p_spacing_between_rows, p_spacing_on_row, p_row_angle_with_south]\n", + "planting = ModelUnit('planting', parameters, \"Planting section\")" + ] + }, + { + "cell_type": "markdown", + "id": "3cdbc30e-fa0f-4a90-955f-dc5ca97c919d", + "metadata": {}, + "source": [ + "Add new sections." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "2f1b072b-be43-4a5c-a8e3-c063616620bf", + "metadata": {}, + "outputs": [], + "source": [ + "config.add_section(planting)" + ] + }, + { + "cell_type": "markdown", + "id": "3be19f2e-5d4f-4f0c-84c4-34ab56ffda4c", + "metadata": {}, + "source": [ + "The modeler can also load and dump a configuration file." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "e8b5003a-44eb-4193-b16b-421c7657199c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[{'simulation': {'sdate': '2012-08-01 00:00:00', 'edate': '2012-08-04 23:00:00', 'latitude': 43.61, 'longitude': 3.87, 'elevation': 44.0, 'tzone': 'Europe/Paris', 'output_index': '', 'unit_scene_length': 'cm', 'hydraulic_structure': True, 'negligible_shoot_resistance': False, 'energy_budget': True}}, {'planting': {'spacing_between_rows': 3.6, 'spacing_on_row': 2.5, 'row_angle_with_south': 140.0}}]\n", + "New value :\n", + "2.5\n" + ] + } + ], + "source": [ + "from ruamel.yaml import YAML\n", + "\n", + "config.dump(\"config.yml\")\n", + "config[\"planting\"][\"spacing_on_row\"] = 2.5\n", + "config.dump(\"config.yml\")\n", + "new_config = config.load(\"config.yml\")\n", + "print(new_config)\n", + "print(\"New value :\")\n", + "print(config[\"planting\"][\"spacing_on_row\"])\n" + ] + }, + { + "cell_type": "markdown", + "id": "eeecb6c8-317e-49a2-a34b-d750db0b94f8", + "metadata": {}, + "source": [ + "## Developer" + ] + }, + { + "cell_type": "markdown", + "id": "8e72f695-9284-44e6-be1a-45bf0ad02747", + "metadata": {}, + "source": [ + "The developer receives a config object. Config inherits from a dict, so the developer can access parameters values directly. He can initialize the model and run it.\n", + "For example, to access the value of a parameter:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "d6dd0b34-e9f1-4a27-9a62-d4110f202a76", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2012-08-01 00:00:00\n" + ] + } + ], + "source": [ + "value = config[\"simulation\"][\"sdate\"]\n", + "print(value)" + ] + }, + { + "cell_type": "markdown", + "id": "787d6ed0-1bb2-40a8-b1ed-ec1f3149994e", + "metadata": {}, + "source": [ + "The developer can also access a specific section." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "29f4704b-7c30-44f6-836c-ecb7d13f9af7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'sdate': '2012-08-01 00:00:00', 'edate': '2012-08-04 23:00:00', 'latitude': 43.61, 'longitude': 3.87, 'elevation': 44.0, 'tzone': 'Europe/Paris', 'output_index': '', 'unit_scene_length': 'cm', 'hydraulic_structure': True, 'negligible_shoot_resistance': False, 'energy_budget': True}\n" + ] + } + ], + "source": [ + "section = config[\"simulation\"]\n", + "print(section)" + ] + }, + { + "cell_type": "markdown", + "id": "945f3dec-e769-4d26-8e71-5772a710ac54", + "metadata": {}, + "source": [ + "The developer can use the config API, the developer can get all sections of the configuration which returns a list of section names. He can also access all parameters of a specific section. Each parameter has a default value and and the developer can check whether its current value has been modified or not." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "196c68b8-d369-44d0-a591-8398fa6912b9", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['simulation', 'planting']\n", + "['sdate', 'edate', 'latitude', 'longitude', 'elevation', 'tzone', 'output_index', 'unit_scene_length', 'hydraulic_structure', 'negligible_shoot_resistance', 'energy_budget']\n" + ] + } + ], + "source": [ + "sections = config.get_sections()\n", + "print(sections)\n", + "\n", + "parameters = config.get_parameters('simulation')\n", + "print(parameters)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}