Problem 3: inflation and active contraction of a ventricle#
In the third problem we will solve the inflation and active contraction of a ventricle. First we import the necessary libraries
from pathlib import Path
from mpi4py import MPI
from dolfinx import log
import dolfinx
import numpy as np
import math
import cardiac_geometries
import fenicsx_pulse
Next we will create the geometry and save it in the folder called lv_ellipsoid
. Now we will also generate fibers and use a sixth order quadrature space for the fibers
geodir = Path("lv_ellipsoid-problem3")
comm = MPI.COMM_WORLD
if not geodir.exists():
comm.barrier()
cardiac_geometries.mesh.lv_ellipsoid(
outdir=geodir,
r_short_endo=7.0,
r_short_epi=10.0,
r_long_endo=17.0,
r_long_epi=20.0,
mu_apex_endo=-math.pi,
mu_base_endo=-math.acos(5 / 17),
mu_apex_epi=-math.pi,
mu_base_epi=-math.acos(5 / 20),
fiber_space="Quadrature_6",
create_fibers=True,
fiber_angle_epi=-90,
fiber_angle_endo=90,
comm=comm,
)
print("Done creating geometry.")
2025-03-05 19:42:31 [debug ] Convert file lv_ellipsoid-problem3/lv_ellipsoid.msh to dolfin
Info : Reading 'lv_ellipsoid-problem3/lv_ellipsoid.msh'...
Info : 53 entities
Info : 771 nodes
Info : 4026 elements
Info : Done reading 'lv_ellipsoid-problem3/lv_ellipsoid.msh'
Done creating geometry.
If the folder already exist, then we just load the geometry
geo = cardiac_geometries.geometry.Geometry.from_folder(
comm=comm,
folder=geodir,
)
Now, lets convert the geometry to a fenicsx_pulse.Geometry
object.
geometry = fenicsx_pulse.HeartGeometry.from_cardiac_geometries(geo, metadata={"quadrature_degree": 6})
The material model used in this benchmark is the Guccione
model.
material_params = {
"C": dolfinx.fem.Constant(geometry.mesh, dolfinx.default_scalar_type(2.0)),
"bf": dolfinx.fem.Constant(geometry.mesh, dolfinx.default_scalar_type(8.0)),
"bt": dolfinx.fem.Constant(geometry.mesh, dolfinx.default_scalar_type(2.0)),
"bfs": dolfinx.fem.Constant(geometry.mesh, dolfinx.default_scalar_type(4.0)),
}
material = fenicsx_pulse.Guccione(f0=geo.f0, s0=geo.s0, n0=geo.n0, **material_params)
Setting C to c_3 kPa
We use an active stress approach with 60% transverse active stress
Ta = dolfinx.fem.Constant(geometry.mesh, dolfinx.default_scalar_type(0.0))
active_model = fenicsx_pulse.ActiveStress(geo.f0, activation=fenicsx_pulse.Variable(Ta, "kPa"))
and the model should be incompressible
comp_model = fenicsx_pulse.Incompressible()
and assembles the CardiacModel
model = fenicsx_pulse.CardiacModel(
material=material,
active=active_model,
compressibility=comp_model,
decouple_deviatoric_volumetric=True,
)
We apply a traction in endocardium
traction = dolfinx.fem.Constant(geometry.mesh, dolfinx.default_scalar_type(0.0))
neumann = fenicsx_pulse.NeumannBC(
traction=fenicsx_pulse.Variable(traction, "kPa"),
marker=geo.markers["ENDO"][0],
)
and finally combine all the boundary conditions
bcs = fenicsx_pulse.BoundaryConditions(neumann=(neumann,))
and create a Mixed problem
problem = fenicsx_pulse.StaticProblem(
model=model, geometry=geometry, bcs=bcs, parameters={"base_bc": fenicsx_pulse.BaseBC.fixed},
)
# Now we can solve the problem
log.set_log_level(log.LogLevel.INFO)
problem.solve()
1
Now we will solve the problem for a range of active contraction and traction values
target_pressure = 15.0
target_Ta = 60.0
Let us just gradually increase the active contraction and traction with a linear ramp of 40 steps
N = 40
for Ta_value, traction_value in zip(np.linspace(0, target_Ta, N), np.linspace(0, target_pressure, N)):
print(f"Solving problem for traction={traction_value} and active contraction={Ta_value}")
Ta.value = Ta_value
traction.value = traction_value
problem.solve()
Solving problem for traction=0.0 and active contraction=0.0
Solving problem for traction=0.38461538461538464 and active contraction=1.5384615384615385
Solving problem for traction=0.7692307692307693 and active contraction=3.076923076923077
Solving problem for traction=1.153846153846154 and active contraction=4.615384615384616
Solving problem for traction=1.5384615384615385 and active contraction=6.153846153846154
Solving problem for traction=1.9230769230769231 and active contraction=7.6923076923076925
Solving problem for traction=2.307692307692308 and active contraction=9.230769230769232
Solving problem for traction=2.6923076923076925 and active contraction=10.76923076923077
Solving problem for traction=3.076923076923077 and active contraction=12.307692307692308
Solving problem for traction=3.4615384615384617 and active contraction=13.846153846153847
Solving problem for traction=3.8461538461538463 and active contraction=15.384615384615385
Solving problem for traction=4.230769230769231 and active contraction=16.923076923076923
Solving problem for traction=4.615384615384616 and active contraction=18.461538461538463
Solving problem for traction=5.0 and active contraction=20.0
Solving problem for traction=5.384615384615385 and active contraction=21.53846153846154
Solving problem for traction=5.769230769230769 and active contraction=23.076923076923077
Solving problem for traction=6.153846153846154 and active contraction=24.615384615384617
Solving problem for traction=6.538461538461539 and active contraction=26.153846153846157
Solving problem for traction=6.923076923076923 and active contraction=27.692307692307693
Solving problem for traction=7.307692307692308 and active contraction=29.230769230769234
Solving problem for traction=7.6923076923076925 and active contraction=30.76923076923077
Solving problem for traction=8.076923076923077 and active contraction=32.30769230769231
Solving problem for traction=8.461538461538462 and active contraction=33.84615384615385
Solving problem for traction=8.846153846153847 and active contraction=35.38461538461539
Solving problem for traction=9.230769230769232 and active contraction=36.92307692307693
Solving problem for traction=9.615384615384617 and active contraction=38.46153846153847
Solving problem for traction=10.0 and active contraction=40.0
Solving problem for traction=10.384615384615385 and active contraction=41.53846153846154
Solving problem for traction=10.76923076923077 and active contraction=43.07692307692308
Solving problem for traction=11.153846153846155 and active contraction=44.61538461538462
Solving problem for traction=11.538461538461538 and active contraction=46.15384615384615
Solving problem for traction=11.923076923076923 and active contraction=47.69230769230769
Solving problem for traction=12.307692307692308 and active contraction=49.23076923076923
Solving problem for traction=12.692307692307693 and active contraction=50.769230769230774
Solving problem for traction=13.076923076923078 and active contraction=52.307692307692314
Solving problem for traction=13.461538461538462 and active contraction=53.84615384615385
Solving problem for traction=13.846153846153847 and active contraction=55.38461538461539
Solving problem for traction=14.230769230769232 and active contraction=56.92307692307693
Solving problem for traction=14.615384615384617 and active contraction=58.46153846153847
Solving problem for traction=15.0 and active contraction=60.0
log.set_log_level(log.LogLevel.INFO)
Now save the displacement to a file that we can view in Paraview
with dolfinx.io.VTXWriter(geometry.mesh.comm, "problem3.bp", [problem.u], engine="BP4") as vtx:
vtx.write(0.0)
try:
import pyvista
except ImportError:
print("Pyvista is not installed")
else:
pyvista.start_xvfb()
V = dolfinx.fem.functionspace(geometry.mesh, ("Lagrange", 1, (geometry.mesh.geometry.dim,)))
uh = dolfinx.fem.Function(V)
uh.interpolate(problem.u)
# Create plotter and pyvista grid
p = pyvista.Plotter()
topology, cell_types, geometry = dolfinx.plot.vtk_mesh(V)
grid = pyvista.UnstructuredGrid(topology, cell_types, geometry)
# Attach vector values to grid and warp grid by vector
grid["u"] = uh.x.array.reshape((geometry.shape[0], 3))
actor_0 = p.add_mesh(grid, style="wireframe", color="k")
warped = grid.warp_by_vector("u", factor=1.5)
actor_1 = p.add_mesh(warped, show_edges=True)
p.show_axes()
if not pyvista.OFF_SCREEN:
p.show()
else:
figure_as_array = p.screenshot("problem2.png")
[2025-03-05 19:45:49.577] [info] Checking required entities per dimension
[2025-03-05 19:45:49.578] [info] Cell type: 0 dofmap: 2838x4
[2025-03-05 19:45:49.578] [info] Global index computation
[2025-03-05 19:45:49.578] [info] Got 1 index_maps
[2025-03-05 19:45:49.578] [info] Get global indices
error: XDG_RUNTIME_DIR is invalid or not set in the environment.
U = dolfinx.fem.Function(problem.u.function_space)
U.interpolate(lambda x: (x[0], x[1], x[2]))
endo_apex_coord = fenicsx_pulse.utils.evaluate_at_vertex_tag(U, geo.vfun, geo.markers["ENDOPT"][0])
u_endo_apex = fenicsx_pulse.utils.evaluate_at_vertex_tag(problem.u, geo.vfun, geo.markers["ENDOPT"][0])
endo_apex_pos = fenicsx_pulse.utils.gather_broadcast_array(geo.mesh.comm, endo_apex_coord + u_endo_apex)
print(f"\nGet longitudinal position of endocardial apex: {endo_apex_pos[0, 0]:4f} mm")
Get longitudinal position of endocardial apex: -11.932800 mm
epi_apex_coord = fenicsx_pulse.utils.evaluate_at_vertex_tag(U, geo.vfun, geo.markers["EPIPT"][0])
u_epi_apex = fenicsx_pulse.utils.evaluate_at_vertex_tag(problem.u, geo.vfun, geo.markers["EPIPT"][0])
epi_apex_pos = fenicsx_pulse.utils.gather_broadcast_array(geo.mesh.comm, epi_apex_coord + u_epi_apex)
print(f"\nGet longitudinal position of epicardial apex: {epi_apex_pos[0, 0]:4f} mm")
Get longitudinal position of epicardial apex: -15.140326 mm