physioblocks 1.0.2__py3-none-any.whl → 1.1.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 CHANGED
@@ -26,7 +26,7 @@
26
26
 
27
27
  """Physioblocks package definition"""
28
28
 
29
- __version__ = "1.0.2"
29
+ __version__ = "1.1.0"
30
30
  __copyright__ = "INRIA"
31
31
  __license__ = "LGPL-3.0-only"
32
32
  __authors__ = [
@@ -32,6 +32,7 @@ functions.
32
32
 
33
33
  from __future__ import annotations
34
34
 
35
+ import functools
35
36
  from collections.abc import Callable
36
37
  from dataclasses import dataclass, field
37
38
  from inspect import get_annotations
@@ -46,7 +47,7 @@ SystemFunction: TypeAlias = Callable[..., np.float64 | NDArray[np.float64]]
46
47
  """Type alias for functions composing the system"""
47
48
 
48
49
 
49
- @dataclass(frozen=True)
50
+ @dataclass
50
51
  class Expression:
51
52
  """Expression(size:int, expr_func: SystemFunction, expr_gradients: Mapping[str, SystemFunction] = {})
52
53
  Store function computing numerical values for terms in the models with the function
@@ -102,6 +103,223 @@ class Expression:
102
103
  )
103
104
 
104
105
 
106
+ class ExpressionDecorator:
107
+ """
108
+ Base class for expression decorators.
109
+
110
+ This is a helper that defines expressions decorating methods in a model or block
111
+ class.
112
+ """
113
+
114
+ expression: Expression
115
+ """The expression resulting from decorators declarations"""
116
+
117
+ def __init__(
118
+ self,
119
+ wrapped_function: Callable[..., Any],
120
+ ):
121
+ """
122
+ :param wrapped_function: The decorated expression function
123
+ :type wrapped_function: Callable
124
+ """
125
+ self.expression = Expression(0, wrapped_function)
126
+ self._terms: list[tuple[str, int, int]] = []
127
+ functools.update_wrapper(self, wrapped_function)
128
+
129
+ def register_term(
130
+ self,
131
+ term_name: str,
132
+ term_size: int,
133
+ term_index: int,
134
+ ) -> None:
135
+ """
136
+ Register a new term to the expression decorator
137
+
138
+ :param term_name: the term local name.
139
+ :type term_name: str
140
+
141
+ :param term_size: The size of the term
142
+ :type term_size: int
143
+
144
+ :param term_index: The start index of the term in the expression
145
+ :type term_index: int
146
+ """
147
+ self.expression.size += term_size
148
+ self._terms.append((term_name, term_size, term_index))
149
+
150
+ @property
151
+ def terms(self) -> list[tuple[str, int, int]]:
152
+ """
153
+ Get the terms described by the expression.
154
+
155
+ :return: list of terms description
156
+ :rtype: list[[tuple[str, int, int]]]
157
+ """
158
+ return self._terms.copy()
159
+
160
+ def __call__(self, *args: Any, **kwargs: Any) -> Any:
161
+ return self.expression.expr_func(*args, **kwargs)
162
+
163
+ def partial_derivative(
164
+ self, variable_name: str
165
+ ) -> Callable[..., Callable[..., Any]]:
166
+ """
167
+ Declares a flux partial derivative.
168
+
169
+ :param variable_name: the variable local name.
170
+ :type variable_name: str
171
+ """
172
+
173
+ def _register(wrapped: Callable[..., Any]) -> Callable[..., Any]:
174
+ self.expression.expr_gradients[variable_name] = wrapped
175
+ return wrapped
176
+
177
+ return _register
178
+
179
+
180
+ def _check_expression_decorator_type(
181
+ wrapped_function: Any, new_decorator_type: type[ExpressionDecorator]
182
+ ) -> None:
183
+ if (
184
+ isinstance(wrapped_function, ExpressionDecorator) is True
185
+ and isinstance(wrapped_function, new_decorator_type) is False
186
+ ):
187
+ raise ValueError(
188
+ str.format(
189
+ "Can not decorate a function of an other type. "
190
+ "Current type is {0}, new type is {1}.",
191
+ type(wrapped_function),
192
+ new_decorator_type,
193
+ )
194
+ )
195
+
196
+
197
+ class InternalEquationDecorator(ExpressionDecorator):
198
+ """
199
+ Define a decorator that identifies internal equation expressions.
200
+ """
201
+
202
+ pass
203
+
204
+
205
+ def declares_internal_equation(
206
+ variable_name: str, size: int = 1, starting_index: int = 0
207
+ ) -> Callable[..., InternalEquationDecorator]:
208
+ """
209
+ Declares a internal equation expression.
210
+
211
+ Once the internal equation function is declared, partial derivatives can be added
212
+ using the function name and the local variable name for the partial derivative.
213
+
214
+ :param variable_name: the variable name on which the expression provides a dynamic.
215
+ :type variable_name: str
216
+
217
+ :param size: the size of the equation. Default is 1
218
+ :type size: int
219
+
220
+ :param starting_index: the starting index of the equation line matching the
221
+ variable. Default is 0
222
+ :type starting_index: int
223
+
224
+ :raises ValueError: Raises a ValueError if the function is already decorated with
225
+ an expression decorator of an other type.
226
+
227
+ Example
228
+ ^^^^^^^
229
+
230
+ .. code:: python
231
+
232
+ @dataclass
233
+ class SimpleModel(ModelComponent):
234
+
235
+ x: Quantity
236
+ a: Quantity
237
+
238
+ @declares_internal_equation("x")
239
+ def residual(self):
240
+ return x.new**2 - a.current
241
+
242
+ @flux_1.partial_derivative("x")
243
+ def dresidual_dx(self):
244
+ return 2.0 * x.new
245
+ """
246
+
247
+ def _create_internal_equation_decorator(
248
+ wrapped: Callable[..., Any],
249
+ ) -> InternalEquationDecorator:
250
+ _check_expression_decorator_type(wrapped, InternalEquationDecorator)
251
+
252
+ decorated = (
253
+ wrapped
254
+ if isinstance(wrapped, InternalEquationDecorator)
255
+ else InternalEquationDecorator(wrapped)
256
+ )
257
+ decorated.register_term(variable_name, size, starting_index)
258
+ return decorated
259
+
260
+ return _create_internal_equation_decorator
261
+
262
+
263
+ class SavedQuantityDecorator(ExpressionDecorator):
264
+ """
265
+ Define a decorator that identifies saved quantities expressions.
266
+ """
267
+
268
+ pass
269
+
270
+
271
+ def declares_saved_quantity(
272
+ quantity_name: str, size: int = 1, starting_index: int = 0
273
+ ) -> Callable[..., SavedQuantityDecorator]:
274
+ """
275
+ Declares a saved quantity expression.
276
+
277
+ :param quantity_name: the local quantity name.
278
+ :type quantity_name: str
279
+
280
+ :param size: the size of the expression. Default is 1
281
+ :type size: int
282
+
283
+ :param starting_index: the starting index of the expression in the function.
284
+ Default is 0
285
+ :type starting_index: int
286
+
287
+ :raises ValueError: Raises a ValueError if the function is already decorated with
288
+ an expression decorator of an other type.
289
+
290
+ Example
291
+ ^^^^^^^
292
+
293
+ .. code:: python
294
+
295
+ @dataclass
296
+ class SimpleModel(ModelComponent):
297
+
298
+ a: Quantity
299
+ b: Quantity
300
+
301
+ @declares_saved_quantity("a + b")
302
+ def sum(self):
303
+ return a.current + b.current
304
+ """
305
+
306
+ def _create_saved_quantity_decorator(
307
+ wrapped: Callable[..., Any],
308
+ ) -> SavedQuantityDecorator:
309
+ _check_expression_decorator_type(wrapped, SavedQuantityDecorator)
310
+
311
+ decorator = (
312
+ wrapped
313
+ if isinstance(wrapped, SavedQuantityDecorator)
314
+ else SavedQuantityDecorator(wrapped)
315
+ )
316
+ decorator.register_term(quantity_name, size, starting_index)
317
+
318
+ return decorator
319
+
320
+ return _create_saved_quantity_decorator
321
+
322
+
105
323
  @dataclass(frozen=True)
106
324
  class TermDefinition:
107
325
  """Describe Terms defined in an :class:`~physioblocks.computing.models.Expression`.
@@ -303,6 +521,23 @@ class ModelComponentMetaClass(type):
303
521
  cls.__INTERNAL_EXPRESSION_KEY: [],
304
522
  cls.__SAVED_QUANTITIES_EXPRESSION_KEY: [],
305
523
  }
