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,37 @@
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
+ """Physioblocks package definition"""
28
+
29
+ __version__ = "1.0.0"
30
+ __copyright__ = "INRIA"
31
+ __license__ = "LGPL-3.0-only"
32
+ __authors__ = [
33
+ "Colin Drieu",
34
+ "Dominique Chapelle",
35
+ "François Kimmig",
36
+ "Philippe Moireau",
37
+ ]
@@ -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 bases classes and functions."""
@@ -0,0 +1,176 @@
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
+ """Define base classe to provide generic base operators"""
28
+
29
+ from abc import ABC, abstractmethod
30
+ from typing import Any
31
+
32
+ import numpy as np
33
+ from numpy.typing import NDArray
34
+
35
+
36
+ class AbstractBaseOperators(ABC):
37
+ """Base class defining behaviors for common operators
38
+
39
+ The derived classes should implement the :attr:`operation_value` property
40
+ to return the attribute of the class that will be used with base
41
+ operators.
42
+ """
43
+
44
+ @property
45
+ @abstractmethod
46
+ def operation_value(self) -> Any:
47
+ """
48
+ Overwrite in derived class to return the value used by the
49
+ class for the base operators.
50
+ """
51
+ raise NotImplementedError(
52
+ str.format(
53
+ "{0} is not implemented in {1} class.",
54
+ AbstractBaseOperators.operation_value.__name__,
55
+ type(self),
56
+ )
57
+ )
58
+
59
+ # Convertion
60
+
61
+ def __float__(self) -> float:
62
+ return float(self.operation_value)
63
+
64
+ def __array__(
65
+ self, dtype: type | None = None, copy: bool | None = None
66
+ ) -> NDArray[Any]:
67
+ return np.asarray(self.operation_value, dtype=dtype, copy=copy)
68
+
69
+ # Compare
70
+
71
+ # Equality
72
+ def __eq__(self, other: Any) -> bool:
73
+ compared_value = (
74
+ other.operation_value if isinstance(other, type(self)) else other
75
+ )
76
+ return bool(self.operation_value == compared_value)
77
+
78
+ # Greater
79
+ def __ge__(self, other: Any) -> bool:
80
+ if isinstance(other, type(self)):
81
+ return bool(self.operation_value >= other.operation_value)
82
+ return bool(self.operation_value >= other)
83
+
84
+ def __gt__(self, other: Any) -> bool:
85
+ if isinstance(other, type(self)):
86
+ return bool(self.operation_value > other.operation_value)
87
+ return bool(self.operation_value > other)
88
+
89
+ # Lesser
90
+ def __le__(self, other: Any) -> bool:
91
+ if isinstance(other, type(self)):
92
+ return bool(self.operation_value <= other.operation_value)
93
+ return bool(self.operation_value <= other)
94
+
95
+ def __lt__(self, other: Any) -> bool:
96
+ if isinstance(other, type(self)):
97
+ return bool(self.operation_value < other.operation_value)
98
+ return bool(self.operation_value < other)
99
+
100
+ # Base Operations
101
+
102
+ # Sum
103
+ def __add__(self, other: Any) -> Any:
104
+ if isinstance(other, type(self)):
105
+ return self.operation_value + other.operation_value
106
+ return self.operation_value + other
107
+
108
+ def __radd__(self, other: Any) -> Any:
109
+ return other + self.operation_value
110
+
111
+ # Subtraction
112
+ def __sub__(self, other: Any) -> Any:
113
+ if isinstance(other, type(self)):
114
+ return self.operation_value - other.operation_value
115
+ return self.operation_value - other
116
+
117
+ def __rsub__(self, other: Any) -> Any:
118
+ return other - self.operation_value
119
+
120
+ # Oposite
121
+ def __neg__(self) -> Any:
122
+ return -self.operation_value
123
+
124
+ # Multiplication
125
+ def __mul__(self, other: Any) -> Any:
126
+ if isinstance(other, type(self)):
127
+ return self.operation_value * other.operation_value
128
+ return self.operation_value * other
129
+
130
+ def __rmul__(self, other: Any) -> Any:
131
+ return other * self.operation_value
132
+
133
+ # MatMul
134
+ def __matmul__(self, other: Any) -> Any:
135
+ if isinstance(other, type(self)):
136
+ return self.operation_value @ other.operation_value
137
+ return self.operation_value @ other
138
+
139
+ def __rmatmul__(self, other: Any) -> Any:
140
+ return other @ self.operation_value
141
+
142
+ # Division
143
+ def __truediv__(self, other: Any) -> Any:
144
+ if isinstance(other, type(self)):
145
+ return self.operation_value / other.operation_value
146
+ return self.operation_value / other
147
+
148
+ def __rtruediv__(self, other: Any) -> Any:
149
+ return other / self.operation_value
150
+
151
+ # Floor Division
152
+ def __floordiv__(self, other: Any) -> Any:
153
+ if isinstance(other, type(self)):
154
+ return self.operation_value // other.operation_value
155
+ return self.operation_value // other
156
+
157
+ def __rfloordiv__(self, other: Any) -> Any:
158
+ return other // self.operation_value
159
+
160
+ # Modulo
161
+ def __mod__(self, other: Any) -> Any:
162
+ if isinstance(other, type(self)):
163
+ return self.operation_value % other.operation_value
164
+ return self.operation_value % other
165
+
166
+ def __rmod__(self, other: Any) -> Any:
167
+ return other % self.operation_value
168
+
169
+ # Power
170
+ def __pow__(self, other: Any) -> Any:
171
+ if isinstance(other, type(self)):
172
+ return self.operation_value**other.operation_value
173
+ return self.operation_value**other
174
+
175
+ def __rpow__(self, other: Any) -> Any:
176
+ return other**self.operation_value
@@ -0,0 +1,108 @@
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
+ """Define functions to update two-way :type:`dict` using common :type:`dict`"""
28
+
29
+ from typing import Any
30
+
31
+
32
+ def register(
33
+ register: dict[Any, Any], registered_key: Any, registered_value: Any
34
+ ) -> None:
35
+ """
36
+ Register the key and value to the provided register.
37
+
38
+ :param register: the register to fill.
39
+ :type dict[Any, Any]:
40
+
41
+ :param registered_key: the key to register.
42
+ :type Any:
43
+
44
+ :param registered_value: the value to register.
45
+ :type Any:
46
+
47
+ :raise ValueError: Raises a ValueError if either the key or value
48
+ is already registered in the provided register.
49
+ """
50
+ if registered_key in register or registered_value in register:
51
+ raise RegisterError(
52
+ str.format(
53
+ "{0}: {1} key or value is already registered.",
54
+ registered_key,
55
+ registered_value,
56
+ )
57
+ )
58
+
59
+ register[registered_key] = registered_value
60
+ register[registered_value] = registered_key
61
+
62
+
63
+ def check_key_value_type(
64
+ key: Any, key_type: type[Any], value: Any, value_type: type[Any]
65
+ ) -> None:
66
+ """
67
+ Check if key and params are of the expected types.
68
+
69
+ If not, it raises a type error.
70
+
71
+ :param key: the key value
72
+ :type key: Any
73
+
74
+ :param key_type: the expected type for the key
75
+ :type key_type: type
76
+
77
+ :param value: the value
78
+ :type value: Any
79
+
80
+ :param value_type: the expected type for the value
81
+ :type value_type: type
82
+
83
+ :raises TypeError: raise a type error if key or value param is not of the expected
84
+ types
85
+ """
86
+ if isinstance(key, key_type) is False:
87
+ raise TypeError(
88
+ str.format(
89
+ "Expected type for key is {0} but got {1}.",
90
+ key_type.__name__,
91
+ type(key).__name__,
92
+ )
93
+ )
94
+
95
+ elif isinstance(value, value_type) is False:
96
+ raise TypeError(
97
+ str.format(
98
+ "Expected type for value is {0} but got {1}.",
99
+ value_type.__name__,
100
+ type(value).__name__,
101
+ )
102
+ )
103
+
104
+
105
+ class RegisterError(Exception):
106
+ """Error Raised when a key or value has already been registered."""
107
+
108
+ pass
@@ -0,0 +1,47 @@
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
+ Computes numeric values for the models.
29
+ """
30
+
31
+ __all__ = [
32
+ "Block",
33
+ "Expression",
34
+ "ModelComponent",
35
+ "Quantity",
36
+ "diff",
37
+ "mid_alpha",
38
+ "mid_point",
39
+ ]
40
+
41
+ from physioblocks.computing.models import Block, Expression, ModelComponent
42
+ from physioblocks.computing.quantities import (
43
+ Quantity,
44
+ diff,
45
+ mid_alpha,
46
+ mid_point,
47
+ )
@@ -0,0 +1,291 @@
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
+ Allows to build systems that computes residual and gradient from collections
29
+ of functions.
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ from dataclasses import dataclass
35
+ from pprint import pformat
36
+ from typing import Any
37
+
38
+ import numpy as np
39
+ from numpy.typing import NDArray
40
+
41
+ from physioblocks.computing.models import ModelComponent, SystemFunction
42
+
43
+
44
+ @dataclass
45
+ class _EqSystemPart:
46
+ """
47
+ Part of the global equation system.
48
+
49
+ It computes a term that can be summed in the global system at the provided index.
50
+ """
51
+
52
+ line_index: int
53
+ """Index where to sum the part in the global system"""
54
+ res_part_size: int
55
+ """Number of lines in the result of the residual part function"""
56
+ model_component: ModelComponent
57
+ """The model that holds the data use to compute the residual and gradient part"""
58
+ res_func: SystemFunction
59
+ """The function to compute the residual part"""
60
+ column_index: int
61
+ """Start column index of the gradient part"""
62
+ grad_part_size: int
63
+ """Number of colums in the result of the gradient part function"""
64
+ grad_funcs: dict[int, SystemFunction]
65
+ """A collection of fonctions to compute the gradient part for each variables,
66
+ associated with their column index"""
67
+
68
+ def compute_residual_part(self) -> np.float64 | NDArray[np.float64]:
69
+ """
70
+ Compute the residual part numerical value.
71
+
72
+ Can be a scalar or vector of size _res_part_size.
73
+
74
+ :return: the residual part value
75
+ :rtype: np.float64 | NDArray[np.float64]
76
+ """
77
+ return self.res_func(self.model_component)
78
+
79
+ def compute_gradient_part(self) -> NDArray[np.float64]:
80
+ """
81
+ Compute the gradient part numerical value.
82
+
83
+ It a matrix of size (_res_part_size, _grad_part_size) to sum in the
84
+ global gradient at index (_index, _min_grad_index).
85
+
86
+ The numerical values are evaluated where the gradient functions are provided,
87
+ the remaing values are set to 0.
88
+
89
+ :return: the gradient part numerical value
90
+ :rtype: np.float64 | NDArray[np.float64]
91
+ """
92
+ grad_part = np.zeros(
93
+ (self.res_part_size, self.grad_part_size),
94
+ )
95
+
96
+ for grad_index in self.grad_funcs:
97
+ gradient_func = self.grad_funcs[grad_index]
98
+ grad_colum_index = grad_index - self.column_index
99
+ grad_part[:, grad_colum_index] += gradient_func(self.model_component)
100
+
101
+ return grad_part
102
+
103
+
104
+ def _create_eq_system_part(
105
+ residual_index: int,
106
+ residual_size: int,
107
+ residual_func: SystemFunction,
108
+ gradients_func: dict[int, SystemFunction],
109
+ model_component: ModelComponent,
110
+ ) -> _EqSystemPart:
111
+ """
112
+ Create an equation system part.
113
+
114
+ :param index: the line index where to sum the expression in the global
115
+ system
116
+ :type index: int
117
+
118
+ :param residual_size: the size of the result of the residual_func
119
+ :type residual_size: int
120
+
121
+ :param residual_func: the function to compute the residual part
122
+ :type residual_func: SystemFunction
123
+
124
+ :param gradients_func: the functions to compute the gradients parts associated with
125
+ the index of the variable in the state vector
126
+
127
+ :param model_component: the model holding the data needed to compute the expression
128
+ :type model_component: ModelComponent
129
+
130
+ :return: an equation system part
131
+ :rtype: _EqSystemPart
132
+ """
133
+ grad_part_size = 0
134
+ min_grad_index = 0
135
+ if len(gradients_func) > 0:
136
+ min_grad_index = min(gradients_func.keys())
137
+ grad_part_size = max(gradients_func) - min_grad_index + 1
138
+
139
+ return _EqSystemPart(
140
+ residual_index,
141
+ residual_size,
142
+ model_component,
143
+ residual_func,
144
+ min_grad_index,
145
+ grad_part_size,
146
+ gradients_func,
147
+ )
148
+
149
+
150
+ class EqSystem:
151
+ """
152
+ Global Equation system
153
+
154
+ Allows to compute the numerical value of the residual and the gradient
155
+ for a :class:`~physioblocks.description.nets.Net`.
156
+
157
+ The system is built from a collection of **System Parts**.
158
+ The parts are summed in a global system to compute the numerical value of
159
+ the residual and the gradient.
160
+ """
161
+
162
+ _system_size: int
163
+ """The system size, fixed at system creation"""
164
+ _system_parts: list[_EqSystemPart]
165
+ """The collection of system parts"""
166
+
167
+ def __init__(self, size: int):
168
+ """
169
+ EqSystem constructor
170
+
171
+ :param size: system fixed size
172
+ :type size: int
173
+ """
174
+ self._system_size = size
175
+ self._system_parts = []
176
+
177
+ def __str__(self) -> str:
178
+ """
179
+ Get the equation system string representation.
180
+
181
+ :return: the equation system string representation
182
+ :rtype: str
183
+ """
184
+
185
+ eq_system_dict = {
186
+ index: [
187
+ (eq_part.res_func.__qualname__, "size " + str(eq_part.res_part_size))
188
+ for eq_part in self._system_parts
189
+ if eq_part.line_index == index
190
+ ]
191
+ for index in range(0, self.system_size)
192
+ }
193
+
194
+ return pformat(eq_system_dict, indent=2, compact=False)
195
+
196
+ @property
197
+ def system_size(self) -> int:
198
+ """
199
+ Get the system size
200
+
201
+ :return: the system size
202
+ :rtype: int
203
+ """
204
+ return self._system_size
205
+
206
+ def add_system_part(
207
+ self,
208
+ residual_index: int,
209
+ residual_size: int,
210
+ residual_func: SystemFunction,
211
+ gradients_func: dict[int, SystemFunction],
212
+ parameters: Any,
213
+ ) -> None:
214
+ """add_system_part(self, residual_index: int, residual_size: int, residual_func: SystemFunction, gradients_func: dict[int, SystemFunction], parameters: Any)
215
+ Add an equation system part to the global system.
216
+
217
+ :param residual_index:
218
+ the line index where to sum the function in the global system
219
+ :type residual_index: int
220
+
221
+ :param residual_size: the size of the result of the residual_func
222
+ :type residual_size: int
223
+
224
+ :param residual_func: the function to compute the residual part
225
+ :type residual_func: SystemFunction
226
+
227
+ :param gradients_func:
228
+ the functions to compute the gradients parts associated with
229
+ the index of the variable in the state vector
230
+
231
+ :param parameters: the parameters needed to compute the SystemFunctions
232
+ (both residual and gradient)
233
+ (should be the same type as the argument of the residual_func and the
234
+ gradients_func input type)
235
+ :type parameters: Any
236
+
237
+ :raise ValueError: Raises a ValueError when either the size of the residual
238
+ or the gradient part will exceed the global system size.
239
+ """ # noqa: E501
240
+
241
+ part = _create_eq_system_part(
242
+ residual_index, residual_size, residual_func, gradients_func, parameters
243
+ )
244
+
245
+ if part.line_index + part.res_part_size > self.system_size:
246
+ raise ValueError("The residual part exceed system size")
247
+
248
+ if part.column_index + part.grad_part_size > self.system_size:
249
+ raise ValueError("The gradient part exceed system size")
250
+
251
+ self._system_parts.append(part)
252
+
253
+ def compute_residual(self) -> NDArray[np.float64]:
254
+ """
255
+ Compute the numerical value of the residual.
256
+
257
+ :return: the residual
258
+ :rtype: NDArray[np.float64]
259
+ """
260
+
261
+ residual = np.zeros(
262
+ shape=self.system_size,
263
+ )
264
+
265
+ for part in self._system_parts:
266
+ residual_part = part.compute_residual_part()
267
+ residual_column_index = part.line_index + part.res_part_size
268
+ residual[part.line_index : residual_column_index] += residual_part
269
+
270
+ return residual
271
+
272
+ def compute_gradient(self) -> NDArray[np.float64]:
273
+ """
274
+ Compute the numerical value of the gradient.
275
+
276
+ :return: the gradient
277
+ :rtype: NDArray[np.float64]
278
+ """
279
+ grad = np.zeros(
280
+ shape=(self.system_size, self.system_size),
281
+ )
282
+
283
+ for part in self._system_parts:
284
+ if part.grad_part_size > 0:
285
+ gradient_part = part.compute_gradient_part()
286
+ grad[
287
+ part.line_index : part.line_index + part.res_part_size,
288
+ part.column_index : part.column_index + part.grad_part_size,
289
+ ] += gradient_part
290
+
291
+ return grad