circulation 0.2.1__tar.gz → 0.3.0__tar.gz

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 (24) hide show
  1. {circulation-0.2.1/src/circulation.egg-info → circulation-0.3.0}/PKG-INFO +1 -1
  2. {circulation-0.2.1 → circulation-0.3.0}/pyproject.toml +2 -2
  3. circulation-0.3.0/src/circulation/__init__.py +11 -0
  4. circulation-0.3.0/src/circulation/windkessel.py +120 -0
  5. {circulation-0.2.1 → circulation-0.3.0/src/circulation.egg-info}/PKG-INFO +1 -1
  6. {circulation-0.2.1 → circulation-0.3.0}/src/circulation.egg-info/SOURCES.txt +1 -0
  7. {circulation-0.2.1 → circulation-0.3.0}/tests/test_models.py +12 -0
  8. circulation-0.2.1/src/circulation/__init__.py +0 -3
  9. {circulation-0.2.1 → circulation-0.3.0}/LICENSE +0 -0
  10. {circulation-0.2.1 → circulation-0.3.0}/README.md +0 -0
  11. {circulation-0.2.1 → circulation-0.3.0}/setup.cfg +0 -0
  12. {circulation-0.2.1 → circulation-0.3.0}/src/circulation/base.py +0 -0
  13. {circulation-0.2.1 → circulation-0.3.0}/src/circulation/bestel.py +0 -0
  14. {circulation-0.2.1 → circulation-0.3.0}/src/circulation/log.py +0 -0
  15. {circulation-0.2.1 → circulation-0.3.0}/src/circulation/regazzoni2020.py +0 -0
  16. {circulation-0.2.1 → circulation-0.3.0}/src/circulation/time_varying_elastance.py +0 -0
  17. {circulation-0.2.1 → circulation-0.3.0}/src/circulation/units.py +0 -0
  18. {circulation-0.2.1 → circulation-0.3.0}/src/circulation/zenker.py +0 -0
  19. {circulation-0.2.1 → circulation-0.3.0}/src/circulation.egg-info/dependency_links.txt +0 -0
  20. {circulation-0.2.1 → circulation-0.3.0}/src/circulation.egg-info/requires.txt +0 -0
  21. {circulation-0.2.1 → circulation-0.3.0}/src/circulation.egg-info/top_level.txt +0 -0
  22. {circulation-0.2.1 → circulation-0.3.0}/tests/test_bestel.py +0 -0
  23. {circulation-0.2.1 → circulation-0.3.0}/tests/test_initialization.py +0 -0
  24. {circulation-0.2.1 → circulation-0.3.0}/tests/test_regazzoni_steady_state.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: circulation
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Summary: Closed loop blood circulation model in FEniCS
5
5
  Author-email: Henrik Finsberg <henriknf@simula.no>
6
6
  License-Expression: MIT
@@ -3,7 +3,7 @@ requires = ["setuptools>=61.0.0", "wheel"]
3
3
 
4
4
  [project]
5
5
  name = "circulation"
6
- version = "0.2.1"
6
+ version = "0.3.0"
7
7
  description = "Closed loop blood circulation model in FEniCS"
8
8
  authors = [{ name = "Henrik Finsberg", email = "henriknf@simula.no" }]
9
9
  license = "MIT"
@@ -116,7 +116,7 @@ tag = true
116
116
  sign_tags = false
117
117
  tag_name = "v{new_version}"
118
118
  tag_message = "Bump version: {current_version} → {new_version}"
119
- current_version = "0.2.1"
119
+ current_version = "0.3.0"
120
120
 
121
121
 
122
122
  [[tool.bumpversion.files]]