524
+ for attr in cls.__dict__.values():
525
+ if isinstance(attr, InternalEquationDecorator):
526
+ for term_name, term_size, term_index in attr.terms:
527
+ cls.declares_internal_expression(
528
+ term_name,
529
+ attr.expression,
530
+ term_size,
531
+ term_index,
532
+ )
533
+ elif isinstance(attr, SavedQuantityDecorator):
534
+ for term_name, term_size, term_index in attr.terms:
535
+ cls.declares_saved_quantity_expression(
536
+ term_name,
537
+ attr.expression,
538
+ term_size,
539
+ term_index,
540
+ )
306
541
 
307
542
  @staticmethod
308
543
  def __is_quantity_type(type_to_test: Any) -> bool:
@@ -481,6 +716,7 @@ class ModelComponentMetaClass(type):
481
716
 
482
717
  # Add the term definition to the expression definition
483
718
  expression_def.terms.append(TermDefinition(term_id, size, index))
719
+ expression_def.terms.sort(key=lambda term: term.index)
484
720
 
485
721
  def declares_internal_expression(
486
722
  cls,
@@ -690,6 +926,78 @@ class ModelComponent(metaclass=ModelComponentMetaClass):
690
926
  """Override this method to define specific for model initialization."""
691
927
 
692
928
 
929
+ class FluxDecorator(ExpressionDecorator):
930
+ """
931
+ Define a decorator that identifies flux functions
932
+ """
933
+
934
+ pass
935
+
936
+
937
+ def declares_flux(
938
+ index: int, dof_name: str, size: int = 1
939
+ ) -> Callable[..., FluxDecorator]:
940
+ """
941
+ Declares a flux function.
942
+
943
+ Once the flux expression is declared, partial derivatives can be added using the
944
+ flux function name and the local variable name for the partial derivative.
945
+
946
+ :param index: the index of the flux in the block.
947
+ :type index: int
948
+
949
+ :param dof_name: the matching dof local name
950
+ :type dof_name: str
951
+
952
+ :param size: the size of the flux returned by the function
953
+ :type size: int
954
+
955
+ :raises ValueError: Raises a ValueError if the function is already decorated with
956
+ an expression decorator of an other type.
957
+
958
+
959
+ Example
960
+ ^^^^^^^
961
+
962
+ .. code:: python
963
+
964
+ @dataclass
965
+ class SimpleBlock(Block):
966
+
967
+ q_1: Quantity
968
+
969
+ # declares a flux shared at local node 1, where the associated dof has the
970
+ # local name "potential_1"
971
+ @declares_flux(1, "potential_1")
972
+ def flux_1(self):
973
+ return q_1.new
974
+
975
+ # associate the following function as the partial derivative of "flux_1" for
976
+ # variable "q_1"
977
+ @flux_1.partial_derivative("q_1")
978
+ def dflux_1_dq_1(self):
979
+ return 1.0
980
+
981
+ """
982
+
983
+ def _create_flux_decorator(wrapped: Callable[..., Any]) -> FluxDecorator:
984
+ if isinstance(wrapped, ExpressionDecorator) is True:
985
+ raise ValueError(
986
+ str.format(
987
+ "Function already declares an expression, it can not be a flux."
988
+ )
989
+ )
990
+
991
+ decorator = (
992
+ wrapped if isinstance(wrapped, FluxDecorator) else FluxDecorator(wrapped)
993
+ )
994
+ decorator.register_term(dof_name, size, index)
995
+
996
+ return decorator
997
+
998
+ return _create_flux_decorator
999
+
1000
+
693
1001
  class BlockMetaClass(ModelComponentMetaClass):
694
1002
  """Meta-class for :class:`~physioblocks.computing.models.Block`.
695
1003
 
@@ -711,6 +1019,10 @@ class BlockMetaClass(ModelComponentMetaClass):
711
1019
  def __init__(cls, *args: Any, **kwargs: Any) -> None:
712
1020
  super().__init__(*args, **kwargs)
713
1021
  cls._fluxes = {}
1022
+ for attr in cls.__dict__.values():
1023
+ if isinstance(attr, FluxDecorator):
1024
+ for term_name, _term_size, term_index in attr.terms:
1025
+ cls.declares_flux_expression(term_index, term_name, attr.expression)
714
1026
 
715
1027
  def declares_flux_expression(
716
1028
  cls, node_index: int, variable_id: str, expr: Expression
@@ -38,11 +38,8 @@ PARAMETERS_ID = "parameters"
38
38
  # Definition of the flux-dof types couples
39
39
  FLUX_DOF_DEFINITION_ID = "flux_dof_definitions"
40
40
 
41
-
42
- # Magnitudes
43
-
44
- # The variable magnitude item label in the configuration
45
- MAGNITUDES = "magnitudes"
41
+ # Options label for specific simulation types
42
+ SIMULATION_OPTIONS = "simulation_options"
46
43
 
47
44
  # The variable magnitude item label in the configuration
48
45
  VARIABLES_MAGNITUDES = "variables_magnitudes"
@@ -35,10 +35,10 @@ from typing import Any
35
35
  from physioblocks.configuration.base import Configuration, ConfigurationError
36
36
  from physioblocks.configuration.constants import (
37
37
  INIT_VARIABLES_ID,
38
- MAGNITUDES,
39
38
  NET_ID,
40
39
  OUTPUTS_FUNCTIONS_ID,
41
40
  PARAMETERS_ID,
41
+ SIMULATION_OPTIONS,
42
42
  SOLVER_ID,
43
43
  TIME_MANAGER_ID,
44
44
  VARIABLES_MAGNITUDES,
@@ -86,16 +86,15 @@ def load_simulation_config(
86
86
  if SOLVER_ID in config:
87
87
  solver = load(config[SOLVER_ID])
88
88
 
89
- # magnitudes
90
- magnitudes = None
91
- if VARIABLES_MAGNITUDES in config:
92
- magnitudes = load(config[VARIABLES_MAGNITUDES])
89
+ simulation_options = None
90
+ if SIMULATION_OPTIONS in config:
91
+ simulation_options = load(config[SIMULATION_OPTIONS])
93
92
 
94
93
  sim_factory = SimulationFactory(
95
94
  configuration_type,
96
95
  solver,
97
96
  net,
98
- simulation_options={MAGNITUDES: magnitudes},
97
+ simulation_options=simulation_options,
99
98
  )
100
99
 
101
100
  configuration_object = sim_factory.create_simulation()
@@ -147,7 +146,7 @@ def save_simulation_config(
147
146
  type(variable_init_values).__name__,
148
147
  )
149
148
  )
150
- sim_config[VARIABLES_MAGNITUDES] = save(simulation.magnitudes)
149
+ sim_config[VARIABLES_MAGNITUDES] = save(simulation.state.magnitudes)
151
150
 
152
151
  # Parameters
153
152
  # Get quantities
@@ -226,6 +225,11 @@ def _configure_simulation(
226
225
  configuration_references=simulation.quantities,
227
226
  )
228
227
 
228
+ # magnitudes
229
+ if VARIABLES_MAGNITUDES in config:
230
+ magnitudes = load(config[VARIABLES_MAGNITUDES])
231
+ simulation.state.set_variables_magnitudes(magnitudes)
232
+
229
233
  references.update(simulation.quantities)
230
234
  references.update(simulation.models)
231
235
 
@@ -31,7 +31,12 @@ from typing import Any
31
31
 
32
32
  import numpy as np
33
33
 
34
- from physioblocks.computing import Block, Expression, Quantity, diff, mid_point
34
+ from physioblocks.computing import Block, Quantity, diff, mid_point
35
+ from physioblocks.computing.models import (
36
+ declares_flux,
37
+ declares_internal_equation,
38
+ declares_saved_quantity,
39
+ )
35
40
  from physioblocks.registers import register_type
36
41
  from physioblocks.simulation import Time
37
42
 
@@ -41,8 +46,8 @@ from physioblocks.simulation import Time
41
46
  # Constant for the c block type id
42
47
  C_BLOCK_TYPE_ID = "c_block"
43
48
 
44
- # Constant for the c block dof id
45
- C_BLOCK_PRESSURE_DOF_ID = "pressure"
49
+ # Constant for the c block pressure local id
50
+ C_BLOCK_PRESSURE_ID = "pressure"
46
51
 
47
52
 
48
53
  @register_type(C_BLOCK_TYPE_ID)
@@ -77,6 +82,7 @@ class CBlock(Block):
77
82
  time: Time
78
83
  """Simulation time"""
79
84
 
85
+ @declares_flux(1, C_BLOCK_PRESSURE_ID)
80
86
  def flux(self) -> Any:
81
87
  """
82
88
  Compute the flux at local node 1
@@ -86,6 +92,7 @@ class CBlock(Block):
86
92
  """
87
93
  return -self.capacitance.current * diff(self.pressure) * self.time.inv_dt
88
94
 
95
+ @flux.partial_derivative(C_BLOCK_PRESSURE_ID)
89
96
  def dflux_dpressure(self) -> Any:
90
97
  """
91
98
  Compute the flux at local node 1 partial derivative for pressure
@@ -96,12 +103,6 @@ class CBlock(Block):
96
103
  return -self.capacitance.current * self.time.inv_dt
97
104
 
98
105
 
99
- _c_block_flux_expression = Expression(
100
- 1, CBlock.flux, {C_BLOCK_PRESSURE_DOF_ID: CBlock.dflux_dpressure}
101
- )
102
- CBlock.declares_flux_expression(1, C_BLOCK_PRESSURE_DOF_ID, _c_block_flux_expression)
103
-
104
-
105
106
  # RC BLOCK Definition
106
107
 
107
108
  # Constant for the rc block type id
@@ -164,6 +165,7 @@ class RCBlock(Block):
164
165
  time: Time
165
166
  """The simulation time"""
166
167
 
168
+ @declares_flux(1, RC_BLOCK_PRESSURE_1_DOF_ID)
167
169
  def flux_1(self) -> Any:
168
170
  """
169
171
  Computes the outlet flux at local node 1.
@@ -175,6 +177,7 @@ class RCBlock(Block):
175
177
  pressure_2 = mid_point(self.pressure_2)
176
178
  return (pressure_2 - pressure_1) / self.resistance.current
177
179
 
180
+ @flux_1.partial_derivative(RC_BLOCK_PRESSURE_1_DOF_ID)
178
181
  def dflux_1_dpressure_1(self) -> Any:
179
182
  """
180
183
  Computes the outlet flux at node 1 derivative for pressure_1.
@@ -184,6 +187,7 @@ class RCBlock(Block):
184
187
  """
185
188
  return -0.5 / self.resistance.current
186
189
 
190
+ @flux_1.partial_derivative(RC_BLOCK_PRESSURE_2_DOF_ID)
187
191
  def dflux_1_dpressure_2(self) -> Any:
188
192
  """
189
193
  Computes the outlet flux at node 1 derivative for pressure_2.
@@ -193,6 +197,7 @@ class RCBlock(Block):
193
197
  """
194
198
  return 0.5 / self.resistance.current
195
199
 
200
+ @declares_flux(2, RC_BLOCK_PRESSURE_2_DOF_ID)
196
201
  def flux_2(self) -> Any:
197
202
  """
198
203
  Computes the outlet flux at node 2.
@@ -208,6 +213,7 @@ class RCBlock(Block):
208
213
  - self.capacitance.current * self.time.inv_dt * dpressure_2
209
214
  )
