Skip to content

Commit bcd10e4

Browse files
committed
Add 0D circulation model example
1 parent f6a4863 commit bcd10e4

6 files changed

Lines changed: 230 additions & 60 deletions

File tree

.cspell_dict.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ cofac
2020
cpus
2121
Cristobal
2222
Culloch
23+
dcirc
2324
deviatoric
2425
dirichletbc
2526
dofmap

demo/0D_circulation.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# # 0D Circulation Model with Regazzoni2020
2+
# This script demonstrates how to set up and solve a 0D circulation model using the Regazzoni2020 model
3+
# from the `circulation` module in the `pulse` package.
4+
5+
from mpi4py import MPI
6+
import dolfinx
7+
import numpy as np
8+
import pulse
9+
import circulation
10+
import logging
11+
import matplotlib.pyplot as plt
12+
13+
logging.basicConfig(level=logging.INFO)
14+
logging.getLogger("scifem").setLevel(logging.WARNING)
15+
16+
comm = MPI.COMM_WORLD
17+
domain = dolfinx.mesh.create_unit_square(comm, 5, 5)
18+
circulation_model = circulation.regazzoni2020.Regazzoni2020()
19+
y0 = circulation_model.state.copy()
20+
dt = 0.001
21+
theta = 0.5
22+
23+
problem = pulse.problem.StaticProblem(
24+
model=pulse.CardiacModel(),
25+
geometry=pulse.Geometry(mesh=domain),
26+
circulation_model=circulation_model,
27+
parameters={"0D": True, "dt": dt, "theta": theta},
28+
)
29+
30+
time = np.arange(0, 10, dt)
31+
y = np.zeros((len(y0), len(time)))
32+
y[:, 0] = y0
33+
34+
for i, ti in enumerate(time[1:]):
35+
if i % 100 == 0:
36+
print(f"Solving for time {ti:.3f} s")
37+
problem.solve(ti)
38+
y[:, i + 1] = problem.circ.x.array[:]
39+
40+
state_names = circulation_model.state_names()
41+
var_names = circulation_model.var_names()
42+
vars = circulation_model.update_static_variables(time, y)
43+
44+
fig, ax = plt.subplots(2, 2, sharex="col", sharey="col", figsize=(12, 8))
45+
ax[0, 0].set_title("Pressures")
46+
ax[0, 0].plot(time, vars[var_names.index("p_LV"), :], label="p_LV")
47+
ax[0, 0].plot(time, vars[var_names.index("p_LA"), :], label="p_LA")
48+
ax[0, 0].plot(time, y[state_names.index("p_AR_SYS"), :], label="p_AR_SYS")
49+
ax[0, 0].plot(time, vars[var_names.index("p_RV"), :], label="p_RV")
50+
ax[0, 0].plot(time, vars[var_names.index("p_RA"), :], label="p_RA")
51+
ax[0, 0].legend()
52+
53+
ax[1, 0].set_title("Volumes")
54+
ax[1, 0].plot(time, y[state_names.index("V_LA"), :], label="V_LA")
55+
ax[1, 0].plot(time, y[state_names.index("V_LV"), :], label="V_LV")
56+
ax[1, 0].plot(time, y[state_names.index("V_RV"), :], label="V_RV")
57+
ax[1, 0].plot(time, y[state_names.index("V_RA"), :], label="V_RA")
58+
ax[1, 0].legend()
59+
60+
ax[0, 1].set_title("LV PV Loop")
61+
ax[0, 1].plot(y[state_names.index("V_LV"), :], vars[var_names.index("p_LV"), :])
62+
63+
ax[1, 1].set_title("RV PV Loop")
64+
ax[1, 1].plot(y[state_names.index("V_RV"), :], vars[var_names.index("p_RV"), :])
65+
66+
for axi in ax.flatten():
67+
axi.grid()
68+
fig.savefig("circulation_regazzoni2020.png")

demo/time_dependent_land_circ_lv.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,12 @@
120120
static = True
121121

122122

123+
124+
123125
if static:
124126
outdir = Path("lv_ellipsoid_time_dependent_circulation_static")
125127
bcs = pulse.BoundaryConditions(robin=(robin_epi, robin_base))
126-
problem = pulse.problem.StaticProblem(model=model, geometry=geometry, bcs=bcs, cavities=[cavity], parameters=parameters)
128+
problem = pulse.problem.StaticProblem(model=model, geometry=geometry, bcs=bcs, cavities=[cavity], parameters=parameters, circulation_model=circulation.regazzoni2020.Regazzoni2020)
127129
else:
128130
outdir = Path("lv_ellipsoid_time_dependent_circulation_dynamic")
129131
beta_epi = pulse.Variable(
@@ -351,7 +353,7 @@ def p_LV_func(V_LV, t):
351353
circulation_model_3D = circulation.regazzoni2020.Regazzoni2020(
352354
add_units=add_units,
353355
callback=callback,
354-
p_LV_func=p_LV_func,
356+
p_LV=p_LV_func,
355357
verbose=True,
356358
comm=comm,
357359
outdir=outdir,

src/pulse/active_model.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,5 +131,9 @@ class Passive(ActiveModel):
131131
def Fe(self, F: ufl.core.expr.Expr) -> ufl.core.expr.Expr:
132132
return F
133133

134-
def strain_energy(self, F: ufl.core.expr.Expr) -> ufl.core.expr.Expr:
135-
return dolfinx.fem.Constant(ufl.domain.extract_unique_domain(F), 0.0)
134+
def strain_energy(self, C: ufl.core.expr.Expr) -> ufl.core.expr.Expr:
135+
return dolfinx.fem.Constant(ufl.domain.extract_unique_domain(C), 0.0)
136+
137+
def S(self, C: ufl.core.expr.Expr) -> ufl.core.expr.Expr:
138+
dim = C.ufl_shape[0]
139+
return ufl.zero((dim, dim))

src/pulse/cardiac_model.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
import dolfinx
1111
import ufl
1212

13+
from .active_model import Passive
14+
from .compressibility import Compressible
15+
from .material_models import NeoHookean
1316
from .viscoelasticity import NoneViscoElasticity
1417

1518

@@ -43,9 +46,9 @@ def S(self, C_dot: ufl.core.expr.Expr) -> ufl.core.expr.Expr: ...
4346

4447
@dataclass(frozen=True, slots=True)
4548
class CardiacModel:
46-
material: HyperElasticMaterial
47-
active: ActiveModel
48-
compressibility: Compressibility
49+
material: HyperElasticMaterial = field(default_factory=NeoHookean)
50+
active: ActiveModel = field(default_factory=Passive)
51+
compressibility: Compressibility = field(default_factory=Compressible)
4952
viscoelasticity: ViscoElasticity = field(default_factory=NoneViscoElasticity)
5053

5154
def strain_energy(

0 commit comments

Comments
 (0)