@@ -0,0 +1,11 @@
1
+ from . import base, log, regazzoni2020, zenker, units, time_varying_elastance, windkessel
2
+
3
+ __all__ = [
4
+ "base",
5
+ "log",
6
+ "regazzoni2020",
7
+ "zenker",
8
+ "units",
9
+ "time_varying_elastance",
10
+ "windkessel",
11
+ ]
@@ -0,0 +1,120 @@
1
+ from . import base
2
+ from . import units
3
+
4
+ mL = units.ureg("mL")
5
+ mmHg = units.ureg("mmHg")
6
+ s = units.ureg("s")
7
+
8
+
9
+ class ThreeElementWindkessel(base.CirculationModel):
10
+ """
11
+ A 0D model of the Left Ventricle coupled to a 3-Element Windkessel system.
12
+
13
+ The 3-Element Windkessel (WK3) models the systemic arterial tree using:
14
+ - R_c: Characteristic resistance (proximal aortic impedance)
15
+ - C: Arterial compliance (vessel elasticity)
16
+ - R_p: Peripheral resistance (microcirculation)
17
+ """
18
+
19
+ def __init__(self, parameters=None, p_LV_func=None, **kwargs):
20
+ super().__init__(parameters, **kwargs)
21
+
22
+ if p_LV_func is not None:
23
+ self.p_LV_func = p_LV_func
24
+ self._E_LV = lambda t: 1.0 # Dummy function, not used
25
+ else:
26
+ # Use default time-varying elastance model if no custom mechanics are provided
27
+ self._E_LV = self.time_varying_elastance(**self.parameters["chambers"]["LV"])
28
+ self.p_LV_func = lambda V, t: (
29
+ self._E_LV(t) * (V - self.parameters["chambers"]["LV"]["V0"])
30
+ )
31
+
32
+ self._initialize()
33
+
34
+ @property
35
+ def HR(self) -> float:
36
+ return self.parameters["HR"]
37
+
38
+ @staticmethod
39
+ def default_parameters():
40
+ return {
41
+ "HR": 1.25 * units.ureg("Hz"), # Heart rate in Hz (75 BPM)
42
+ "R_c": 0.05 * mmHg * s / mL, # Characteristic resistance (mmHg*s/mL)
43
+ "R_p": 1.05 * mmHg * s / mL, # Peripheral resistance (mmHg*s/mL)
44
+ "C": 1.3 * mL / mmHg, # Arterial compliance (mL/mmHg)
45
+ "P_venous": 8.0 * mmHg, # Preload / Left Atrial Pressure (mmHg)
46
+ "R_mitral": 0.01 * mmHg * s / mL, # Mitral valve resistance
47
+ "R_aortic": 0.01 * mmHg * s / mL, # Aortic valve resistance
48
+ "chambers": {
49
+ "LV": {
50
+ "EA": 2.5 * mmHg / mL, # Max elastance (mmHg/mL)
51
+ "EB": 0.08 * mmHg / mL, # Min elastance (mmHg/mL)
52
+ "TC": 0.25 * s, # Contraction duration (s)
53
+ "TR": 0.15 * s, # Relaxation duration (s)
54
+ "tC": 0.0 * s, # Time of contraction onset (s)
55
+ "V0": 15.0 * mL, # Unstressed volume (mL)
56
+ }
57
+ },
58
+ }
59
+
60
+ @staticmethod
61
+ def default_initial_conditions() -> dict:
62
+ return {
63
+ "V_LV": 140.0 * mL, # Initial left ventricular volume
64
+ "p_c": 80.0 * mmHg, # Pressure in the arterial compliance capacitor
65
+ }
66
+
67
+ @staticmethod
68
+ def state_names() -> list:
69
+ return ["V_LV", "p_c"]
70
+
71
+ @staticmethod
72
+ def var_names() -> list:
73
+ return ["p_LV", "p_ao", "Q_in", "Q_out", "Q_p"]
74
+
75
+ def update_static_variables(self, t, y):
76
+ V_LV = y[0]
77
+ p_c = y[1]
78
+
79
+ p_LV = self.p_LV_func(V_LV, t)
80
+
81
+ P_venous = self.parameters["P_venous"]
82
+ R_mitral = self.parameters["R_mitral"]
83
+ R_aortic = self.parameters["R_aortic"]
84
+ R_c = self.parameters["R_c"]
85
+ R_p = self.parameters["R_p"]
86
+
87
+ # 1. Mitral Flow (Diastolic Filling)
88
+ Q_in = max(0.0, (P_venous - p_LV) / R_mitral)
89
+
90
+ # 2. Aortic Flow (Systolic Ejection)
91
+ # In a WK3 model, aortic pressure P_ao = p_c + Q_out * R_c
92
+ # If the valve is open (p_LV > p_ao), we substitute P_ao and solve for Q_out
93
+ if p_LV > p_c:
94
+ Q_out = (p_LV - p_c) / (R_aortic + R_c)
95
+ else:
96
+ Q_out = 0.0
97
+
98
+ # 3. Pressures and Peripheral Flow
99
+ p_ao = p_c + Q_out * R_c
100
+ Q_p = p_c / R_p # Discharge to venous pool (assumed 0 mmHg pressure drop reference)
101
+
102
+ var = self._get_var(t)
103
+ var[0] = p_LV
104
+ var[1] = p_ao
105
+ var[2] = Q_in
106
+ var[3] = Q_out
107
+ var[4] = Q_p
108
+ return var
109
+
110
+ def rhs(self, t, y):
111
+ var = self.update_static_variables(t, y)
112
+ Q_in = var[2]
113
+ Q_out = var[3]
114
+ Q_p = var[4]
115
+ C = self.parameters["C"]
116
+
117
+ self.dy[0] = Q_in - Q_out
118
+ self.dy[1] = (Q_out - Q_p) / C
119
+
120
+ return self.dy
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: circulation
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Summary: Closed loop blood circulation model in FEniCS
5
5
  Author-email: Henrik Finsberg <henriknf@simula.no>
6
6
  License-Expression: MIT
@@ -8,6 +8,7 @@ src/circulation/log.py
8
8
  src/circulation/regazzoni2020.py
9
9
  src/circulation/time_varying_elastance.py
10
10
  src/circulation/units.py
11
+ src/circulation/windkessel.py
11
12
  src/circulation/zenker.py
12
13
  src/circulation.egg-info/PKG-INFO
13
14
  src/circulation.egg-info/SOURCES.txt
@@ -24,3 +24,15 @@ def test_Regazzoni2020():
24
24
  for k, v in model.default_initial_conditions().items():
25
25
  assert k in results
26
26
  assert results[k][0] == v.magnitude
27
+
28
+
29
+ def test_ThreeElementWindkessel():
30
+ model = circulation.windkessel.ThreeElementWindkessel()
31
+ results = model.solve(T=1.0, dt=1e-3, dt_eval=0.1)
32
+
33
+ for k, v in results.items():
34
+ assert len(v) == len(results["time"]), k
35
+
36
+ for k, v in model.default_initial_conditions().items():
37
+ assert k in results
38
+ assert results[k][0] == v.magnitude
@@ -1,3 +0,0 @@
1
- from . import base, log, regazzoni2020, zenker, units, time_varying_elastance
2
-
3
- __all__ = ["base", "log", "regazzoni2020", "zenker", "units", "time_varying_elastance"]
File without changes
File without changes
File without changes