210
215
 
216
+ @flux_2.partial_derivative(RC_BLOCK_PRESSURE_1_DOF_ID)
211
217
  def dflux_2_dpressure_1(self) -> Any:
212
218
  """
213
219
  Computes the outlet flux at node 2 derivative for pressure_1.
@@ -217,6 +223,7 @@ class RCBlock(Block):
217
223
  """
218
224
  return 0.5 / self.resistance.current
219
225
 
226
+ @flux_2.partial_derivative(RC_BLOCK_PRESSURE_2_DOF_ID)
220
227
  def dflux_2_dpressure_2(self) -> Any:
221
228
  """
222
229
  Computes the outlet flux at node 2 derivative for pressure_2.
@@ -229,29 +236,6 @@ class RCBlock(Block):
229
236
  )
230
237
 
231
238
 
232
- # Define the flux expression going in the input node for rc_block
233
- _rc_block_flux_1_expr = Expression(
234
- 1,
235
- RCBlock.flux_1,
236
- {
237
- RC_BLOCK_PRESSURE_1_DOF_ID: RCBlock.dflux_1_dpressure_1,
238
- RC_BLOCK_PRESSURE_2_DOF_ID: RCBlock.dflux_1_dpressure_2,
239
- },
240
- )
241
-
242
- # Define the flux expression going in the output node for rc_block
243
- _rc_block_flux_2_expr = Expression(
244
- 1,
245
- RCBlock.flux_2,
246
- {
247
- RC_BLOCK_PRESSURE_1_DOF_ID: RCBlock.dflux_2_dpressure_1,
248
- RC_BLOCK_PRESSURE_2_DOF_ID: RCBlock.dflux_2_dpressure_2,
249
- },
250
- )
251
-
252
- RCBlock.declares_flux_expression(1, RC_BLOCK_PRESSURE_1_DOF_ID, _rc_block_flux_1_expr)
253
- RCBlock.declares_flux_expression(2, RC_BLOCK_PRESSURE_2_DOF_ID, _rc_block_flux_2_expr)
254
-
255
239
  # RCR BLOCK Definition
256
240
 
257
241
  # Constant for the rcr block type id
@@ -300,7 +284,7 @@ class RCRBlock(Block):
300
284
 
301
285
  \frac{P_1 - P_{mid}}{R_1} + \frac{P_2 - P_{mid}}{R_2} - C\dot{P}_{mid} = 0
302
286
 
303
- **Discretisation:**
287
+ **Discretization:**
304
288
 
305
289
  .. math::
306
290
 
@@ -341,6 +325,7 @@ class RCRBlock(Block):
341
325
  time: Time
342
326
  """The simulation time"""
343
327
 
328
+ @declares_flux(1, RCR_BLOCK_PRESSURE_1_ID)
344
329
  def flux_1(self) -> Any:
345
330
  """
