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,811 @@
|
|
|
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
|
+
Declares the **ModelComponents** and the **Block** base classes along with
|
|
29
|
+
objects to hold the **Flux**, **Internal Expressions** and **Saved Quantities**
|
|
30
|
+
functions.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
from collections.abc import Callable
|
|
36
|
+
from dataclasses import dataclass, field
|
|
37
|
+
from inspect import get_annotations
|
|
38
|
+
from typing import Any, TypeAlias, get_origin
|
|
39
|
+
|
|
40
|
+
import numpy as np
|
|
41
|
+
from numpy.typing import NDArray
|
|
42
|
+
|
|
43
|
+
from physioblocks.computing.quantities import Quantity
|
|
44
|
+
|
|
45
|
+
SystemFunction: TypeAlias = Callable[..., np.float64 | NDArray[np.float64]]
|
|
46
|
+
"""Type alias for functions composing the system"""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True)
|
|
50
|
+
class Expression:
|
|
51
|
+
"""Expression(size:int, expr_func: SystemFunction, expr_gradients: Mapping[str, SystemFunction] = {})
|
|
52
|
+
Store function computing numerical values for terms in the models with the function
|
|
53
|
+
result size.
|
|
54
|
+
|
|
55
|
+
Optionally, it can define a set of function to compute the partial derivatives
|
|
56
|
+
for a set of variables.
|
|
57
|
+
|
|
58
|
+
Example
|
|
59
|
+
^^^^^^^
|
|
60
|
+
|
|
61
|
+
.. code:: python
|
|
62
|
+
|
|
63
|
+
def f1(x1, x2):
|
|
64
|
+
return 0.5 * x1 + 0.8 * x2
|
|
65
|
+
|
|
66
|
+
def df1_dx1(x1, x2):
|
|
67
|
+
return 0.5
|
|
68
|
+
|
|
69
|
+
def df1_dx2(x1, x2):
|
|
70
|
+
return 0.8
|
|
71
|
+
|
|
72
|
+
expression_f1 = Expression(
|
|
73
|
+
1, # size
|
|
74
|
+
f1, # expression function
|
|
75
|
+
{
|
|
76
|
+
"x1": df1_dx1,
|
|
77
|
+
"x2": df1_dx2,
|
|
78
|
+
} # expressions partial derivatives
|
|
79
|
+
)
|
|
80
|
+
""" # noqa: E501
|
|
81
|
+
|
|
82
|
+
size: int
|
|
83
|
+
"""Size of the result of the function"""
|
|
84
|
+
|
|
85
|
+
expr_func: SystemFunction
|
|
86
|
+
"""Function to compute the numerical value"""
|
|
87
|
+
|
|
88
|
+
expr_gradients: dict[str, SystemFunction] = field(default_factory=dict)
|
|
89
|
+
"""
|
|
90
|
+
Collection of functions to compute the derivatives of the expression for
|
|
91
|
+
variables
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
def __eq__(self, value: Any) -> bool:
|
|
95
|
+
return bool(
|
|
96
|
+
isinstance(value, Expression)
|
|
97
|
+
and (
|
|
98
|
+
self.size == value.size
|
|
99
|
+
and self.expr_func == value.expr_func
|
|
100
|
+
and self.expr_gradients == value.expr_gradients
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dataclass(frozen=True)
|
|
106
|
+
class TermDefinition:
|
|
107
|
+
"""Describe Terms defined in an :class:`~physioblocks.computing.models.Expression`.
|
|
108
|
+
|
|
109
|
+
An :class:`~physioblocks.computing.models.Expression` object can define several
|
|
110
|
+
**Terms**.
|
|
111
|
+
|
|
112
|
+
Example
|
|
113
|
+
^^^^^^^
|
|
114
|
+
|
|
115
|
+
.. code:: python
|
|
116
|
+
|
|
117
|
+
def vector_3d(x1, x2, x3):
|
|
118
|
+
return [x1, x2, x3]
|
|
119
|
+
|
|
120
|
+
expression_vector = Expression(
|
|
121
|
+
3, # size
|
|
122
|
+
vector_3d # expression function
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
x1_term = Term(
|
|
126
|
+
"x1", # id
|
|
127
|
+
1, # term size
|
|
128
|
+
0 # starting index in vector expression
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
x2_term = Term(
|
|
132
|
+
"x2", # id
|
|
133
|
+
1, # term size
|
|
134
|
+
1 # starting index in vector expression
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
x3_term = Term(
|
|
138
|
+
"x3", # id
|
|
139
|
+
1, # term size
|
|
140
|
+
2 # starting index in vector expression
|
|
141
|
+
)
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
term_id: str
|
|
145
|
+
"""Term id"""
|
|
146
|
+
|
|
147
|
+
size: int
|
|
148
|
+
"""Term size"""
|
|
149
|
+
|
|
150
|
+
index: int = 0
|
|
151
|
+
"""Starting line of the term index in its expression"""
|
|
152
|
+
|
|
153
|
+
def __eq__(self, value: Any) -> bool:
|
|
154
|
+
return isinstance(value, TermDefinition) and self.term_id == value.term_id
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@dataclass(frozen=True)
|
|
158
|
+
class ExpressionDefinition:
|
|
159
|
+
"""ExpressionDefinition(expression: Expression, terms: list[TermDefinition] = [])
|
|
160
|
+
|
|
161
|
+
Holds an :class:`~physioblocks.computing.models.Expression` and
|
|
162
|
+
the :class:`~physioblocks.computing.models.TermDefinition` couple.
|
|
163
|
+
|
|
164
|
+
Example
|
|
165
|
+
^^^^^^^
|
|
166
|
+
|
|
167
|
+
.. code:: python
|
|
168
|
+
|
|
169
|
+
# Expression Definition for the example above:
|
|
170
|
+
>>> definition = ExpressionDefinition(
|
|
171
|
+
expression_vector,
|
|
172
|
+
[
|
|
173
|
+
x1_term,
|
|
174
|
+
x2_term,
|
|
175
|
+
x3_term
|
|
176
|
+
]
|
|
177
|
+
)
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
expression: Expression
|
|
181
|
+
"""The expression"""
|
|
182
|
+
|
|
183
|
+
terms: list[TermDefinition] = field(default_factory=list)
|
|
184
|
+
"""The expressed Terms"""
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def valid(self) -> bool:
|
|
188
|
+
"""Check if the definition is complete, meaning a term is
|
|
189
|
+
associated with each line of the expression and terms do not
|
|
190
|
+
overlap.
|
|
191
|
+
|
|
192
|
+
:return: True if the definition is valid, False otherwise
|
|
193
|
+
:rtype: bool
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
.. code ::
|
|
197
|
+
|
|
198
|
+
# From example above
|
|
199
|
+
>>> definition = ExpressionDefinition(
|
|
200
|
+
expression_vector,
|
|
201
|
+
[
|
|
202
|
+
x1_term,
|
|
203
|
+
x2_term,
|
|
204
|
+
x3_term
|
|
205
|
+
]
|
|
206
|
+
)
|
|
207
|
+
>>> definition.valid # True
|
|
208
|
+
|
|
209
|
+
>>> overlapping_definition = ExpressionDefinition(
|
|
210
|
+
expression_vector,
|
|
211
|
+
[
|
|
212
|
+
x1_term,
|
|
213
|
+
x2_term,
|
|
214
|
+
x3_term,
|
|
215
|
+
x1_term # overlapping term on index 0
|
|
216
|
+
]
|
|
217
|
+
)
|
|
218
|
+
>>> overlapping_definition.valid # False
|
|
219
|
+
|
|
220
|
+
>>> incomplete_definition = ExpressionDefinition(
|
|
221
|
+
expression_vector,
|
|
222
|
+
[
|
|
223
|
+
x1_term,
|
|
224
|
+
# missing index 1 term
|
|
225
|
+
x3_term
|
|
226
|
+
]
|
|
227
|
+
)
|
|
228
|
+
>>> incomplete_definition.valid # False
|
|
229
|
+
|
|
230
|
+
"""
|
|
231
|
+
used_indexes = []
|
|
232
|
+
for term in self.terms:
|
|
233
|
+
for i in range(term.index, term.index + term.size):
|
|
234
|
+
if i in used_indexes:
|
|
235
|
+
return False
|
|
236
|
+
used_indexes.append(i)
|
|
237
|
+
|
|
238
|
+
return len(used_indexes) == self.expression.size and 0 in used_indexes
|
|
239
|
+
|
|
240
|
+
def get_term(self, index: int) -> TermDefinition:
|
|
241
|
+
"""Get term starting in expression at the given index
|
|
242
|
+
|
|
243
|
+
:param index: the first index of the term in the expression.
|
|
244
|
+
:type index: int
|
|
245
|
+
|
|
246
|
+
:return: the term definition
|
|
247
|
+
:rtype: TermDefinition
|
|
248
|
+
|
|
249
|
+
.. code ::
|
|
250
|
+
|
|
251
|
+
# From example above
|
|
252
|
+
>>> definition = ExpressionDefinition(
|
|
253
|
+
expression_vector,
|
|
254
|
+
[
|
|
255
|
+
x1_term,
|
|
256
|
+
x2_term,
|
|
257
|
+
x3_term
|
|
258
|
+
]
|
|
259
|
+
)
|
|
260
|
+
>>> definition.get_term(0) # x1_term
|
|
261
|
+
>>> definition.get_term(1) # x2_term
|
|
262
|
+
>>> definition.get_term(2) # x3_term
|
|
263
|
+
|
|
264
|
+
"""
|
|
265
|
+
for term in self.terms:
|
|
266
|
+
if term.index == index:
|
|
267
|
+
return term
|
|
268
|
+
|
|
269
|
+
raise KeyError(
|
|
270
|
+
str.format("No term starts at index {0} in expression, {1}", index, self)
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
ExpressionsCollection: TypeAlias = dict[str, list[ExpressionDefinition]]
|
|
275
|
+
"""
|
|
276
|
+
Type alias for a collection of expressions.
|
|
277
|
+
Keys are the expression types as strings.
|
|
278
|
+
Values are a tuple defining the actual expression and a list of Term Definitions it
|
|
279
|
+
expresses.
|
|
280
|
+
"""
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class ModelComponentMetaClass(type):
|
|
284
|
+
"""Meta-class for :class:`~physioblocks.computing.models.ModelComponent`.
|
|
285
|
+
|
|
286
|
+
Defines the model **Internal Equations** and **Saved Quantities**
|
|
287
|
+
using :class:`~physioblocks.computing.models.Expression` objects.
|
|
288
|
+
|
|
289
|
+
* **Internal Equations** are expressing **Internal Variables** with a residual
|
|
290
|
+
equation.
|
|
291
|
+
* **Saved Quantities** are given with a direct relation
|
|
292
|
+
|
|
293
|
+
"""
|
|
294
|
+
|
|
295
|
+
__INTERNAL_EXPRESSION_KEY = "internals"
|
|
296
|
+
__SAVED_QUANTITIES_EXPRESSION_KEY = "saved_quantities"
|
|
297
|
+
|
|
298
|
+
_expressions: ExpressionsCollection
|
|
299
|
+
|
|
300
|
+
def __init__(cls, *args: Any, **kwargs: Any) -> None:
|
|
301
|
+
super().__init__(*args, **kwargs)
|
|
302
|
+
cls._expressions = {
|
|
303
|
+
cls.__INTERNAL_EXPRESSION_KEY: [],
|
|
304
|
+
cls.__SAVED_QUANTITIES_EXPRESSION_KEY: [],
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
@staticmethod
|
|
308
|
+
def __is_quantity_type(type_to_test: Any) -> bool:
|
|
309
|
+
if isinstance(type_to_test, type) is False:
|
|
310
|
+
type_to_test = get_origin(type_to_test)
|
|
311
|
+
return issubclass(type_to_test, Quantity) is True
|
|
312
|
+
|
|
313
|
+
@property
|
|
314
|
+
def local_ids(cls) -> list[str]:
|
|
315
|
+
"""
|
|
316
|
+
Get local parameters ids of the model.
|
|
317
|
+
|
|
318
|
+
Every member of the :class:`~physioblocks.computing.models.ModelComponent`
|
|
319
|
+
annotated with :class:`~physioblocks.computing.quantities.Quantity` type
|
|
320
|
+
has a local id.
|
|
321
|
+
|
|
322
|
+
:return: the local ids of the parameters
|
|
323
|
+
:rtype: list[str]
|
|
324
|
+
|
|
325
|
+
Example
|
|
326
|
+
^^^^^^^
|
|
327
|
+
|
|
328
|
+
.. code:: python
|
|
329
|
+
|
|
330
|
+
@dataclass
|
|
331
|
+
class SimpleModel(metaclass=ModelComponentMetaClass):
|
|
332
|
+
|
|
333
|
+
x1: Quantity
|
|
334
|
+
x2: Quantity
|
|
335
|
+
|
|
336
|
+
SimpleModel.local_ids # ["x1", "x2"]
|
|
337
|
+
"""
|
|
338
|
+
annotations = get_annotations(cls)
|
|
339
|
+
|
|
340
|
+
# get the quantities local ids
|
|
341
|
+
local_ids = [
|
|
342
|
+
key
|
|
343
|
+
for key, item in annotations.items()
|
|
344
|
+
if ModelComponentMetaClass.__is_quantity_type(item)
|
|
345
|
+
]
|
|
346
|
+
|
|
347
|
+
# add the saved quantities local ids
|
|
348
|
+
local_ids.extend(
|
|
349
|
+
[
|
|
350
|
+
saved_quantity_expr.term_id
|
|
351
|
+
for saved_quantity_expr in cls.saved_quantities
|
|
352
|
+
]
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
return local_ids
|
|
356
|
+
|
|
357
|
+
def _get_all_terms(cls, expr_type: str) -> list[TermDefinition]:
|
|
358
|
+
if expr_type not in cls._expressions:
|
|
359
|
+
return []
|
|
360
|
+
# get terms local id and size for all expressions of the given type
|
|
361
|
+
return [
|
|
362
|
+
term_def
|
|
363
|
+
for expression_def in cls._expressions[expr_type]
|
|
364
|
+
for term_def in expression_def.terms
|
|
365
|
+
]
|
|
366
|
+
|
|
367
|
+
def _has_term_defined(cls, tested_id: str, expr_type: str) -> bool:
|
|
368
|
+
"""Get if the given id is defined as the given espresstion type
|
|
369
|
+
|
|
370
|
+
:param variable_id: the id to test
|
|
371
|
+
:type variable_id: str
|
|
372
|
+
|
|
373
|
+
:param expr_type: the expression type to test
|
|
374
|
+
:type expr_type: str
|
|
375
|
+
|
|
376
|
+
:return: True if the id defines an expresseion of the given expression type,
|
|
377
|
+
false otherwise
|
|
378
|
+
:rtype: bool
|
|
379
|
+
"""
|
|
380
|
+
return any(term.term_id == tested_id for term in cls._get_all_terms(expr_type))
|
|
381
|
+
|
|
382
|
+
def _get_all_expressions(cls, expr_type: str) -> list[ExpressionDefinition]:
|
|
383
|
+
# get all expressions of a type with the matching defined terms
|
|
384
|
+
if expr_type not in cls._expressions:
|
|
385
|
+
return []
|
|
386
|
+
|
|
387
|
+
return cls._expressions[expr_type].copy()
|
|
388
|
+
|
|
389
|
+
def _get_term_expression(
|
|
390
|
+
cls, term_id: str, expression_type: str
|
|
391
|
+
) -> tuple[Expression, int, int]:
|
|
392
|
+
"""Get the expression, the size and the line index in the expression
|
|
393
|
+
of the given given local term id.
|
|
394
|
+
|
|
395
|
+
:param term_id: the term id
|
|
396
|
+
:type term_id: str
|
|
397
|
+
|
|
398
|
+
:param expression_type: the type of expression of the term
|
|
399
|
+
:type term_id: str
|
|
400
|
+
|
|
401
|
+
:return: the expression, the size and line of the term in the expression.
|
|
402
|
+
:rtype: tuple[Expression, int, int]
|
|
403
|
+
"""
|
|
404
|
+
if expression_type not in cls._expressions:
|
|
405
|
+
raise KeyError(str.format("No expressions of type {0}.", expression_type))
|
|
406
|
+
|
|
407
|
+
for expr_def in cls._expressions[expression_type]:
|
|
408
|
+
for term_def in expr_def.terms:
|
|
409
|
+
if term_def.term_id == term_id:
|
|
410
|
+
return (expr_def.expression, term_def.size, term_def.index)
|
|
411
|
+
|
|
412
|
+
raise KeyError(str.format("No expression defined for {0}.", term_id))
|
|
413
|
+
|
|
414
|
+
def _get_all_terms_ids(cls) -> list[str]:
|
|
415
|
+
# get all expressed terms local ids
|
|
416
|
+
return [
|
|
417
|
+
term_def.term_id
|
|
418
|
+
for expr_type in cls._expressions
|
|
419
|
+
for expr_def in cls._expressions[expr_type]
|
|
420
|
+
for term_def in expr_def.terms
|
|
421
|
+
]
|
|
422
|
+
|
|
423
|
+
def _declares_term_expression(
|
|
424
|
+
cls,
|
|
425
|
+
term_id: str,
|
|
426
|
+
expr: Expression,
|
|
427
|
+
expr_type: str,
|
|
428
|
+
size: int | None = None,
|
|
429
|
+
index: int = 0,
|
|
430
|
+
) -> None:
|
|
431
|
+
"""
|
|
432
|
+
Add a term expression to the model definition.
|
|
433
|
+
|
|
434
|
+
:param term_id: the local id of the term.
|
|
435
|
+
:type term_id: str
|
|
436
|
+
|
|
437
|
+
:param expr: the associated expression
|
|
438
|
+
:type expr: Expression
|
|
439
|
+
|
|
440
|
+
:param expr_type: the expression type
|
|
441
|
+
:type expr_type: str
|
|
442
|
+
|
|
443
|
+
:param size: the term size
|
|
444
|
+
:type size: int
|
|
445
|
+
|
|
446
|
+
:param index: the starting line index of the term in the expression.
|
|
447
|
+
:type index: str
|
|
448
|
+
"""
|
|
449
|
+
if size is None:
|
|
450
|
+
size = expr.size
|
|
451
|
+
|
|
452
|
+
if index + size > expr.size:
|
|
453
|
+
raise ValueError(
|
|
454
|
+
str.format(
|
|
455
|
+
"{0} definition of size {1} starting at index {2} exceed "
|
|
456
|
+
"expression size {3}",
|
|
457
|
+
term_id,
|
|
458
|
+
size,
|
|
459
|
+
index,
|
|
460
|
+
expr.size,
|
|
461
|
+
)
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
# check if a term with the same id is already used
|
|
465
|
+
if term_id in cls._get_all_terms_ids():
|
|
466
|
+
raise KeyError(
|
|
467
|
+
str.format("An expression is already defined for {0}.", term_id)
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
# get existing expression definition
|
|
471
|
+
expression_def = None
|
|
472
|
+
for expr_def in cls._expressions[expr_type]:
|
|
473
|
+
if expr_def.expression is expr:
|
|
474
|
+
expression_def = expr_def
|
|
475
|
+
break
|
|
476
|
+
|
|
477
|
+
# if not found, create a new one
|
|
478
|
+
if expression_def is None:
|
|
479
|
+
expression_def = ExpressionDefinition(expr, [])
|
|
480
|
+
cls._expressions[expr_type].append(expression_def)
|
|
481
|
+
|
|
482
|
+
# Add the term definition to the expression definition
|
|
483
|
+
expression_def.terms.append(TermDefinition(term_id, size, index))
|
|
484
|
+
|
|
485
|
+
def declares_internal_expression(
|
|
486
|
+
cls,
|
|
487
|
+
variable_id: str,
|
|
488
|
+
expr: Expression,
|
|
489
|
+
size: int | None = None,
|
|
490
|
+
index: int = 0,
|
|
491
|
+
) -> None:
|
|
492
|
+
"""
|
|
493
|
+
Declares a :class:`~physioblocks.computing.models.Expression` object
|
|
494
|
+
for an **Internal Equation** of the model.
|
|
495
|
+
|
|
496
|
+
:param term_id: the local id of the variable associated with the expression
|
|
497
|
+
:type term_id: str
|
|
498
|
+
|
|
499
|
+
:param expr: the associated expression
|
|
500
|
+
:type expr: Expression
|
|
501
|
+
|
|
502
|
+
:param size: the term size
|
|
503
|
+
:type size: int
|
|
504
|
+
|
|
505
|
+
:param index: the starting line index of the term in the expression.
|
|
506
|
+
:type index: str
|
|
507
|
+
|
|
508
|
+
Example
|
|
509
|
+
^^^^^^^
|
|
510
|
+
|
|
511
|
+
.. code:: python
|
|
512
|
+
|
|
513
|
+
@dataclass
|
|
514
|
+
class SimpleModel(metaclass=ModelComponentMetaClass):
|
|
515
|
+
|
|
516
|
+
x1: Quantity
|
|
517
|
+
a: Quantity
|
|
518
|
+
b: Quantity
|
|
519
|
+
|
|
520
|
+
def x1_residual(self):
|
|
521
|
+
return self.a.current * self.x1.new - b.current
|
|
522
|
+
|
|
523
|
+
def dx1_residual_dx1(self):
|
|
524
|
+
return self.a
|
|
525
|
+
|
|
526
|
+
x1_expression = Expression(
|
|
527
|
+
1,
|
|
528
|
+
SimpleModel.x1_residual,
|
|
529
|
+
{
|
|
530
|
+
"x1": SimpleModel.dx1_residual_dx1
|
|
531
|
+
}
|
|
532
|
+
)
|
|
533
|
+
SimpleModel.declare_internal_expression(
|
|
534
|
+
"x1", # term id
|
|
535
|
+
x1_expression, # term expression
|
|
536
|
+
1, # term size
|
|
537
|
+
0 # Term index in the expression
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
"""
|
|
541
|
+
cls._declares_term_expression(
|
|
542
|
+
variable_id, expr, cls.__INTERNAL_EXPRESSION_KEY, size, index
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
@property
|
|
546
|
+
def internal_variables(cls) -> list[TermDefinition]:
|
|
547
|
+
"""Get the :class:`~physioblocks.computing.models.TermDefinition`
|
|
548
|
+
object describing **internal Variables**
|
|
549
|
+
|
|
550
|
+
:return: the internal variables term definitions
|
|
551
|
+
:rtype: list[TermDefinition]
|
|
552
|
+
"""
|
|
553
|
+
return cls._get_all_terms(cls.__INTERNAL_EXPRESSION_KEY)
|
|
554
|
+
|
|
555
|
+
@property
|
|
556
|
+
def internal_expressions(cls) -> list[ExpressionDefinition]:
|
|
557
|
+
"""Get the all :class:`~physioblocks.computing.models.Expression` object
|
|
558
|
+
describing **Internal Equations** of the model component.
|
|
559
|
+
|
|
560
|
+
:return: the internal equation expressions
|
|
561
|
+
:rtype: list[ExpressionDefinition]
|
|
562
|
+
"""
|
|
563
|
+
return cls._get_all_expressions(cls.__INTERNAL_EXPRESSION_KEY)
|
|
564
|
+
|
|
565
|
+
def get_internal_variable_expression(
|
|
566
|
+
cls, term_id: str
|
|
567
|
+
) -> tuple[Expression, int, int]:
|
|
568
|
+
"""Get the :class:`~physioblocks.computing.models.Expression` for the given
|
|
569
|
+
**Internal Variable** local name.
|
|
570
|
+
|
|
571
|
+
:param term_id: the term id
|
|
572
|
+
:type term_id: str
|
|
573
|
+
|
|
574
|
+
:return: the expression, its size and the starting index of the
|
|
575
|
+
term in the expression.
|
|
576
|
+
:rtype: tuple[Expression, int, int]
|
|
577
|
+
"""
|
|
578
|
+
return cls._get_term_expression(term_id, cls.__INTERNAL_EXPRESSION_KEY)
|
|
579
|
+
|
|
580
|
+
def has_internal_variable(cls, variable_id: str) -> bool:
|
|
581
|
+
"""Get if the given name match an **Internal Variable** of the
|
|
582
|
+
model component
|
|
583
|
+
|
|
584
|
+
:param variable_id: the id to test
|
|
585
|
+
:type variable_id: str
|
|
586
|
+
|
|
587
|
+
:return: True if the id defines an **Internal Variable**, False otherwise
|
|
588
|
+
:rtype: bool
|
|
589
|
+
"""
|
|
590
|
+
return cls._has_term_defined(variable_id, cls.__INTERNAL_EXPRESSION_KEY)
|
|
591
|
+
|
|
592
|
+
def declares_saved_quantity_expression(
|
|
593
|
+
cls, term_id: str, expr: Expression, size: int | None = None, index: int = 0
|
|
594
|
+
) -> None:
|
|
595
|
+
"""
|
|
596
|
+
Add a **Saved Quantity** :class:`~physioblocks.computing.models.Expression`
|
|
597
|
+
object to the model definition.
|
|
598
|
+
|
|
599
|
+
:param term_id: the local id of the term.
|
|
600
|
+
:type term_id: str
|
|
601
|
+
|
|
602
|
+
:param expr: the associated expression
|
|
603
|
+
:type expr: Expression
|
|
604
|
+
|
|
605
|
+
:param size: the term size
|
|
606
|
+
:type size: int
|
|
607
|
+
|
|
608
|
+
:param index: the starting line index of the term in the expression.
|
|
609
|
+
:type index: str
|
|
610
|
+
|
|
611
|
+
Example
|
|
612
|
+
^^^^^^^
|
|
613
|
+
|
|
614
|
+
.. code:: python
|
|
615
|
+
|
|
616
|
+
@dataclass
|
|
617
|
+
class SimpleModel(metaclass=ModelComponentMetaClass):
|
|
618
|
+
|
|
619
|
+
x1: Quantity
|
|
620
|
+
|
|
621
|
+
def x1_squared(self):
|
|
622
|
+
return x1.current * x1.current
|
|
623
|
+
|
|
624
|
+
x1_squared_expression = Expression(
|
|
625
|
+
1,
|
|
626
|
+
SimpleModel.x1_squared
|
|
627
|
+
)
|
|
628
|
+
SimpleModel.declares_saved_quantity_expression(
|
|
629
|
+
"x1_squared", # term id
|
|
630
|
+
x1_squared_expression, # term expression
|
|
631
|
+
1, # term size
|
|
632
|
+
0 # Term index in the expression
|
|
633
|
+
)
|
|
634
|
+
"""
|
|
635
|
+
cls._declares_term_expression(
|
|
636
|
+
term_id, expr, cls.__SAVED_QUANTITIES_EXPRESSION_KEY, size, index
|
|
637
|
+
)
|
|
638
|
+
|
|
639
|
+
@property
|
|
640
|
+
def saved_quantities(cls) -> list[TermDefinition]:
|
|
641
|
+
"""Get the saved quantities expressed by the model
|
|
642
|
+
|
|
643
|
+
:return: the saved quantities local id and size.
|
|
644
|
+
:rtype: list[tuple[str, int]]
|
|
645
|
+
"""
|
|
646
|
+
return cls._get_all_terms(cls.__SAVED_QUANTITIES_EXPRESSION_KEY)
|
|
647
|
+
|
|
648
|
+
def has_saved_quantity(cls, saved_quantity_id: str) -> bool:
|
|
649
|
+
"""Get if the given id is a saved quantity
|
|
650
|
+
|
|
651
|
+
:param saved_quantity_id: the id to test
|
|
652
|
+
:type saved_quantity_id: str
|
|
653
|
+
|
|
654
|
+
:return: True if the id defines a saved quantity, false otherwise
|
|
655
|
+
:rtype: bool
|
|
656
|
+
"""
|
|
657
|
+
return cls._has_term_defined(
|
|
658
|
+
saved_quantity_id, cls.__SAVED_QUANTITIES_EXPRESSION_KEY
|
|
659
|
+
)
|
|
660
|
+
|
|
661
|
+
@property
|
|
662
|
+
def saved_quantities_expressions(cls) -> list[ExpressionDefinition]:
|
|
663
|
+
"""Get the all saved quantities expressions
|
|
664
|
+
|
|
665
|
+
:return: the saved quantities expressions
|
|
666
|
+
:rtype: list[ExpressionDefinition]
|
|
667
|
+
"""
|
|
668
|
+
return cls._get_all_expressions(cls.__SAVED_QUANTITIES_EXPRESSION_KEY)
|
|
669
|
+
|
|
670
|
+
def get_saved_quantity_expression(cls, term_id: str) -> tuple[Expression, int, int]:
|
|
671
|
+
"""Get the expression for the given saved quantity local id.
|
|
672
|
+
|
|
673
|
+
:param term_id: the term id
|
|
674
|
+
:type term_id: str
|
|
675
|
+
|
|
676
|
+
:return: the expression, the starting index of the term in the expression
|
|
677
|
+
and its size.
|
|
678
|
+
:rtype: tuple[Expression, int, int]
|
|
679
|
+
"""
|
|
680
|
+
return cls._get_term_expression(term_id, cls.__SAVED_QUANTITIES_EXPRESSION_KEY)
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
class ModelComponent(metaclass=ModelComponentMetaClass):
|
|
684
|
+
"""
|
|
685
|
+
Holds parameters and define functions to compute
|
|
686
|
+
**Internal Equations** and **Saved Quantities**.
|
|
687
|
+
"""
|
|
688
|
+
|
|
689
|
+
def initialize(self) -> None:
|
|
690
|
+
"""Override this method to define specific for model initialization."""
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
class BlockMetaClass(ModelComponentMetaClass):
|
|
694
|
+
"""Meta-class for :class:`~physioblocks.computing.models.Block`.
|
|
695
|
+
|
|
696
|
+
Extends :class:`~physioblocks.computing.models.ModelComponentMetaClass` type adding
|
|
697
|
+
**Flux** :class:`~physioblocks.computing.models.Expression` to the model definition.
|
|
698
|
+
|
|
699
|
+
* Every **Flux** is expressed toward the outside of the **Block**.
|
|
700
|
+
* Every **Local Nodes** index of the **Block** defines one **Flux**.
|
|
701
|
+
|
|
702
|
+
.. note::
|
|
703
|
+
|
|
704
|
+
:class:`~physioblocks.computing.models.BlockMetaClass` can also define
|
|
705
|
+
**Internal Equations** and **Saved Quantities**
|
|
706
|
+
"""
|
|
707
|
+
|
|
708
|
+
_fluxes: dict[int, ExpressionDefinition]
|
|
709
|
+
"""Stores the flux expressions at each local nodes"""
|
|
710
|
+
|
|
711
|
+
def __init__(cls, *args: Any, **kwargs: Any) -> None:
|
|
712
|
+
super().__init__(*args, **kwargs)
|
|
713
|
+
cls._fluxes = {}
|
|
714
|
+
|
|
715
|
+
def declares_flux_expression(
|
|
716
|
+
cls, node_index: int, variable_id: str, expr: Expression
|
|
717
|
+
) -> None:
|
|
718
|
+
"""
|
|
719
|
+
Add a flux expression defining a block external relation.
|
|
720
|
+
|
|
721
|
+
:param node_index: the local node index where the flux is shared
|
|
722
|
+
:type node_index: int
|
|
723
|
+
|
|
724
|
+
:param variable_id: the local id of the variable associated to the node.
|
|
725
|
+
:type variable_id: str
|
|
726
|
+
|
|
727
|
+
:param expr: the associated expression
|
|
728
|
+
:type expr: Expression
|
|
729
|
+
|
|
730
|
+
Example
|
|
731
|
+
^^^^^^^
|
|
732
|
+
|
|
733
|
+
.. code:: python
|
|
734
|
+
|
|
735
|
+
@dataclass
|
|
736
|
+
class SimpleBlock(metaclass=BlockMetaClass):
|
|
737
|
+
|
|
738
|
+
q0.new: Quantity
|
|
739
|
+
|
|
740
|
+
def flux_0(self):
|
|
741
|
+
return q0.new
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
def dflux_0_dq0(self):
|
|
745
|
+
return 1.0
|
|
746
|
+
|
|
747
|
+
flux_0_expression = Expression(
|
|
748
|
+
1,
|
|
749
|
+
SimpleBlock.flux_0,
|
|
750
|
+
{
|
|
751
|
+
"q0": SimpleBlock.dflux_0_dq0
|
|
752
|
+
}
|
|
753
|
+
)
|
|
754
|
+
|
|
755
|
+
SimpleBlock.declares_flux_expression(
|
|
756
|
+
0, # Local Node index,
|
|
757
|
+
"potential_0", # Associated DOF id
|
|
758
|
+
flux_0_expression, # flux expression
|
|
759
|
+
)
|
|
760
|
+
"""
|
|
761
|
+
|
|
762
|
+
if node_index in cls.nodes:
|
|
763
|
+
raise ValueError(
|
|
764
|
+
str.format(
|
|
765
|
+
"Flux {0} is already defined for the block node at index {1}.",
|
|
766
|
+
cls._fluxes[node_index].expression.expr_func.__name__,
|
|
767
|
+
node_index,
|
|
768
|
+
)
|
|
769
|
+
)
|
|
770
|
+
|
|
771
|
+
cls._fluxes[node_index] = ExpressionDefinition(
|
|
772
|
+
expr, [TermDefinition(variable_id, expr.size)]
|
|
773
|
+
)
|
|
774
|
+
|
|
775
|
+
@property
|
|
776
|
+
def nodes(cls) -> list[int]:
|
|
777
|
+
"""Get all the local nodes indexes.
|
|
778
|
+
|
|
779
|
+
:return: The list of indexes.
|
|
780
|
+
:rtype: list[int]
|
|
781
|
+
"""
|
|
782
|
+
return [node_index for node_index in cls._fluxes]
|
|
783
|
+
|
|
784
|
+
@property
|
|
785
|
+
def fluxes_expressions(cls) -> dict[int, ExpressionDefinition]:
|
|
786
|
+
"""Get all the fluxes expressions in the block with the local node where they
|
|
787
|
+
are shared.
|
|
788
|
+
|
|
789
|
+
:return: the fluxes exressions ordered by node index.
|
|
790
|
+
:rtype: dict[int, ExpressionDefinition]
|
|
791
|
+
"""
|
|
792
|
+
return cls._fluxes.copy()
|
|
793
|
+
|
|
794
|
+
@property
|
|
795
|
+
def external_variables_ids(cls) -> list[str]:
|
|
796
|
+
"""
|
|
797
|
+
Get local id of variables defined by the flux connecting to a node in the block.
|
|
798
|
+
|
|
799
|
+
:return: a list of all local external variables ids.
|
|
800
|
+
:rtype: list[str]
|
|
801
|
+
"""
|
|
802
|
+
return [
|
|
803
|
+
term.term_id for expr_def in cls._fluxes.values() for term in expr_def.terms
|
|
804
|
+
]
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
class Block(ModelComponent, metaclass=BlockMetaClass):
|
|
808
|
+
"""
|
|
809
|
+
Extends :class:`~physioblocks.computing.models.ModelComponent` and declare
|
|
810
|
+
functions to compute **Flux**.
|
|
811
|
+
"""
|