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.
- {circulation-0.2.1/src/circulation.egg-info → circulation-0.3.0}/PKG-INFO +1 -1
- {circulation-0.2.1 → circulation-0.3.0}/pyproject.toml +2 -2
- circulation-0.3.0/src/circulation/__init__.py +11 -0
- circulation-0.3.0/src/circulation/windkessel.py +120 -0
- {circulation-0.2.1 → circulation-0.3.0/src/circulation.egg-info}/PKG-INFO +1 -1
- {circulation-0.2.1 → circulation-0.3.0}/src/circulation.egg-info/SOURCES.txt +1 -0
- {circulation-0.2.1 → circulation-0.3.0}/tests/test_models.py +12 -0
- circulation-0.2.1/src/circulation/__init__.py +0 -3
- {circulation-0.2.1 → circulation-0.3.0}/LICENSE +0 -0
- {circulation-0.2.1 → circulation-0.3.0}/README.md +0 -0
- {circulation-0.2.1 → circulation-0.3.0}/setup.cfg +0 -0
- {circulation-0.2.1 → circulation-0.3.0}/src/circulation/base.py +0 -0
- {circulation-0.2.1 → circulation-0.3.0}/src/circulation/bestel.py +0 -0
- {circulation-0.2.1 → circulation-0.3.0}/src/circulation/log.py +0 -0
- {circulation-0.2.1 → circulation-0.3.0}/src/circulation/regazzoni2020.py +0 -0
- {circulation-0.2.1 → circulation-0.3.0}/src/circulation/time_varying_elastance.py +0 -0
- {circulation-0.2.1 → circulation-0.3.0}/src/circulation/units.py +0 -0
- {circulation-0.2.1 → circulation-0.3.0}/src/circulation/zenker.py +0 -0
- {circulation-0.2.1 → circulation-0.3.0}/src/circulation.egg-info/dependency_links.txt +0 -0
- {circulation-0.2.1 → circulation-0.3.0}/src/circulation.egg-info/requires.txt +0 -0
- {circulation-0.2.1 → circulation-0.3.0}/src/circulation.egg-info/top_level.txt +0 -0
- {circulation-0.2.1 → circulation-0.3.0}/tests/test_bestel.py +0 -0
- {circulation-0.2.1 → circulation-0.3.0}/tests/test_initialization.py +0 -0
- {circulation-0.2.1 → circulation-0.3.0}/tests/test_regazzoni_steady_state.py +0 -0
|
@@ -3,7 +3,7 @@ requires = ["setuptools>=61.0.0", "wheel"]
|
|
|
3
3
|
|
|
4
4
|
[project]
|
|
5
5
|
name = "circulation"
|
|
6
|
-
version = "0.
|
|
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.
|
|
119
|
+
current_version = "0.3.0"
|
|
120
120
|
|
|
121
121
|
|
|
122
122
|
[[tool.bumpversion.files]]
|
|
@@ -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
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|