346
331
  Computes the outlet flux at node 1.
@@ -354,6 +339,7 @@ class RCRBlock(Block):
354
339
 
355
340
  return (pressure_mid_discr - pressure_1_discr) / self.resistance_1.current
356
341
 
342
+ @flux_1.partial_derivative(RCR_BLOCK_PRESSURE_1_ID)
357
343
  def dflux_1_dp_1(self) -> Any:
358
344
  """
359
345
  Computes the outlet flux at node 1 derivative for pressure_1.
@@ -364,6 +350,7 @@ class RCRBlock(Block):
364
350
 
365
351
  return -0.5 / self.resistance_1.current
366
352
 
353
+ @flux_1.partial_derivative(RCR_BLOCK_PRESSURE_MID_ID)
367
354
  def dflux_1_dp_mid(self) -> Any:
368
355
  """
369
356
  Computes the outlet flux at node 1 derivative for pressure_mid.
@@ -374,6 +361,7 @@ class RCRBlock(Block):
374
361
 
375
362
  return 0.5 / self.resistance_1.current
376
363
 
364
+ @declares_flux(2, RCR_BLOCK_PRESSURE_2_ID)
377
365
  def flux_2(self) -> Any:
378
366
  """
379
367
  Computes the flux at node 2.
@@ -386,6 +374,7 @@ class RCRBlock(Block):
386
374
 
387
375
  return (pressure_mid_discr - pressure_2_discr) / self.resistance_2.current
388
376
 
377
+ @flux_2.partial_derivative(RCR_BLOCK_PRESSURE_MID_ID)
389
378
  def dflux_2_dp_mid(self) -> Any:
390
379
  """
