Skip to content

Commit 1a1adc3

Browse files
authored
Merge pull request #38 from finsberg/benchmark-problem2
Benchmark problem2
2 parents 3e435cf + 9e83dd4 commit 1a1adc3

File tree

4 files changed

+393
-4
lines changed

4 files changed

+393
-4
lines changed

_toc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ parts:
1010
- file: "demo/benchmark/README"
1111
sections:
1212
- file: "demo/benchmark/problem1"
13+
- file: "demo/benchmark/problem2"
14+
- file: "demo/benchmark/problem3"
1315
- caption: Community
1416
chapters:
1517
- file: "CONTRIBUTING"

demo/benchmark/problem1.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,11 @@
4949

5050
active_model = fenicsx_pulse.active_model.Passive()
5151

52-
# and the model should be incompressble
52+
# and the model should be incompressible
5353

5454
comp_model = fenicsx_pulse.Incompressible()
5555

56-
# We can now assemble the `CaridacModel`
56+
# We can now assemble the `CardiacModel`
5757
#
5858

5959
model = fenicsx_pulse.CardiacModel(
@@ -120,8 +120,9 @@ def dirichlet_bc(
120120
cell_candidates = dolfinx.geometry.compute_collisions_points(tree, point)
121121
cell = dolfinx.geometry.compute_colliding_cells(mesh, cell_candidates, point)
122122
uz = mesh.comm.allreduce(u.eval(point, cell.array)[2], op=MPI.MAX)
123-
print(f"Get z-position of point {point}: {point[2] + uz:.2f} mm")
124-
123+
result = point[2] + uz
124+
print(f"Get z-position of point {point}: {result:.2f} mm")
125+
assert np.isclose(result, 4.17, atol=1.0e-2)
125126
# Finally, let us plot the deflected beam using pyvista
126127

127128
try:

demo/benchmark/problem2.py

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
# # Problem 2: Inflation of a ventricle
2+
# In the second problem we will solve the inflation of a ventricle. First we import the necessary libraries
3+
#
4+
5+
from pathlib import Path
6+
from mpi4py import MPI
7+
from dolfinx import log
8+
import dolfinx
9+
import numpy as np
10+
import math
11+
import cardiac_geometries
12+
import fenicsx_pulse
13+
14+
# Next we will create the geometry and save it in the folder called `lv_ellipsoid`.
15+
16+
geodir = Path("lv_ellipsoid")
17+
if not geodir.exists():
18+
cardiac_geometries.mesh.lv_ellipsoid(
19+
outdir=geodir,
20+
r_short_endo=7.0,
21+
r_short_epi=10.0,
22+
r_long_endo=17.0,
23+
r_long_epi=20.0,
24+
mu_apex_endo = -math.pi,
25+
mu_base_endo = -math.acos(5 / 17),
26+
mu_apex_epi = -math.pi,
27+
mu_base_epi = -math.acos(5 / 20),
28+
)
29+
30+
# If the folder already exist, then we just load the geometry
31+
32+
geo = cardiac_geometries.geometry.Geometry.from_folder(
33+
comm=MPI.COMM_WORLD,
34+
folder=geodir,
35+
)
36+
37+
# Now, lets convert the geometry to a `fenicsx_pulse.Geometry` object.
38+
39+
geometry = fenicsx_pulse.Geometry.from_cardiac_geometries(geo, metadata={"quadrature_degree": 4})
40+
41+
42+
# The material model used in this benchmark is the {py:class}`Guccione <fenicsx_pulse.material_models.guccione.Guccione>` model.
43+
44+
material_params = {
45+
"C": dolfinx.fem.Constant(geometry.mesh, dolfinx.default_scalar_type(10.0)),
46+
"bf": dolfinx.fem.Constant(geometry.mesh, dolfinx.default_scalar_type(1.0)),
47+
"bt": dolfinx.fem.Constant(geometry.mesh, dolfinx.default_scalar_type(1.0)),
48+
"bfs": dolfinx.fem.Constant(geometry.mesh, dolfinx.default_scalar_type(1.0)),
49+
}
50+
material = fenicsx_pulse.Guccione(**material_params)
51+
52+
53+
# There are now active contraction, so we choose a pure passive model
54+
55+
active_model = fenicsx_pulse.active_model.Passive()
56+
57+
# and the model should be incompressible
58+
59+
comp_model = fenicsx_pulse.Incompressible()
60+
61+
62+
# and assembles the `CardiacModel`
63+
64+
model = fenicsx_pulse.CardiacModel(
65+
material=material,
66+
active=active_model,
67+
compressibility=comp_model,
68+
)
69+
70+
71+
# Next we need to apply some boundary conditions. For the Dirichlet BC we can supply a function that takes as input the state space and returns a list of `DirichletBC`. We will fix the base in all directions. Note that the displacement is in the first subspace since we use an incompressible model. The hydrostatic pressure is in the second subspace
72+
73+
def dirichlet_bc(
74+
state_space: dolfinx.fem.FunctionSpace,
75+
) -> list[dolfinx.fem.bcs.DirichletBC]:
76+
V, _ = state_space.sub(0).collapse()
77+
facets = geometry.facet_tags.find(
78+
geo.markers["BASE"][0],
79+
) # Specify the marker used on the boundary
80+
geometry.mesh.topology.create_connectivity(
81+
geometry.mesh.topology.dim - 1,
82+
geometry.mesh.topology.dim,
83+
)
84+
dofs = dolfinx.fem.locate_dofs_topological((state_space.sub(0), V), 2, facets)
85+
u_fixed = dolfinx.fem.Function(V)
86+
u_fixed.x.array[:] = 0.0
87+
return [dolfinx.fem.dirichletbc(u_fixed, dofs, state_space.sub(0))]
88+
89+
90+
# We apply a traction in endocardium
91+
92+
traction = dolfinx.fem.Constant(geometry.mesh, dolfinx.default_scalar_type(0.0))
93+
neumann = fenicsx_pulse.NeumannBC(traction=traction, marker=geo.markers["ENDO"][0])
94+
95+
# and finally combine all the boundary conditions
96+
97+
bcs = fenicsx_pulse.BoundaryConditions(dirichlet=(dirichlet_bc,), neumann=(neumann,))
98+
99+
# and create a Mixed problem
100+
101+
problem = fenicsx_pulse.MechanicsProblemMixed(model=model, geometry=geometry, bcs=bcs)
102+
103+
# Now we can solve the problem
104+
log.set_log_level(log.LogLevel.INFO)
105+
106+
problem.solve()
107+
108+
# Now step up the pressure to 10 kPa starting with an increment of 1 kPa
109+
target_value = 10.0
110+
incr = 1.0
111+
112+
# Here we use a continuation strategy to speed up the convergence
113+
114+
use_continuation = True
115+
116+
old_states = [problem.state.copy()]
117+
old_tractions = [traction.value.copy()]
118+
119+
while traction.value < target_value:
120+
value = min(traction.value + incr, target_value)
121+
print(f"Solving problem for traction={value}")
122+
123+
if use_continuation and len(old_tractions) > 1:
124+
d = (value - old_tractions[-2]) / (old_tractions[-1] - old_tractions[-2])
125+
problem.state.x.array[:] = (1 - d) * old_states[-2].x.array + d * old_states[-1].x.array
126+
127+
traction.value = value
128+
129+
try:
130+
nit, converged = problem.solve()
131+
except RuntimeError:
132+
133+
# Reset state and half the increment
134+
traction.value = old_tractions[-1]
135+
problem.state.x.array[:] = old_states[-1].x.array
136+
incr *= 0.5
137+
else:
138+
if nit < 3:
139+
# Increase increment
140+
incr *= 1.5
141+
old_states.append(problem.state.copy())
142+
old_tractions.append(traction.value.copy())
143+
144+
log.set_log_level(log.LogLevel.INFO)
145+
146+
# Now save the displacement to a file that we can view in Paraview
147+
148+
u = problem.state.sub(0).collapse()
149+
with dolfinx.io.VTXWriter(geometry.mesh.comm, "problem2.bp", [u], engine="BP4") as vtx:
150+
vtx.write(0.0)
151+
152+
153+
try:
154+
import pyvista
155+
except ImportError:
156+
print("Pyvista is not installed")
157+
else:
158+
pyvista.start_xvfb()
159+
V = dolfinx.fem.functionspace(geometry.mesh, ("Lagrange", 1, (geometry.mesh.geometry.dim,)))
160+
uh = dolfinx.fem.Function(V)
161+
uh.interpolate(u)
162+
# Create plotter and pyvista grid
163+
p = pyvista.Plotter()
164+
topology, cell_types, geometry = dolfinx.plot.vtk_mesh(V)
165+
grid = pyvista.UnstructuredGrid(topology, cell_types, geometry)
166+
167+
# Attach vector values to grid and warp grid by vector
168+
grid["u"] = uh.x.array.reshape((geometry.shape[0], 3))
169+
actor_0 = p.add_mesh(grid, style="wireframe", color="k")
170+
warped = grid.warp_by_vector("u", factor=1.5)
171+
actor_1 = p.add_mesh(warped, show_edges=True)
172+
p.show_axes()
173+
if not pyvista.OFF_SCREEN:
174+
p.show()
175+
else:
176+
figure_as_array = p.screenshot("problem2.png")
177+
178+
179+
# FIXME: Need to figure out how to evaluate the displacement at the apex
180+
# geometry.mesh.topology.create_connectivity(0, geometry.mesh.topology.dim)
181+
# apex_endo = geo.vfun.find(geo.markers["ENDOPT"][0])
182+
# endo_apex_coord = geo.mesh.geometry.x[apex_endo]
183+
184+
# dofs_endo_apex = dolfinx.fem.locate_dofs_topological(problem.state_space.sub(0), 0, apex_endo)
185+
# u_endo_apex = u.x.array[dofs_endo_apex]
186+
187+
# endo_apex_pos = endo_apex_coord + u_endo_apex
188+
189+
# print(f"\nGet longitudinal position of endocardial apex: {endo_apex_pos[0, 0]:4f} mm")
190+
191+
# apex_epi = geo.vfun.find(geo.markers["EPIPT"][0])
192+
# epi_apex_coord = geo.mesh.geometry.x[apex_epi]
193+
194+
# geometry.mesh.topology.create_connectivity(0, geometry.mesh.topology.dim)
195+
# dofs_epi_apex = dolfinx.fem.locate_dofs_topological(problem.state_space.sub(0), 0, apex_epi)
196+
# u_epi_apex = u.x.array[dofs_epi_apex]
197+
198+
# epi_apex_pos = epi_apex_coord + u_epi_apex
199+
200+
# print(f"\nGet longitudinal position of epicardial apex: {epi_apex_pos[0, 0]:.4f} mm")

0 commit comments

Comments
 (0)