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.
Files changed (93) hide show
  1. physioblocks/__init__.py +37 -0
  2. physioblocks/base/__init__.py +27 -0
  3. physioblocks/base/operators.py +176 -0
  4. physioblocks/base/registers.py +108 -0
  5. physioblocks/computing/__init__.py +47 -0
  6. physioblocks/computing/assembling.py +291 -0
  7. physioblocks/computing/models.py +811 -0
  8. physioblocks/computing/quantities.py +354 -0
  9. physioblocks/configuration/__init__.py +38 -0
  10. physioblocks/configuration/aliases.py +203 -0
  11. physioblocks/configuration/base.py +123 -0
  12. physioblocks/configuration/computing/__init__.py +27 -0
  13. physioblocks/configuration/computing/quantities.py +56 -0
  14. physioblocks/configuration/constants.py +121 -0
  15. physioblocks/configuration/description/__init__.py +33 -0
  16. physioblocks/configuration/description/blocks.py +239 -0
  17. physioblocks/configuration/description/nets.py +155 -0
  18. physioblocks/configuration/functions.py +695 -0
  19. physioblocks/configuration/simulation/__init__.py +32 -0
  20. physioblocks/configuration/simulation/simulations.py +280 -0
  21. physioblocks/description/__init__.py +34 -0
  22. physioblocks/description/blocks.py +418 -0
  23. physioblocks/description/flux.py +157 -0
  24. physioblocks/description/nets.py +746 -0
  25. physioblocks/io/__init__.py +29 -0
  26. physioblocks/io/aliases.py +73 -0
  27. physioblocks/io/configuration.py +125 -0
  28. physioblocks/launcher/__main__.py +285 -0
  29. physioblocks/launcher/configuration.py +231 -0
  30. physioblocks/launcher/configure/__main__.py +99 -0
  31. physioblocks/launcher/constants.py +105 -0
  32. physioblocks/launcher/files.py +150 -0
  33. physioblocks/launcher/series.py +165 -0
  34. physioblocks/library/__init__.py +27 -0
  35. physioblocks/library/aliases/blocks/c_block.json +5 -0
  36. physioblocks/library/aliases/blocks/rc_block.json +5 -0
  37. physioblocks/library/aliases/blocks/rcr_block.json +5 -0
  38. physioblocks/library/aliases/blocks/spherical_cavity_block.json +5 -0
  39. physioblocks/library/aliases/blocks/valve_rl_block.json +5 -0
  40. physioblocks/library/aliases/flux/heart_flux_dof_couples.jsonc +4 -0
  41. physioblocks/library/aliases/model_components/active_law_macro_huxley_two_moments.json +5 -0
  42. physioblocks/library/aliases/model_components/rheology_fiber_additive.json +5 -0
  43. physioblocks/library/aliases/model_components/spherical_dynamics.json +5 -0
  44. physioblocks/library/aliases/model_components/velocity_law_hht.json +5 -0
  45. physioblocks/library/aliases/nets/circulation_alone_net.json +31 -0
  46. physioblocks/library/aliases/nets/spherical_heart_net.json +93 -0
  47. physioblocks/library/aliases/simulations/circulation_alone_forward_simulation.jsonc +55 -0
  48. physioblocks/library/aliases/simulations/default_forward_simulation.jsonc +7 -0
  49. physioblocks/library/aliases/simulations/default_time.jsonc +8 -0
  50. physioblocks/library/aliases/simulations/newton_method_solver.json +5 -0
  51. physioblocks/library/aliases/simulations/spherical_heart_forward_simulation.jsonc +157 -0
  52. physioblocks/library/aliases/simulations/spherical_heart_with_respiration_forward_simulation.jsonc +45 -0
  53. physioblocks/library/blocks/__init__.py +27 -0
  54. physioblocks/library/blocks/capacitances.py +516 -0
  55. physioblocks/library/blocks/cavity.py +192 -0
  56. physioblocks/library/blocks/valves.py +281 -0
  57. physioblocks/library/functions/__init__.py +27 -0
  58. physioblocks/library/functions/base_operations.py +129 -0
  59. physioblocks/library/functions/first_order.py +113 -0
  60. physioblocks/library/functions/piecewise.py +271 -0
  61. physioblocks/library/functions/trigonometric.py +78 -0
  62. physioblocks/library/functions/watchers.py +113 -0
  63. physioblocks/library/model_components/__init__.py +27 -0
  64. physioblocks/library/model_components/active_law.py +345 -0
  65. physioblocks/library/model_components/dynamics.py +986 -0
  66. physioblocks/library/model_components/rheology.py +160 -0
  67. physioblocks/library/model_components/velocity_law.py +169 -0
  68. physioblocks/references/circulation_alone_sim.jsonc +24 -0
  69. physioblocks/references/spherical_heart_respiration_sim.jsonc +33 -0
  70. physioblocks/references/spherical_heart_sim.jsonc +29 -0
  71. physioblocks/registers/__init__.py +32 -0
  72. physioblocks/registers/load_function_register.py +93 -0
  73. physioblocks/registers/save_function_register.py +106 -0
  74. physioblocks/registers/type_register.py +97 -0
  75. physioblocks/simulation/__init__.py +48 -0
  76. physioblocks/simulation/constants.py +30 -0
  77. physioblocks/simulation/functions.py +71 -0
  78. physioblocks/simulation/runtime.py +484 -0
  79. physioblocks/simulation/saved_quantities.py +129 -0
  80. physioblocks/simulation/setup.py +576 -0
  81. physioblocks/simulation/solvers.py +235 -0
  82. physioblocks/simulation/state.py +340 -0
  83. physioblocks/simulation/time_manager.py +354 -0
  84. physioblocks/utils/__init__.py +27 -0
  85. physioblocks/utils/dynamic_import_utils.py +150 -0
  86. physioblocks/utils/exceptions_utils.py +115 -0
  87. physioblocks/utils/gradient_test_utils.py +337 -0
  88. physioblocks/utils/math_utils.py +109 -0
  89. physioblocks-1.0.0.dist-info/METADATA +127 -0
  90. physioblocks-1.0.0.dist-info/RECORD +93 -0
  91. physioblocks-1.0.0.dist-info/WHEEL +4 -0
  92. physioblocks-1.0.0.dist-info/licenses/licenses/GPL-3.0-only.txt +674 -0
  93. 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
+ """