391
380
  Computes the outlet flux at node 2 derivative for pressure_mid.
@@ -396,6 +385,7 @@ class RCRBlock(Block):
396
385
 
397
386
  return 0.5 / self.resistance_2.current
398
387
 
388
+ @flux_2.partial_derivative(RCR_BLOCK_PRESSURE_2_ID)
399
389
  def dflux_2_dp_2(self) -> Any:
400
390
  """
401
391
  Computes the outlet flux at node 2 derivative for pressure_2.
@@ -406,6 +396,7 @@ class RCRBlock(Block):
406
396
 
407
397
  return -0.5 / self.resistance_2.current
408
398
 
399
+ @declares_internal_equation(RCR_BLOCK_PRESSURE_MID_ID)
409
400
  def pressure_mid_residual(self) -> Any:
410
401
  """
411
402
  Compute the residual representing dynamics of the mid node pressure.
@@ -424,6 +415,7 @@ class RCRBlock(Block):
424
415
  - self.capacitance.current * self.time.inv_dt * diff(self.pressure_mid)
425
416
  )
426
417
 
418
+ @pressure_mid_residual.partial_derivative(RCR_BLOCK_PRESSURE_1_ID)
427
419
  def pressure_mid_residual_dp_1(self) -> Any:
428
420
  """
429
421
  Compute the residual derivative for pressure_1
@@ -434,6 +426,7 @@ class RCRBlock(Block):
434
426
 
435
427
  return 0.5 / self.resistance_1.current
436
428
 
429
+ @pressure_mid_residual.partial_derivative(RCR_BLOCK_PRESSURE_2_ID)
437
430
  def pressure_mid_residual_dp_2(self) -> Any:
438
431
  """
