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,354 @@
|
|
|
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
|
+
"""
|
|
28
|
+
Defines a Time object, and a manager to hold the simulation time.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from typing import Any
|
|
32
|
+
|
|
33
|
+
import numpy as np
|
|
34
|
+
|
|
35
|
+
from physioblocks.computing.quantities import Quantity
|
|
36
|
+
from physioblocks.registers.type_register import register_type
|
|
37
|
+
|
|
38
|
+
# Constant for the time quantity id in the simulation
|
|
39
|
+
TIME_QUANTITY_ID = "time"
|
|
40
|
+
|
|
41
|
+
_DEFAULT_STEP = 0.001
|
|
42
|
+
_DEFAULT_MIN_STEP = _DEFAULT_STEP / 16.0
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Time(Quantity[np.float64]):
|
|
46
|
+
"""
|
|
47
|
+
Extend :class:`~physioblocks.computing.quantities.Quantity` class to define
|
|
48
|
+
simulation time.
|
|
49
|
+
|
|
50
|
+
Whenever the time is updated, it recomputes the difference between the new
|
|
51
|
+
and the current value and its inverse (accessibles trough the ``dt`` and ``inv_dt``
|
|
52
|
+
properties)
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
_dt: float
|
|
56
|
+
"""Difference between ``new`` and ``current`` values"""
|
|
57
|
+
|
|
58
|
+
_inv_dt: float
|
|
59
|
+
"""Inverse of ``dt``"""
|
|
60
|
+
|
|
61
|
+
def __init__(self, value: float):
|
|
62
|
+
"""
|
|
63
|
+
Initialize ``dt`` and ``inv_dt`` to 0.
|
|
64
|
+
|
|
65
|
+
:param value: the initial time value
|
|
66
|
+
:type value: float
|
|
67
|
+
"""
|
|
68
|
+
super().__init__(value)
|
|
69
|
+
self._dt = 0.0
|
|
70
|
+
self._inv_dt = 0.0
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def dt(self) -> float:
|
|
74
|
+
"""
|
|
75
|
+
Get the difference between the ``new`` and the ``current`` value of the time.
|
|
76
|
+
|
|
77
|
+
:return: the delta time value
|
|
78
|
+
:rtype: float
|
|
79
|
+
"""
|
|
80
|
+
return self._dt
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def inv_dt(self) -> float:
|
|
84
|
+
"""
|
|
85
|
+
Get the inverse of ``dt``.
|
|
86
|
+
|
|
87
|
+
:return: the delta time inverse
|
|
88
|
+
:rtype: float
|
|
89
|
+
"""
|
|
90
|
+
return self._inv_dt
|
|
91
|
+
|
|
92
|
+
def update(self, new: Any) -> None:
|
|
93
|
+
"""
|
|
94
|
+
Update the ``new`` value of the time and recomputes ``dt`` and ``inv_dt``.
|
|
95
|
+
|
|
96
|
+
.. note::
|
|
97
|
+
|
|
98
|
+
If ``dt`` is 0.0, ``inv_dt`` is set to 0.0.
|
|
99
|
+
|
|
100
|
+
:param new: the new value to set
|
|
101
|
+
:type new: float
|
|
102
|
+
"""
|
|
103
|
+
super().update(new)
|
|
104
|
+
|
|
105
|
+
self._dt = self._new - self._current
|
|
106
|
+
if self._dt != 0.0:
|
|
107
|
+
self._inv_dt = 1.0 / self._dt
|
|
108
|
+
else:
|
|
109
|
+
self._inv_dt = 0.0
|
|
110
|
+
|
|
111
|
+
def initialize(self, value: Any) -> None:
|
|
112
|
+
"""
|
|
113
|
+
Initialize the ``new`` and ``current`` value.
|
|
114
|
+
|
|
115
|
+
.. note::
|
|
116
|
+
|
|
117
|
+
It set both ``dt`` and ``inv_dt`` to 0.
|
|
118
|
+
|
|
119
|
+
:param value: the value to set
|
|
120
|
+
:type new: Any
|
|
121
|
+
"""
|
|
122
|
+
super().initialize(value)
|
|
123
|
+
self._dt = 0.0
|
|
124
|
+
self._inv_dt = 0.0
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# Constant for time manager type
|
|
128
|
+
TIME_MANAGER_ID = "time"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@register_type(TIME_MANAGER_ID)
|
|
132
|
+
class TimeManager:
|
|
133
|
+
"""
|
|
134
|
+
Updates the time value during the simulation.
|
|
135
|
+
|
|
136
|
+
:param start: start value for the simulation time.
|
|
137
|
+
:type start: float
|
|
138
|
+
|
|
139
|
+
:param end: end value for the simulation time.
|
|
140
|
+
The time ``new`` and ``current`` value can not exceed it.
|
|
141
|
+
:type end: float
|
|
142
|
+
|
|
143
|
+
:param time_step: time increment when update time is called.
|
|
144
|
+
:type time_step: float
|
|
145
|
+
|
|
146
|
+
:param min_step: minimum allowed value of the time increment.
|
|
147
|
+
:type time_step: float
|
|
148
|
+
|
|
149
|
+
:raise ValueError: ValueError is raised when :
|
|
150
|
+
* the start parameter is superior to the end parameter.
|
|
151
|
+
* the time_step parameter is negative.
|
|
152
|
+
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
_time: Time
|
|
156
|
+
"""Store the current time value"""
|
|
157
|
+
|
|
158
|
+
start: float
|
|
159
|
+
"""The starting time of the time manager."""
|
|
160
|
+
|
|
161
|
+
def __init__(
|
|
162
|
+
self,
|
|
163
|
+
start: float = 0.0,
|
|
164
|
+
duration: float = _DEFAULT_STEP,
|
|
165
|
+
step_size: float = _DEFAULT_STEP,
|
|
166
|
+
min_step: float = _DEFAULT_MIN_STEP,
|
|
167
|
+
):
|
|
168
|
+
if min_step <= 0.0:
|
|
169
|
+
raise ValueError(
|
|
170
|
+
str.format(
|
|
171
|
+
"Time Manager minimum time step value can not be 0 or negative",
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
self._min_step = min_step
|
|
175
|
+
self._time = Time(start)
|
|
176
|
+
self.start = start
|
|
177
|
+
self.duration = duration
|
|
178
|
+
self.step_size = step_size
|
|
179
|
+
self.current_step_size = step_size
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def min_step(self) -> float:
|
|
183
|
+
"""
|
|
184
|
+
Get the minimum time step size allowed.
|
|
185
|
+
|
|
186
|
+
:return: the minimum time step size.
|
|
187
|
+
:rtype: float
|
|
188
|
+
"""
|
|
189
|
+
return self._min_step
|
|
190
|
+
|
|
191
|
+
@min_step.setter
|
|
192
|
+
def min_step(self, value: float) -> None:
|
|
193
|
+
"""
|
|
194
|
+
Set the start time.
|
|
195
|
+
|
|
196
|
+
:param value: the start time to set
|
|
197
|
+
:type value: float
|
|
198
|
+
|
|
199
|
+
:raise ValueError: Raises a ValueError if the given value is greater than the
|
|
200
|
+
end time.
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
if value <= 0.0:
|
|
204
|
+
raise ValueError(
|
|
205
|
+
str.format(
|
|
206
|
+
"Time Manager minimum time step value can not be 0 or negative",
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
elif value > self.step_size:
|
|
210
|
+
raise ValueError(
|
|
211
|
+
str.format(
|
|
212
|
+
"Time Manager minimum step value can not be superior to time_step",
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
self._min_step = value
|
|
216
|
+
|
|
217
|
+
@property
|
|
218
|
+
def end(self) -> float:
|
|
219
|
+
"""
|
|
220
|
+
Get the current end time.
|
|
221
|
+
|
|
222
|
+
:return: the end time
|
|
223
|
+
:rtype: float
|
|
224
|
+
"""
|
|
225
|
+
return self.start + self._duration
|
|
226
|
+
|
|
227
|
+
@property
|
|
228
|
+
def duration(self) -> float:
|
|
229
|
+
"""
|
|
230
|
+
Get the duration.
|
|
231
|
+
|
|
232
|
+
:return: the duration
|
|
233
|
+
:rtype: float
|
|
234
|
+
"""
|
|
235
|
+
return self._duration
|
|
236
|
+
|
|
237
|
+
@duration.setter
|
|
238
|
+
def duration(self, value: float) -> None:
|
|
239
|
+
"""
|
|
240
|
+
Set the duration
|
|
241
|
+
|
|
242
|
+
:return: the duration
|
|
243
|
+
:rtype: float
|
|
244
|
+
"""
|
|
245
|
+
if value <= 0.0:
|
|
246
|
+
raise ValueError(
|
|
247
|
+
str.format(
|
|
248
|
+
"Time Manager duration value can not be 0 or negative",
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
self._duration = value
|
|
252
|
+
|
|
253
|
+
@property
|
|
254
|
+
def step_size(self) -> float:
|
|
255
|
+
"""
|
|
256
|
+
Get the standard step size
|
|
257
|
+
|
|
258
|
+
:return: the standard step size
|
|
259
|
+
:rtype: float
|
|
260
|
+
"""
|
|
261
|
+
return self._max_step
|
|
262
|
+
|
|
263
|
+
@step_size.setter
|
|
264
|
+
def step_size(self, value: float) -> None:
|
|
265
|
+
"""
|
|
266
|
+
Get the standard step size
|
|
267
|
+
|
|
268
|
+
:return: the standard step size
|
|
269
|
+
:rtype: float
|
|
270
|
+
"""
|
|
271
|
+
if value <= 0.0:
|
|
272
|
+
raise ValueError(
|
|
273
|
+
str.format(
|
|
274
|
+
"Time Manager time step value can not be 0 or negative",
|
|
275
|
+
)
|
|
276
|
+
)
|
|
277
|
+
self._max_step = value
|
|
278
|
+
self.current_step_size = value
|
|
279
|
+
|
|
280
|
+
@property
|
|
281
|
+
def current_step_size(self) -> float:
|
|
282
|
+
"""
|
|
283
|
+
Get the current time step size.
|
|
284
|
+
|
|
285
|
+
:return: the time step
|
|
286
|
+
:rtype: float
|
|
287
|
+
"""
|
|
288
|
+
return self._time_step
|
|
289
|
+
|
|
290
|
+
@current_step_size.setter
|
|
291
|
+
def current_step_size(self, value: float) -> None:
|
|
292
|
+
"""
|
|
293
|
+
Set the current time step size.
|
|
294
|
+
|
|
295
|
+
:param value: the time step to set
|
|
296
|
+
:type value: float
|
|
297
|
+
|
|
298
|
+
:raise ValueError: Raises a ValueError if the given value is
|
|
299
|
+
less than min_step or greater than max_step.
|
|
300
|
+
"""
|
|
301
|
+
|
|
302
|
+
if value < self.min_step:
|
|
303
|
+
raise ValueError(
|
|
304
|
+
"Time Manager time step value can not be inferior to minimum step",
|
|
305
|
+
)
|
|
306
|
+
elif value > self._max_step:
|
|
307
|
+
raise ValueError(
|
|
308
|
+
"Time Manager time step value can not be superior to default step",
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
self._time_step = value
|
|
312
|
+
|
|
313
|
+
@property
|
|
314
|
+
def time(self) -> Time:
|
|
315
|
+
"""
|
|
316
|
+
Get the current :class:`~.Time` quantity
|
|
317
|
+
|
|
318
|
+
:return: the time quantity
|
|
319
|
+
:rtype: Time
|
|
320
|
+
"""
|
|
321
|
+
return self._time
|
|
322
|
+
|
|
323
|
+
@property
|
|
324
|
+
def ended(self) -> bool:
|
|
325
|
+
"""
|
|
326
|
+
Get if the time has reached the end time.
|
|
327
|
+
|
|
328
|
+
:return: True if the new time value has reached the end time, False otherwise.
|
|
329
|
+
:rtype: bool
|
|
330
|
+
"""
|
|
331
|
+
return bool(self._time.new >= self.end)
|
|
332
|
+
|
|
333
|
+
def update_time(self) -> None:
|
|
334
|
+
"""
|
|
335
|
+
Set the ``current`` time value to the ``new`` time value and update the ``new``
|
|
336
|
+
time value with the defined time step increment.
|
|
337
|
+
|
|
338
|
+
If the ``end`` value is reached, the ``new`` time value is set
|
|
339
|
+
to the ``end`` value.
|
|
340
|
+
"""
|
|
341
|
+
updated_time = self._time.current + self._time_step
|
|
342
|
+
if updated_time < self.end:
|
|
343
|
+
self._time.initialize(self._time.new)
|
|
344
|
+
self._time.update(self._time.current + self._time_step)
|
|
345
|
+
else:
|
|
346
|
+
# time manager reached end time
|
|
347
|
+
self._time.initialize(self._time.new)
|
|
348
|
+
self._time.update(self.end)
|
|
349
|
+
|
|
350
|
+
def initialize(self) -> None:
|
|
351
|
+
"""
|
|
352
|
+
Initialize the time with the ``start`` value.
|
|
353
|
+
"""
|
|
354
|
+
self._time.initialize(self.start)
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
"""Defines utils modules for Physioblocks"""
|
|
@@ -0,0 +1,150 @@
|
|
|
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
|
+
"""
|
|
28
|
+
Defines methods to dynamically import modules while in a script or python
|
|
29
|
+
application.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
import importlib
|
|
33
|
+
import logging
|
|
34
|
+
import pkgutil
|
|
35
|
+
import sys
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
|
|
38
|
+
from physioblocks.utils.exceptions_utils import log_exception
|
|
39
|
+
|
|
40
|
+
_logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def import_libraries(libraries_folder_paths: list[Path]) -> None:
|
|
44
|
+
"""
|
|
45
|
+
Dynamically import all the modules at the given paths.
|
|
46
|
+
|
|
47
|
+
.. note:: The libraries folders must be in a site to be able to
|
|
48
|
+
load theirs modules.
|
|
49
|
+
|
|
50
|
+
:param libraries_folder_path: the paths
|
|
51
|
+
:type libraries_folder_path: list[Path]
|
|
52
|
+
|
|
53
|
+
Example
|
|
54
|
+
^^^^^^^
|
|
55
|
+
|
|
56
|
+
.. code:: python
|
|
57
|
+
|
|
58
|
+
# Add site for the library to load
|
|
59
|
+
site.addsitedir(ABSOLUTE_PATH_TO_LIBRARY)
|
|
60
|
+
|
|
61
|
+
lib_path = Path(ABSOLUTE_PATH_TO_LIBRARY)
|
|
62
|
+
import_libraries([lib_path]) # dynamically import the library
|
|
63
|
+
|
|
64
|
+
"""
|
|
65
|
+
packages_absolute_paths: list[tuple[Path, str]] = []
|
|
66
|
+
|
|
67
|
+
for library_path in libraries_folder_paths:
|
|
68
|
+
if library_path.exists() is True:
|
|
69
|
+
absolute_path = library_path.absolute()
|
|
70
|
+
full_package_name = _get_full_package_name(absolute_path)
|
|
71
|
+
packages_absolute_paths.append((absolute_path, full_package_name))
|
|
72
|
+
else:
|
|
73
|
+
_logger.error(
|
|
74
|
+
str.format(
|
|
75
|
+
"There is no library folder at path {0}. Path skipped.",
|
|
76
|
+
str(library_path),
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
for package_path, full_package_name in packages_absolute_paths:
|
|
81
|
+
_import_modules_recursivly_at_path(package_path, full_package_name)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _get_full_package_name(package_path: Path) -> str:
|
|
85
|
+
if _is_package(package_path) is False:
|
|
86
|
+
raise ImportError(str.format("{0} is not a package", package_path.name))
|
|
87
|
+
|
|
88
|
+
full_package_name = package_path.name
|
|
89
|
+
parent = package_path.parent
|
|
90
|
+
check_parent = True
|
|
91
|
+
while check_parent is True:
|
|
92
|
+
check_parent = False
|
|
93
|
+
if _is_package(parent) is True:
|
|
94
|
+
full_package_name = ".".join([parent.name, full_package_name])
|
|
95
|
+
parent = parent.parent
|
|
96
|
+
check_parent = True
|
|
97
|
+
|
|
98
|
+
return full_package_name
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _is_package(dir_path: Path) -> bool:
|
|
102
|
+
return (
|
|
103
|
+
any(
|
|
104
|
+
path.is_file() and path.name == "__init__.py" for path in dir_path.iterdir()
|
|
105
|
+
)
|
|
106
|
+
is True
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _import_modules_recursivly_at_path(
|
|
111
|
+
package_path: Path, full_package_name: str
|
|
112
|
+
) -> None:
|
|
113
|
+
for module_info in pkgutil.walk_packages([str(package_path)]):
|
|
114
|
+
full_module_name = module_info.name
|
|
115
|
+
if full_package_name is not None:
|
|
116
|
+
full_module_name = ".".join([full_package_name, module_info.name])
|
|
117
|
+
if full_module_name in sys.modules:
|
|
118
|
+
# already loaded module
|
|
119
|
+
_logger.warning(
|
|
120
|
+
str.format(
|
|
121
|
+
"Module {0} at {1} already loaded. Module skipped ",
|
|
122
|
+
full_module_name,
|
|
123
|
+
str(package_path),
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
else:
|
|
127
|
+
try:
|
|
128
|
+
importlib.import_module(full_module_name)
|
|
129
|
+
except ImportError as import_exception:
|
|
130
|
+
# Error while loading the module, log and skip the module
|
|
131
|
+
log_exception(
|
|
132
|
+
_logger,
|
|
133
|
+
ImportError,
|
|
134
|
+
import_exception,
|
|
135
|
+
import_exception.__traceback__,
|
|
136
|
+
logging.WARNING,
|
|
137
|
+
)
|
|
138
|
+
_logger.warning(
|
|
139
|
+
str.format(
|
|
140
|
+
"Import Error while loading {0} from {1}. Module skipped ",
|
|
141
|
+
full_module_name,
|
|
142
|
+
str(package_path),
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
if module_info.ispkg is True:
|
|
147
|
+
# recursivle load packa submodules
|
|
148
|
+
_import_modules_recursivly_at_path(
|
|
149
|
+
package_path / module_info.name, full_module_name
|
|
150
|
+
)
|
|
@@ -0,0 +1,115 @@
|
|
|
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 a functions to log exceptions and to caught unhandled exceptions"""
|
|
28
|
+
|
|
29
|
+
import logging
|
|
30
|
+
import traceback
|
|
31
|
+
from collections.abc import Callable
|
|
32
|
+
from logging import Logger
|
|
33
|
+
from types import TracebackType
|
|
34
|
+
from typing import TypeAlias
|
|
35
|
+
|
|
36
|
+
ExceptionHandlerFunction: TypeAlias = Callable[
|
|
37
|
+
[type[BaseException], BaseException, TracebackType | None], None
|
|
38
|
+
]
|
|
39
|
+
"""Type alias for exception handler signature"""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def create_uncaught_exception_logger_handler(
|
|
43
|
+
logger: Logger,
|
|
44
|
+
) -> ExceptionHandlerFunction:
|
|
45
|
+
"""create_uncaught_exception_logger_handler(logger: Logger) -> ExceptionHandlerFunction
|
|
46
|
+
|
|
47
|
+
Create an handler that log an uncaught exception.
|
|
48
|
+
|
|
49
|
+
:param logger: the logger to use to log the exception
|
|
50
|
+
:type logger: Logger
|
|
51
|
+
|
|
52
|
+
:return: the exception handler
|
|
53
|
+
:rtype: Callable
|
|
54
|
+
|
|
55
|
+
Example
|
|
56
|
+
^^^^^^^
|
|
57
|
+
|
|
58
|
+
.. code:: python
|
|
59
|
+
|
|
60
|
+
# get a logger
|
|
61
|
+
SIMULATION_LOG_FORMATER = logging.Formatter(logging.BASIC_FORMAT)
|
|
62
|
+
_root_logger = logging.getLogger()
|
|
63
|
+
_root_logger.setLevel(logging.DEBUG)
|
|
64
|
+
|
|
65
|
+
# register a hook to log uncaught exceptions to the logger
|
|
66
|
+
sys.excepthook = create_uncaught_exception_logger_handler(_root_logger)
|
|
67
|
+
|
|
68
|
+
""" # noqa: E501
|
|
69
|
+
|
|
70
|
+
def log_handler(
|
|
71
|
+
exception_type: type[BaseException],
|
|
72
|
+
exception_value: BaseException,
|
|
73
|
+
tb: TracebackType | None = None,
|
|
74
|
+
) -> None:
|
|
75
|
+
log_exception(logger, exception_type, exception_value, tb, logger.level)
|
|
76
|
+
|
|
77
|
+
return log_handler
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def log_exception(
|
|
81
|
+
logger: Logger,
|
|
82
|
+
exc_type: type[BaseException],
|
|
83
|
+
exception: BaseException,
|
|
84
|
+
tb: TracebackType | None,
|
|
85
|
+
loglevel: int = logging.ERROR,
|
|
86
|
+
) -> None:
|
|
87
|
+
"""
|
|
88
|
+
Log the provided exception.
|
|
89
|
+
|
|
90
|
+
.. note:: Type and message of the exception are logged with the provided
|
|
91
|
+
``loglevel``, while the traceback informations are logged as ``DEBUG``.
|
|
92
|
+
|
|
93
|
+
:param logger: the logger used to log
|
|
94
|
+
:type logger: Logger
|
|
95
|
+
|
|
96
|
+
:param exc_type: the exception type
|
|
97
|
+
:type exception: type[BaseException]
|
|
98
|
+
|
|
99
|
+
:param exception: the exception to log
|
|
100
|
+
:type exception: Exception
|
|
101
|
+
|
|
102
|
+
:param tb: the traceback
|
|
103
|
+
:type exception: TracebackType
|
|
104
|
+
|
|
105
|
+
:param loglevel: The loglevel, default is error
|
|
106
|
+
:type loglevel: int
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
logger.log(loglevel, str.format("{0}: {1}", exc_type.__name__, exception))
|
|
110
|
+
|
|
111
|
+
if tb is not None:
|
|
112
|
+
exception_tb = str.join(
|
|
113
|
+
"", traceback.format_exception(exc_type, value=exception, tb=tb)
|
|
114
|
+
)
|
|
115
|
+
logger.debug(exception_tb)
|