Coverage for physioblocks / configuration / description / blocks.py: 84%
92 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-09 16:40 +0100
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-09 16:40 +0100
1# SPDX-FileCopyrightText: Copyright INRIA
2#
3# SPDX-License-Identifier: LGPL-3.0-only
4#
5# Copyright INRIA
6#
7# This file is part of PhysioBlocks, a library mostly developed by the
8# [Ananke project-team](https://team.inria.fr/ananke) at INRIA.
9#
10# Authors:
11# - Colin Drieu
12# - Dominique Chapelle
13# - François Kimmig
14# - Philippe Moireau
15#
16# PhysioBlocks is free software: you can redistribute it and/or modify it under the
17# terms of the GNU Lesser General Public License as published by the Free Software
18# Foundation, version 3 of the License.
19#
20# PhysioBlocks is distributed in the hope that it will be useful, but WITHOUT ANY
21# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
22# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
23#
24# You should have received a copy of the GNU Lesser General Public License along with
25# PhysioBlocks. If not, see <https://www.gnu.org/licenses/>.
27"""
28Define how to configure block.
29"""
31from typing import Any
33from physioblocks.configuration.base import Configuration, ConfigurationError
34from physioblocks.configuration.constants import (
35 BLOCK_FLUX_TYPE_ITEM_ID,
36 MODEL_COMPONENT_TYPE_ITEM_ID,
37 SUBMODEL_ITEM_ID,
38)
39from physioblocks.configuration.functions import load, save
40from physioblocks.description.blocks import BlockDescription, ModelComponentDescription
41from physioblocks.registers.load_function_register import loads
42from physioblocks.registers.save_function_register import saves
43from physioblocks.registers.type_register import (
44 get_registered_type,
45 get_registered_type_id,
46)
47from physioblocks.simulation.time_manager import TIME_QUANTITY_ID
49VARIABLES_ITEM_ID = "variables"
50"""Constant for variables item id in the global id dict"""
52PARAMETERS_ITEM_ID = "parameters"
53"""Constant for parameters item id in the global id dict"""
55SAVED_QUANTITIES_ITEM_ID = "saved_quantities"
56"""Constant for saved quantities item id in the global id dict"""
59@saves(ModelComponentDescription)
60def save_model_component_config(
61 model_component: ModelComponentDescription, *args: Any, **kwargs: Any
62) -> Configuration:
63 """
64 Create a Configuration for a model description.
66 :param model_component: the model_component description
67 :type model_component: ModelComponentDescription
69 :return: the configuration
70 :rtype: Configuration
71 """
73 description_type_id = get_registered_type_id(type(model_component))
74 model_type_id = get_registered_type_id(model_component.described_type)
75 config = Configuration(description_type_id)
76 config[MODEL_COMPONENT_TYPE_ITEM_ID] = model_type_id
78 # save local ids
79 global_ids = _get_global_ids(model_component)
81 config.configuration_items.update(global_ids)
83 # save sub-models
84 if len(model_component.submodels) > 0:
85 config[SUBMODEL_ITEM_ID] = save(model_component.submodels, *args, **kwargs)
87 return config
90@saves(BlockDescription)
91def save_block_description_config(
92 block_description: BlockDescription, *args: Any, **kwargs: Any
93) -> Configuration:
94 config: Configuration = save_model_component_config(block_description)
95 config[BLOCK_FLUX_TYPE_ITEM_ID] = block_description.flux_type
96 return config
99def _get_global_ids(model: ModelComponentDescription) -> dict[str, Any]:
100 global_ids: dict[str, Any] = {}
102 for local_id, global_id in model.global_ids.items():
103 if global_id == TIME_QUANTITY_ID:
104 global_ids[TIME_QUANTITY_ID] = TIME_QUANTITY_ID
105 elif model.described_type.has_internal_variable(local_id):
106 if VARIABLES_ITEM_ID not in global_ids:
107 global_ids[VARIABLES_ITEM_ID] = {}
108 global_ids[VARIABLES_ITEM_ID][local_id] = global_id
109 elif model.described_type.has_saved_quantity(local_id):
110 if SAVED_QUANTITIES_ITEM_ID not in global_ids:
111 global_ids[SAVED_QUANTITIES_ITEM_ID] = {}
112 global_ids[SAVED_QUANTITIES_ITEM_ID][local_id] = global_id
113 else:
114 if PARAMETERS_ITEM_ID not in global_ids:
115 global_ids[PARAMETERS_ITEM_ID] = {}
116 global_ids[PARAMETERS_ITEM_ID][local_id] = global_id
118 return global_ids
121@loads(ModelComponentDescription)
122def load_model_component_config(
123 configuration: Configuration,
124 configuration_key: str,
125 configuration_type: type[ModelComponentDescription],
126 configuration_object: BlockDescription | None = None,
127 additional_model_arguments: dict[str, Any] | None = None,
128 *args: Any,
129 **kwargs: Any,
130) -> ModelComponentDescription:
131 """
132 Load a model component description from a configuration.
134 :param config: the configuration
135 :type config: Configuration
137 :return: the model description
138 :rtype: ModelComponentDescription
139 """
140 additional_model_arguments = (
141 {} if additional_model_arguments is None else additional_model_arguments
142 )
143 model_type = get_registered_type(configuration[MODEL_COMPONENT_TYPE_ITEM_ID])
145 model_ids = {}
146 model_ids = _get_model_global_ids(configuration)
148 sub_models_desc = {}
149 if SUBMODEL_ITEM_ID in configuration.configuration_items:
150 sub_models_desc = load(configuration[SUBMODEL_ITEM_ID], *args, **kwargs)
152 if configuration_object is not None:
153 new_model_ids = configuration_object.global_ids.copy()
154 new_model_ids.update(model_ids)
155 new_submodels = configuration_object.submodels
156 new_submodels.update(sub_models_desc)
157 else:
158 new_model_ids = model_ids
159 new_submodels = sub_models_desc
161 return configuration_type(
162 configuration_key,
163 model_type,
164 global_ids=new_model_ids,
165 submodels=new_submodels,
166 **additional_model_arguments,
167 )
170@loads(BlockDescription)
171def load_block_description_config(
172 configuration: Configuration,
173 configuration_key: str,
174 configuration_type: type[ModelComponentDescription],
175 configuration_object: BlockDescription | None = None,
176 *args: Any,
177 **kwargs: Any,
178) -> BlockDescription:
179 if BLOCK_FLUX_TYPE_ITEM_ID not in configuration:
180 raise ConfigurationError(
181 str.format(
182 """Missing item {0} in block {1}.""",
183 BLOCK_FLUX_TYPE_ITEM_ID,
184 configuration_key,
185 )
186 )
187 block_additional_arguments = {
188 BLOCK_FLUX_TYPE_ITEM_ID: configuration[BLOCK_FLUX_TYPE_ITEM_ID]
189 }
190 return load_model_component_config(
191 configuration,
192 configuration_key,
193 configuration_type,
194 configuration_object,
195 *args,
196 additional_model_arguments=block_additional_arguments,
197 **kwargs,
198 ) # type: ignore
201def _get_model_global_ids(config: Configuration) -> dict[str, str]:
202 model_ids = config.configuration_items.copy()
204 # remove the submodels and model type keys from the model ids (keep them in the
205 # configuration)
206 if SUBMODEL_ITEM_ID in model_ids:
207 model_ids.pop(SUBMODEL_ITEM_ID)
209 if MODEL_COMPONENT_TYPE_ITEM_ID in model_ids:
210 model_ids.pop(MODEL_COMPONENT_TYPE_ITEM_ID)
212 if BLOCK_FLUX_TYPE_ITEM_ID in model_ids:
213 model_ids.pop(BLOCK_FLUX_TYPE_ITEM_ID)
215 # Recursivlely unfold global id in configuration or dict:
216 model_ids = _unfold_global_ids(model_ids)
218 return model_ids
221def _unfold_global_ids(global_ids: dict[str, Any]) -> dict[str, str]:
222 result = {}
224 for key, value in global_ids.items():
225 if isinstance(value, str):
226 result[key] = value
227 elif isinstance(value, dict):
228 result.update(_unfold_global_ids(value))
229 elif isinstance(value, Configuration):
230 result.update(_unfold_global_ids(value.configuration_items))
231 else:
232 raise ConfigurationError(
233 str.format(
234 """{0}: {1} is not configurable as a model local to global id.""",
235 key,
236 value,
237 )
238 )
239 return result