439
432
  Compute the residual derivative for pressure_2
@@ -444,6 +437,7 @@ class RCRBlock(Block):
444
437
 
445
438
  return 0.5 / self.resistance_2.current
446
439
 
440
+ @pressure_mid_residual.partial_derivative(RCR_BLOCK_PRESSURE_MID_ID)
447
441
  def pressure_mid_residual_dp_mid(self) -> Any:
448
442
  """
449
443
  Compute the residual derivative for pressure_mid
@@ -458,6 +452,7 @@ class RCRBlock(Block):
458
452
  - 0.5 / self.resistance_2.current
459
453
  )
460
454
 
455
+ @declares_saved_quantity("volume")
461
456
  def compute_volume_stored(self) -> Any:
462
457
  """
463
458
  Computes volume stored in the capacitance.
@@ -467,50 +462,3 @@ class RCRBlock(Block):
467
462
  """
468
463
 
469
464
  return self.capacitance.current * self.pressure_mid.current
470
-
471
-
472
- # Define the flux expression going in node 1 for rcr block
473
- _rcr_block_flux_1_expr = Expression(
474
- 1,
475
- RCRBlock.flux_1,
476
- {
477
- RCR_BLOCK_PRESSURE_1_ID: RCRBlock.dflux_1_dp_1,
478
- RCR_BLOCK_PRESSURE_MID_ID: RCRBlock.dflux_1_dp_mid,
479
- },
480
- )
481
-
482
-
483
- # Define the flux expression going in node 2 for rcr block
484
- _rcr_block_flux_2_expr = Expression(
485
- 1,
486
- RCRBlock.flux_2,
487
- {
488
- RCR_BLOCK_PRESSURE_MID_ID: RCRBlock.dflux_2_dp_mid,
489
- RCR_BLOCK_PRESSURE_2_ID: RCRBlock.dflux_2_dp_2,
490
- },
491
- )
492
-
493
- # Define the residual expression giving the pressure at the mid node
494
- _rcr_block_pressure_mid_residual_expr = Expression(
495
- 1,
496
- RCRBlock.pressure_mid_residual,
497
- {
498
- RCR_BLOCK_PRESSURE_1_ID: RCRBlock.pressure_mid_residual_dp_1,
499
- RCR_BLOCK_PRESSURE_MID_ID: RCRBlock.pressure_mid_residual_dp_mid,
500
- RCR_BLOCK_PRESSURE_2_ID: RCRBlock.pressure_mid_residual_dp_2,
501
- },
502
- )
503
-
504
- # Derfine the stored volume saved quantity expression.
505
- _rcr_volume_stored_expr = Expression(1, RCRBlock.compute_volume_stored)
506
-
507
-
508
- RCRBlock.declares_internal_expression(
509
- RCR_BLOCK_PRESSURE_MID_ID, _rcr_block_pressure_mid_residual_expr
510
- )
511
-
512
- RCRBlock.declares_flux_expression(1, RCR_BLOCK_PRESSURE_1_ID, _rcr_block_flux_1_expr)
513
- RCRBlock.declares_flux_expression(2, RCR_BLOCK_PRESSURE_2_ID, _rcr_block_flux_2_expr)
514
- RCRBlock.declares_saved_quantity_expression(
515
- RCR_BLOCK_VOLUME_OUTPUT_ID, _rcr_volume_stored_expr
516
- )