physioblocks 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- physioblocks/__init__.py +37 -0
- physioblocks/base/__init__.py +27 -0
- physioblocks/base/operators.py +176 -0
- physioblocks/base/registers.py +108 -0
- physioblocks/computing/__init__.py +47 -0
- physioblocks/computing/assembling.py +291 -0
- physioblocks/computing/models.py +811 -0
- physioblocks/computing/quantities.py +354 -0
- physioblocks/configuration/__init__.py +38 -0
- physioblocks/configuration/aliases.py +203 -0
- physioblocks/configuration/base.py +123 -0
- physioblocks/configuration/computing/__init__.py +27 -0
- physioblocks/configuration/computing/quantities.py +56 -0
- physioblocks/configuration/constants.py +121 -0
- physioblocks/configuration/description/__init__.py +33 -0
- physioblocks/configuration/description/blocks.py +239 -0
- physioblocks/configuration/description/nets.py +155 -0
- physioblocks/configuration/functions.py +695 -0
- physioblocks/configuration/simulation/__init__.py +32 -0
- physioblocks/configuration/simulation/simulations.py +280 -0
- physioblocks/description/__init__.py +34 -0
- physioblocks/description/blocks.py +418 -0
- physioblocks/description/flux.py +157 -0
- physioblocks/description/nets.py +746 -0
- physioblocks/io/__init__.py +29 -0
- physioblocks/io/aliases.py +73 -0
- physioblocks/io/configuration.py +125 -0
- physioblocks/launcher/__main__.py +285 -0
- physioblocks/launcher/configuration.py +231 -0
- physioblocks/launcher/configure/__main__.py +99 -0
- physioblocks/launcher/constants.py +105 -0
- physioblocks/launcher/files.py +150 -0
- physioblocks/launcher/series.py +165 -0
- physioblocks/library/__init__.py +27 -0
- physioblocks/library/aliases/blocks/c_block.json +5 -0
- physioblocks/library/aliases/blocks/rc_block.json +5 -0
- physioblocks/library/aliases/blocks/rcr_block.json +5 -0
- physioblocks/library/aliases/blocks/spherical_cavity_block.json +5 -0
- physioblocks/library/aliases/blocks/valve_rl_block.json +5 -0
- physioblocks/library/aliases/flux/heart_flux_dof_couples.jsonc +4 -0
- physioblocks/library/aliases/model_components/active_law_macro_huxley_two_moments.json +5 -0
- physioblocks/library/aliases/model_components/rheology_fiber_additive.json +5 -0
- physioblocks/library/aliases/model_components/spherical_dynamics.json +5 -0
- physioblocks/library/aliases/model_components/velocity_law_hht.json +5 -0
- physioblocks/library/aliases/nets/circulation_alone_net.json +31 -0
- physioblocks/library/aliases/nets/spherical_heart_net.json +93 -0
- physioblocks/library/aliases/simulations/circulation_alone_forward_simulation.jsonc +55 -0
- physioblocks/library/aliases/simulations/default_forward_simulation.jsonc +7 -0
- physioblocks/library/aliases/simulations/default_time.jsonc +8 -0
- physioblocks/library/aliases/simulations/newton_method_solver.json +5 -0
- physioblocks/library/aliases/simulations/spherical_heart_forward_simulation.jsonc +157 -0
- physioblocks/library/aliases/simulations/spherical_heart_with_respiration_forward_simulation.jsonc +45 -0
- physioblocks/library/blocks/__init__.py +27 -0
- physioblocks/library/blocks/capacitances.py +516 -0
- physioblocks/library/blocks/cavity.py +192 -0
- physioblocks/library/blocks/valves.py +281 -0
- physioblocks/library/functions/__init__.py +27 -0
- physioblocks/library/functions/base_operations.py +129 -0
- physioblocks/library/functions/first_order.py +113 -0
- physioblocks/library/functions/piecewise.py +271 -0
- physioblocks/library/functions/trigonometric.py +78 -0
- physioblocks/library/functions/watchers.py +113 -0
- physioblocks/library/model_components/__init__.py +27 -0
- physioblocks/library/model_components/active_law.py +345 -0
- physioblocks/library/model_components/dynamics.py +986 -0
- physioblocks/library/model_components/rheology.py +160 -0
- physioblocks/library/model_components/velocity_law.py +169 -0
- physioblocks/references/circulation_alone_sim.jsonc +24 -0
- physioblocks/references/spherical_heart_respiration_sim.jsonc +33 -0
- physioblocks/references/spherical_heart_sim.jsonc +29 -0
- physioblocks/registers/__init__.py +32 -0
- physioblocks/registers/load_function_register.py +93 -0
- physioblocks/registers/save_function_register.py +106 -0
- physioblocks/registers/type_register.py +97 -0
- physioblocks/simulation/__init__.py +48 -0
- physioblocks/simulation/constants.py +30 -0
- physioblocks/simulation/functions.py +71 -0
- physioblocks/simulation/runtime.py +484 -0
- physioblocks/simulation/saved_quantities.py +129 -0
- physioblocks/simulation/setup.py +576 -0
- physioblocks/simulation/solvers.py +235 -0
- physioblocks/simulation/state.py +340 -0
- physioblocks/simulation/time_manager.py +354 -0
- physioblocks/utils/__init__.py +27 -0
- physioblocks/utils/dynamic_import_utils.py +150 -0
- physioblocks/utils/exceptions_utils.py +115 -0
- physioblocks/utils/gradient_test_utils.py +337 -0
- physioblocks/utils/math_utils.py +109 -0
- physioblocks-1.0.0.dist-info/METADATA +127 -0
- physioblocks-1.0.0.dist-info/RECORD +93 -0
- physioblocks-1.0.0.dist-info/WHEEL +4 -0
- physioblocks-1.0.0.dist-info/licenses/licenses/GPL-3.0-only.txt +674 -0
- physioblocks-1.0.0.dist-info/licenses/licenses/LGPL-3.0-only.txt +165 -0
|
@@ -0,0 +1,695 @@
|
|
|
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/>.
|
|
26
|
+
|
|
27
|
+
"""Declares functions to load and save PhysioBlocks object to generic
|
|
28
|
+
:class:`~physioblocks.configuration.base.Configuration` objects.
|
|
29
|
+
|
|
30
|
+
Before using generic :func:`load` and :func:`save` functions on a PhysioBlocks object,
|
|
31
|
+
the object type must be registered with the
|
|
32
|
+
:func:`~physioblocks.registers.type_register.register_type` decorator.
|
|
33
|
+
|
|
34
|
+
To define a specific behavior when saving or loading an registered object with the
|
|
35
|
+
generic :func:`load` or :func:`save` functions, declare a function decorated with
|
|
36
|
+
:func:`~physioblocks.registers.load_function_register.loads` or
|
|
37
|
+
:func:`~physioblocks.registers.save_function_register.saves`.
|
|
38
|
+
|
|
39
|
+
.. note::
|
|
40
|
+
|
|
41
|
+
If you want to create a **Configurable Item** for a dataclass type,
|
|
42
|
+
you will not have to register a specific save or load function.
|
|
43
|
+
|
|
44
|
+
Registering the type the :func:`~physioblocks.registers.type_register.register_type`
|
|
45
|
+
decorator will suffice to create a configuration item that needs the same parameters
|
|
46
|
+
as the dataclass.
|
|
47
|
+
|
|
48
|
+
See :doc:`register module <./registers>` to for decorators documentation to
|
|
49
|
+
:func:`~physioblocks.registers.type_register.register_type` as well as
|
|
50
|
+
:func:`~physioblocks.registers.load_function_register.loads` and
|
|
51
|
+
:func:`~physioblocks.registers.save_function_register.saves`.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
import functools
|
|
55
|
+
from collections.abc import Iterable, Mapping, Sequence
|
|
56
|
+
from inspect import signature
|
|
57
|
+
from typing import Any, TypeAlias, TypeVar
|
|
58
|
+
|
|
59
|
+
import numpy as np
|
|
60
|
+
from numpy.typing import NDArray
|
|
61
|
+
|
|
62
|
+
from physioblocks.configuration.base import Configuration, ConfigurationError
|
|
63
|
+
from physioblocks.registers.load_function_register import get_load_function, loads
|
|
64
|
+
from physioblocks.registers.save_function_register import (
|
|
65
|
+
get_save_function,
|
|
66
|
+
has_save_function,
|
|
67
|
+
)
|
|
68
|
+
from physioblocks.registers.type_register import (
|
|
69
|
+
get_registered_type,
|
|
70
|
+
get_registered_type_id,
|
|
71
|
+
is_registered,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
BaseTypes: TypeAlias = float | int | bool | str
|
|
75
|
+
"""Type alias for basic types usable in a Configuration File"""
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def load(
|
|
79
|
+
configuration: Any,
|
|
80
|
+
configuration_key: str | None = None,
|
|
81
|
+
configuration_object: Any | None = None,
|
|
82
|
+
configuration_type: type[Any] | None = None,
|
|
83
|
+
configuration_references: dict[str, Any] | None = None,
|
|
84
|
+
configuration_sort: bool = False,
|
|
85
|
+
) -> Any:
|
|
86
|
+
"""
|
|
87
|
+
Generic load an object from the given configuration item.
|
|
88
|
+
|
|
89
|
+
The method can load:
|
|
90
|
+
- :class:`~physioblocks.configuration.base.Configuration`: Use the matching
|
|
91
|
+
registered load function.
|
|
92
|
+
- `dict` and `list`: recursivly load values in the collection
|
|
93
|
+
|
|
94
|
+
:param configuration: the configuration to load
|
|
95
|
+
:type configuration: Any
|
|
96
|
+
|
|
97
|
+
:param configuration_key: (optional) key of the configuration in the parent
|
|
98
|
+
configuration item.
|
|
99
|
+
:type configuration_key: str
|
|
100
|
+
|
|
101
|
+
:param configuration_object: (optional) The object to configure.
|
|
102
|
+
If empty, a the object is first instanciated then configured.
|
|
103
|
+
:type configuration_object: Any
|
|
104
|
+
|
|
105
|
+
:param configuration_type: (optional) the type of the object to configure.
|
|
106
|
+
If empty, it is determined from the configuration object.
|
|
107
|
+
:type configuration_type: Any
|
|
108
|
+
|
|
109
|
+
:param configuration_references: (optional) mapping of configuration item keys
|
|
110
|
+
with already configured objects to use in the current configured object.
|
|
111
|
+
:type configuration_references: dict[str, Any]
|
|
112
|
+
|
|
113
|
+
:param configuration_sort: (optional) flag to signal that configuration items
|
|
114
|
+
should be sorted be sorted before they are loaded. Default is False.
|
|
115
|
+
:type configuration_sort: dict[str, Any]
|
|
116
|
+
|
|
117
|
+
:return: the configured object
|
|
118
|
+
:rtype: Any
|
|
119
|
+
"""
|
|
120
|
+
if (
|
|
121
|
+
isinstance(configuration, str)
|
|
122
|
+
and configuration_references is not None
|
|
123
|
+
and configuration in configuration_references
|
|
124
|
+
):
|
|
125
|
+
# the value is already in the references:
|
|
126
|
+
return (
|
|
127
|
+
configuration_references[configuration]
|
|
128
|
+
if configuration_type is None
|
|
129
|
+
else configuration_type(configuration_references[configuration])
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
elif configuration_type is not None:
|
|
133
|
+
load_func = get_load_function(configuration_type)
|
|
134
|
+
return load_func(
|
|
135
|
+
configuration,
|
|
136
|
+
configuration_key=configuration_key,
|
|
137
|
+
configuration_object=configuration_object,
|
|
138
|
+
configuration_type=configuration_type,
|
|
139
|
+
configuration_references=configuration_references,
|
|
140
|
+
configuration_sort=configuration_sort,
|
|
141
|
+
)
|
|
142
|
+
elif isinstance(configuration, BaseTypes):
|
|
143
|
+
# No load function required
|
|
144
|
+
return configuration
|
|
145
|
+
|
|
146
|
+
elif isinstance(configuration, Configuration):
|
|
147
|
+
return load_configuration(
|
|
148
|
+
configuration,
|
|
149
|
+
configuration_key=configuration_key,
|
|
150
|
+
configuration_object=configuration_object,
|
|
151
|
+
configuration_type=configuration_type,
|
|
152
|
+
configuration_references=configuration_references,
|
|
153
|
+
configuration_sort=configuration_sort,
|
|
154
|
+
)
|
|
155
|
+
elif isinstance(configuration, Mapping):
|
|
156
|
+
return load_dict(
|
|
157
|
+
configuration,
|
|
158
|
+
configuration_key=configuration_key,
|
|
159
|
+
configuration_object=configuration_object,
|
|
160
|
+
configuration_type=configuration_type,
|
|
161
|
+
configuration_references=configuration_references,
|
|
162
|
+
configuration_sort=configuration_sort,
|
|
163
|
+
)
|
|
164
|
+
elif isinstance(configuration, Sequence):
|
|
165
|
+
return load_list(
|
|
166
|
+
configuration,
|
|
167
|
+
configuration_key=configuration_key,
|
|
168
|
+
configuration_object=configuration_object,
|
|
169
|
+
configuration_type=configuration_type,
|
|
170
|
+
configuration_references=configuration_references,
|
|
171
|
+
configuration_sort=configuration_sort,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
raise TypeError(
|
|
175
|
+
str.format(
|
|
176
|
+
"Type {0} can not be loaded as a configuration.",
|
|
177
|
+
type(configuration).__name__,
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def load_configuration(
|
|
183
|
+
configuration: Configuration,
|
|
184
|
+
configuration_key: str | None = None,
|
|
185
|
+
configuration_object: Any | None = None,
|
|
186
|
+
configuration_references: dict[str, Any] | None = None,
|
|
187
|
+
configuration_sort: bool = False,
|
|
188
|
+
*args: Any,
|
|
189
|
+
**kwargs: Any,
|
|
190
|
+
) -> Any:
|
|
191
|
+
"""
|
|
192
|
+
Specific load function for a
|
|
193
|
+
:class:`~physioblocks.configuration.base.Configuration`: configuration item.
|
|
194
|
+
|
|
195
|
+
It recursivly loads any configuration item used in the ``configuration`` parameter.
|
|
196
|
+
|
|
197
|
+
:param configuration: the configuration item to load
|
|
198
|
+
:type configuration: Configuration
|
|
199
|
+
|
|
200
|
+
:param configuration_key: (optional) key of the configuration in the parent
|
|
201
|
+
configuration item.
|
|
202
|
+
:type configuration_key: str
|
|
203
|
+
|
|
204
|
+
:param configuration_object: (optional) The object to configure.
|
|
205
|
+
If empty, a the object is first instanciated then configured.
|
|
206
|
+
:type configuration_object: Any
|
|
207
|
+
|
|
208
|
+
:param configuration_type: (optional) the type of the object to configure.
|
|
209
|
+
If empty, it is determined from the configuration object.
|
|
210
|
+
:type configuration_type: Any
|
|
211
|
+
|
|
212
|
+
:param configuration_references: (optional) mapping of configuration item keys
|
|
213
|
+
with already configured objects to use in the current configured object.
|
|
214
|
+
:type configuration_references: dict[str, Any]
|
|
215
|
+
|
|
216
|
+
:param configuration_sort: (optional) flag to signal that configuration items
|
|
217
|
+
should be sorted be sorted before they are loaded. Default is False.
|
|
218
|
+
:type configuration_sort: dict[str, Any]
|
|
219
|
+
|
|
220
|
+
:return: the configured object
|
|
221
|
+
:rtype: Any
|
|
222
|
+
"""
|
|
223
|
+
configuration = (
|
|
224
|
+
configuration
|
|
225
|
+
if configuration_sort is False
|
|
226
|
+
else __sort_configuration(configuration)
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
new_configuration_type = get_registered_type(configuration.label)
|
|
230
|
+
load_func = get_load_function(new_configuration_type)
|
|
231
|
+
|
|
232
|
+
return load_func(
|
|
233
|
+
configuration,
|
|
234
|
+
configuration_key=configuration_key,
|
|
235
|
+
configuration_object=configuration_object,
|
|
236
|
+
configuration_type=new_configuration_type,
|
|
237
|
+
configuration_references=configuration_references,
|
|
238
|
+
configuration_sort=configuration_sort,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def load_dict(
|
|
243
|
+
configuration: Mapping[str, Any],
|
|
244
|
+
configuration_object: dict[str, Any] | None = None,
|
|
245
|
+
configuration_references: dict[str, Any] | None = None,
|
|
246
|
+
configuration_sort: bool = False,
|
|
247
|
+
*args: Any,
|
|
248
|
+
**kwargs: Any,
|
|
249
|
+
) -> dict[str, Any]:
|
|
250
|
+
"""
|
|
251
|
+
Specific load function for a `Mapping` configuration item.
|
|
252
|
+
|
|
253
|
+
It recursivly loads any configuration item used in the mapping values.
|
|
254
|
+
|
|
255
|
+
:param configuration: the configuration item to load
|
|
256
|
+
:type configuration: Configuration
|
|
257
|
+
|
|
258
|
+
:param configuration_key: (optional) key of the configuration in the parent
|
|
259
|
+
configuration item.
|
|
260
|
+
:type configuration_key: str
|
|
261
|
+
|
|
262
|
+
:param configuration_object: (optional) The object to configure.
|
|
263
|
+
If empty, a the object is first instanciated then configured.
|
|
264
|
+
:type configuration_object: Any
|
|
265
|
+
|
|
266
|
+
:param configuration_type: (optional) the type of the object to configure.
|
|
267
|
+
If empty, it is determined from the configuration object.
|
|
268
|
+
:type configuration_type: Any
|
|
269
|
+
|
|
270
|
+
:param configuration_references: (optional) mapping of configuration item keys
|
|
271
|
+
with already configured objects to use in the current configured object.
|
|
272
|
+
:type configuration_references: dict[str, Any]
|
|
273
|
+
|
|
274
|
+
:param configuration_sort: (optional) flag to signal that configuration items
|
|
275
|
+
should be sorted be sorted before they are loaded. Default is False.
|
|
276
|
+
:type configuration_sort: dict[str, Any]
|
|
277
|
+
|
|
278
|
+
:return: the configured object
|
|
279
|
+
:rtype: Any
|
|
280
|
+
"""
|
|
281
|
+
configuration = (
|
|
282
|
+
configuration
|
|
283
|
+
if configuration_sort is False
|
|
284
|
+
else __sort_configuration(configuration)
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
if configuration_object is None:
|
|
288
|
+
configuration_object = {}
|
|
289
|
+
|
|
290
|
+
updated_references = (
|
|
291
|
+
configuration_references.copy() if configuration_references is not None else {}
|
|
292
|
+
)
|
|
293
|
+
configuration_values = {}
|
|
294
|
+
|
|
295
|
+
for key, value in configuration.items():
|
|
296
|
+
loaded_obj = load(
|
|
297
|
+
value,
|
|
298
|
+
configuration_key=key,
|
|
299
|
+
configuration_object=configuration_object.get(key),
|
|
300
|
+
configuration_type=type(configuration_object.get(key))
|
|
301
|
+
if configuration_object.get(key) is not None
|
|
302
|
+
and not isinstance(configuration_object.get(key), BaseTypes)
|
|
303
|
+
else None,
|
|
304
|
+
configuration_references=updated_references,
|
|
305
|
+
configuration_sort=configuration_sort,
|
|
306
|
+
)
|
|
307
|
+
configuration_values[key] = loaded_obj
|
|
308
|
+
updated_references[key] = loaded_obj
|
|
309
|
+
if isinstance(loaded_obj, Mapping):
|
|
310
|
+
updated_references.update(loaded_obj)
|
|
311
|
+
|
|
312
|
+
configuration_object.update(configuration_values)
|
|
313
|
+
|
|
314
|
+
return configuration_object
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def load_list(
|
|
318
|
+
configuration: Sequence[Any],
|
|
319
|
+
configuration_object: list[Any] | None = None,
|
|
320
|
+
configuration_references: dict[str, Any] | None = None,
|
|
321
|
+
configuration_sort: bool = False,
|
|
322
|
+
*args: Any,
|
|
323
|
+
**kwargs: Any,
|
|
324
|
+
) -> Sequence[Any]:
|
|
325
|
+
"""
|
|
326
|
+
Specific load function for a `Sequence` configuration item.
|
|
327
|
+
|
|
328
|
+
It recursivly loads any configuration item used in the sequence values.
|
|
329
|
+
|
|
330
|
+
:param configuration: the configuration item to load
|
|
331
|
+
:type configuration: Configuration
|
|
332
|
+
|
|
333
|
+
:param configuration_key: (optional) key of the configuration in the parent
|
|
334
|
+
configuration item.
|
|
335
|
+
:type configuration_key: str
|
|
336
|
+
|
|
337
|
+
:param configuration_object: (optional) The object to configure.
|
|
338
|
+
If empty, a the object is first instanciated then configured.
|
|
339
|
+
:type configuration_object: Any
|
|
340
|
+
|
|
341
|
+
:param configuration_type: (optional) the type of the object to configure.
|
|
342
|
+
If empty, it is determined from the configuration object.
|
|
343
|
+
:type configuration_type: Any
|
|
344
|
+
|
|
345
|
+
:param configuration_references: (optional) mapping of configuration item keys
|
|
346
|
+
with already configured objects to use in the current configured object.
|
|
347
|
+
:type configuration_references: dict[str, Any]
|
|
348
|
+
|
|
349
|
+
:param configuration_sort: (optional) flag to signal that configuration items
|
|
350
|
+
should be sorted be sorted before they are loaded. Default is False.
|
|
351
|
+
:type configuration_sort: dict[str, Any]
|
|
352
|
+
|
|
353
|
+
:return: the configured object
|
|
354
|
+
:rtype: Any
|
|
355
|
+
"""
|
|
356
|
+
|
|
357
|
+
if configuration_object is None:
|
|
358
|
+
configuration_object = []
|
|
359
|
+
|
|
360
|
+
configuration_values = [
|
|
361
|
+
load(
|
|
362
|
+
configuration[index],
|
|
363
|
+
configuration_object=configuration_object[index]
|
|
364
|
+
if index < len(configuration_object)
|
|
365
|
+
else None,
|
|
366
|
+
configuration_type=type(configuration_object[index])
|
|
367
|
+
if index < len(configuration_object)
|
|
368
|
+
and not isinstance(configuration[index], BaseTypes)
|
|
369
|
+
else None,
|
|
370
|
+
configuration_references=configuration_references,
|
|
371
|
+
configuration_sort=configuration_sort,
|
|
372
|
+
)
|
|
373
|
+
for index in range(0, len(configuration))
|
|
374
|
+
]
|
|
375
|
+
|
|
376
|
+
return configuration_values
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
@loads(bool)
|
|
380
|
+
def _bool_load(
|
|
381
|
+
configuration: Any,
|
|
382
|
+
*args: Any,
|
|
383
|
+
**kwargs: Any,
|
|
384
|
+
) -> bool:
|
|
385
|
+
if isinstance(configuration, str):
|
|
386
|
+
return configuration.lower() == str(True)
|
|
387
|
+
return bool(configuration)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
T = TypeVar("T")
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
@loads(object)
|
|
394
|
+
def _base_load(
|
|
395
|
+
configuration: Any,
|
|
396
|
+
configuration_key: str | None = None,
|
|
397
|
+
configuration_object: T | None = None,
|
|
398
|
+
configuration_type: type[T] | None = None,
|
|
399
|
+
configuration_references: dict[str, Any] | None = None,
|
|
400
|
+
configuration_sort: bool = False,
|
|
401
|
+
*args: Any,
|
|
402
|
+
**kwargs: Any,
|
|
403
|
+
) -> T:
|
|
404
|
+
"""
|
|
405
|
+
Load function called when no other load function is defined.
|
|
406
|
+
|
|
407
|
+
:param configuration: the configuration to load
|
|
408
|
+
:type configuration: Configuration
|
|
409
|
+
|
|
410
|
+
:param configuration_object: The object to configure.
|
|
411
|
+
If empty, a new object is created and configured.
|
|
412
|
+
:type configuration_object: Any
|
|
413
|
+
|
|
414
|
+
:return: the configured object
|
|
415
|
+
:rtype: Any
|
|
416
|
+
"""
|
|
417
|
+
if configuration_type is None and configuration_object is not None:
|
|
418
|
+
configuration_type = type(configuration_object)
|
|
419
|
+
elif configuration_type is None:
|
|
420
|
+
raise ConfigurationError(
|
|
421
|
+
str.format("Missing configuration type for {0}:{1}.", configuration_key)
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
config_args: list[Any] = []
|
|
425
|
+
config_kwargs: dict[str, Any] = {}
|
|
426
|
+
|
|
427
|
+
if isinstance(configuration, Configuration | Mapping):
|
|
428
|
+
config_kwargs.update(configuration)
|
|
429
|
+
elif isinstance(configuration, str | float | int | bool):
|
|
430
|
+
config_args.append(configuration)
|
|
431
|
+
elif isinstance(configuration, Sequence):
|
|
432
|
+
config_args.extend(configuration)
|
|
433
|
+
|
|
434
|
+
# load the values in the provided arguments
|
|
435
|
+
config_args = load(
|
|
436
|
+
config_args,
|
|
437
|
+
configuration_key=configuration_key,
|
|
438
|
+
configuration_references=configuration_references,
|
|
439
|
+
configuration_sort=configuration_sort,
|
|
440
|
+
)
|
|
441
|
+
config_kwargs = load(
|
|
442
|
+
config_kwargs,
|
|
443
|
+
configuration_key=configuration_key,
|
|
444
|
+
configuration_references=configuration_references,
|
|
445
|
+
configuration_sort=configuration_sort,
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
if configuration_object is None:
|
|
449
|
+
try:
|
|
450
|
+
configuration_object = configuration_type(*config_args, **config_kwargs)
|
|
451
|
+
except Exception as exception:
|
|
452
|
+
raise ConfigurationError(
|
|
453
|
+
str.format("Error while initialising key {0}", configuration_key)
|
|
454
|
+
) from exception
|
|
455
|
+
else:
|
|
456
|
+
if len(config_args) == 0:
|
|
457
|
+
for key, value in config_kwargs.items():
|
|
458
|
+
setattr(configuration_object, key, value)
|
|
459
|
+
else:
|
|
460
|
+
raise ConfigurationError(
|
|
461
|
+
str.format(
|
|
462
|
+
"Can not set arguments {0} to existing object {1}. "
|
|
463
|
+
"Missing attribute keys.",
|
|
464
|
+
config_args,
|
|
465
|
+
configuration_object,
|
|
466
|
+
)
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
return configuration_object
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
@functools.singledispatch
|
|
473
|
+
def save(
|
|
474
|
+
obj: Any,
|
|
475
|
+
configuration_references: dict[str, Any] | None = None,
|
|
476
|
+
*args: Any,
|
|
477
|
+
**kwargs: Any,
|
|
478
|
+
) -> Any:
|
|
479
|
+
"""
|
|
480
|
+
Save an object to a configuration.
|
|
481
|
+
|
|
482
|
+
It first check if the object has a ``save`` function registered and use it.
|
|
483
|
+
|
|
484
|
+
When no specific ``save`` function is registered for a object type,
|
|
485
|
+
it saves recursivly every annotated parameters of the object class.
|
|
486
|
+
|
|
487
|
+
:param obj: the object to save
|
|
488
|
+
:type obj: Any
|
|
489
|
+
|
|
490
|
+
:return: the object configuration
|
|
491
|
+
:rtype: Any
|
|
492
|
+
|
|
493
|
+
:raise ConfigurationError: raise a Configuration Error when the
|
|
494
|
+
object can not be saved to a configuration.
|
|
495
|
+
"""
|
|
496
|
+
|
|
497
|
+
# try to replace the object with a reference when possible.
|
|
498
|
+
if configuration_references is not None:
|
|
499
|
+
obj_reference = next(
|
|
500
|
+
(key for key, item in configuration_references.items() if item is obj), None
|
|
501
|
+
)
|
|
502
|
+
if obj_reference is not None:
|
|
503
|
+
return obj_reference
|
|
504
|
+
|
|
505
|
+
obj_type = type(obj)
|
|
506
|
+
|
|
507
|
+
if has_save_function(obj_type) is True:
|
|
508
|
+
save_func = get_save_function(obj_type)
|
|
509
|
+
return save_func(
|
|
510
|
+
obj, *args, configuration_references=configuration_references, **kwargs
|
|
511
|
+
)
|
|
512
|
+
elif is_registered(obj_type):
|
|
513
|
+
return _base_save_obj(
|
|
514
|
+
obj, *args, configuration_references=configuration_references, **kwargs
|
|
515
|
+
)
|
|
516
|
+
raise ConfigurationError(str.format("Can not configure object {0}.", obj))
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def _base_save_obj(obj: Any, *args: Any, **kwargs: Any) -> Configuration:
|
|
520
|
+
obj_type = type(obj)
|
|
521
|
+
type_id = get_registered_type_id(obj_type)
|
|
522
|
+
|
|
523
|
+
# get parameters of the constructor
|
|
524
|
+
parameters_ids = _get_init_parameters(obj_type)
|
|
525
|
+
config_parameters = {
|
|
526
|
+
key: save(getattr(obj, key), *args, **kwargs) for key in parameters_ids
|
|
527
|
+
}
|
|
528
|
+
return Configuration(type_id, config_parameters)
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
@save.register
|
|
532
|
+
def _save_dict(obj: Mapping, *args: Any, **kwargs: Any) -> dict[str, Any]: # type: ignore
|
|
533
|
+
"""Save specific function for mappings."""
|
|
534
|
+
return {
|
|
535
|
+
key: save(
|
|
536
|
+
value,
|
|
537
|
+
*args,
|
|
538
|
+
**kwargs,
|
|
539
|
+
)
|
|
540
|
+
for key, value in obj.items()
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
@save.register
|
|
545
|
+
def _save_list(obj: Sequence, *args: Any, **kwargs: Any) -> list[Any]: # type: ignore
|
|
546
|
+
return [
|
|
547
|
+
save(
|
|
548
|
+
value,
|
|
549
|
+
*args,
|
|
550
|
+
**kwargs,
|
|
551
|
+
)
|
|
552
|
+
for value in obj
|
|
553
|
+
]
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
@save.register(float)
|
|
557
|
+
@save.register(str)
|
|
558
|
+
@save.register(int)
|
|
559
|
+
def _save_base_types(
|
|
560
|
+
obj: Any,
|
|
561
|
+
configuration_references: dict[str, Any] | None = None,
|
|
562
|
+
*args: Any,
|
|
563
|
+
**kwargs: Any,
|
|
564
|
+
) -> Any:
|
|
565
|
+
if configuration_references is not None:
|
|
566
|
+
obj_reference = next(
|
|
567
|
+
(
|
|
568
|
+
key
|
|
569
|
+
for key, item in configuration_references.items()
|
|
570
|
+
if isinstance(item, type(obj)) and item == obj
|
|
571
|
+
),
|
|
572
|
+
None,
|
|
573
|
+
)
|
|
574
|
+
if obj_reference is not None:
|
|
575
|
+
return obj_reference
|
|
576
|
+
return obj
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
@save.register
|
|
580
|
+
def _save_bool(obj: bool, *args: Any, **kwargs: Any) -> str:
|
|
581
|
+
return str(obj)
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
@save.register(np.ndarray)
|
|
585
|
+
def _save_array(obj: NDArray[Any], *args: Any, **kwargs: Any) -> Any:
|
|
586
|
+
return float(obj) if obj.size == 1 else obj.tolist()
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
def _get_init_parameters(obj_type: type[T]) -> list[str]:
|
|
590
|
+
# get parameters of the constructor
|
|
591
|
+
obj_cstr_sig = signature(obj_type.__init__)
|
|
592
|
+
# remove first argument (self)
|
|
593
|
+
parameters_ids = list(obj_cstr_sig.parameters.keys())[1:]
|
|
594
|
+
return parameters_ids
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def __sort_configuration(configuration: Mapping[str, Any]) -> Any:
|
|
598
|
+
# Sort dict entries based on on their dependencies to initialize most
|
|
599
|
+
# required arguments first
|
|
600
|
+
dependencies_score = __build_dependencies_sorting_score(configuration)
|
|
601
|
+
sorted_values = dict(
|
|
602
|
+
sorted(
|
|
603
|
+
configuration.items(),
|
|
604
|
+
key=lambda item: dependencies_score[item[0]],
|
|
605
|
+
)
|
|
606
|
+
)
|
|
607
|
+
if isinstance(configuration, Configuration):
|
|
608
|
+
return Configuration(configuration.label, sorted_values)
|
|
609
|
+
elif isinstance(configuration, Mapping):
|
|
610
|
+
return sorted_values
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
def __build_dependencies_sorting_score(
|
|
614
|
+
configuration: Mapping[str, Any],
|
|
615
|
+
) -> dict[str, int]:
|
|
616
|
+
dependencies = __build_dependencies(configuration)
|
|
617
|
+
for key, item in dependencies.items():
|
|
618
|
+
__check_dependencies(key, item, dependencies)
|
|
619
|
+
return {key: __get_score(key, dependencies) for key in configuration}
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def __check_dependencies(
|
|
623
|
+
key: str,
|
|
624
|
+
dependencies: set[str],
|
|
625
|
+
all_dependencies: Mapping[str, set[str]],
|
|
626
|
+
dependency_chain: list[str] | None = None,
|
|
627
|
+
) -> None:
|
|
628
|
+
dependency_chain_copy = (
|
|
629
|
+
dependency_chain.copy() if dependency_chain is not None else []
|
|
630
|
+
)
|
|
631
|
+
if key in dependency_chain_copy:
|
|
632
|
+
raise ConfigurationError(
|
|
633
|
+
str.format("Item {0} is referencing itself: {1}", key, dependency_chain)
|
|
634
|
+
)
|
|
635
|
+
dependency_chain_copy.append(key)
|
|
636
|
+
for dependency_item in dependencies:
|
|
637
|
+
__check_dependencies(
|
|
638
|
+
dependency_item,
|
|
639
|
+
all_dependencies[dependency_item],
|
|
640
|
+
all_dependencies,
|
|
641
|
+
dependency_chain_copy,
|
|
642
|
+
)
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
def __build_dependencies(configuration: Mapping[str, Any]) -> Mapping[str, set[str]]:
|
|
646
|
+
dependencies: dict[str, set[str]] = {}
|
|
647
|
+
for key, item in configuration.items():
|
|
648
|
+
all_values = __get_all_strings(item)
|
|
649
|
+
dependencies[key] = set.intersection(all_values, configuration.keys())
|
|
650
|
+
for new_key, new_item in configuration.items():
|
|
651
|
+
recursive_keys = __get_recursives_keys(new_item)
|
|
652
|
+
if new_key != key and any(
|
|
653
|
+
[
|
|
654
|
+
value in recursive_keys
|
|
655
|
+
for value in all_values
|
|
656
|
+
if value not in dependencies[key]
|
|
657
|
+
]
|
|
658
|
+
):
|
|
659
|
+
dependencies[key].add(new_key)
|
|
660
|
+
|
|
661
|
+
return dependencies
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
def __get_score(item_key: str, dependencies: Mapping[str, set[str]]) -> int:
|
|
665
|
+
score = 1
|
|
666
|
+
for dependencies_key in dependencies[item_key]:
|
|
667
|
+
score += __get_score(dependencies_key, dependencies)
|
|
668
|
+
return score
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
def __get_recursives_keys(entry: Any) -> set[str]:
|
|
672
|
+
result: set[str] = set()
|
|
673
|
+
if isinstance(entry, str):
|
|
674
|
+
return result
|
|
675
|
+
elif isinstance(entry, Mapping):
|
|
676
|
+
result = result.union(entry.keys())
|
|
677
|
+
result = result.union(__get_recursives_keys(entry.values()))
|
|
678
|
+
elif isinstance(entry, Iterable):
|
|
679
|
+
for value in entry:
|
|
680
|
+
result = result.union(__get_recursives_keys(value))
|
|
681
|
+
return result
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
def __get_all_strings(entry: Any) -> set[str]:
|
|
685
|
+
result = set()
|
|
686
|
+
if isinstance(entry, str):
|
|
687
|
+
result.add(entry)
|
|
688
|
+
elif isinstance(entry, Mapping):
|
|
689
|
+
result = result.union(__get_all_strings(entry.values()))
|
|
690
|
+
elif isinstance(entry, Iterable):
|
|
691
|
+
for sub_entry in entry:
|
|
692
|
+
entry_set = __get_all_strings(sub_entry)
|
|
693
|
+
result = result.union(entry_set)
|
|
694
|
+
|
|
695
|
+
return result
|