Skip to content

Commit 3ddf547

Browse files
committed
Start documenting demo
1 parent 583360a commit 3ddf547

File tree

4 files changed

+142
-55
lines changed

4 files changed

+142
-55
lines changed

_config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Book settings
22
# Learn more at https://jupyterbook.org/customize/config.html
33

4-
title: scifem
4+
title: fenicsx_ii
55
author: Jørgen S. Dokken
66
copyright: "2025"
77
only_build_toc_files: true

_toc.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ format: jb-book
55
root: README
66
parts:
77

8+
- caption: Examples
9+
chapters:
10+
- file: "demos/coupled_poisson_solver"
11+
812
- caption: Reference
913
chapters:
1014
- file: "docs/api.rst"

demos/coupled_poisson_solver.py

Lines changed: 125 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,66 @@
11
# # A coupled 3D-1D manufactured solution
22
#
3-
# This example is based on the manufactured solution from {cite}`masri2024coupled3d1d`.
4-
3+
# This example is based on the strong formulation and manufactured solution
4+
# from {cite}`3D1Dmasri-masri2024coupled3d1d`.
5+
# We consider a 3D domain $\Omega$ with a 1D inclusion $\Lambda$.
6+
# We assume that $\Lambda$ can be parameterised as a curve $\lambda(s)$, $s\in(0,L)$.
7+
# Next, we define a generalized cylinder $B_{\Lambda,R(s)}$ with centerline $\Lambda$ and radius $R$.
8+
# A cross section of the cylinder at $s$ is defined as the circle $\Theta_{R}(s)$ with radius
9+
# $R: [0, L] \to \mathbb{R}^+$, area $A(s)$ and perimeter $P(s)$.
10+
# The boundary of $B_\Lambda$ is denoted $\Gamma$.
11+
# For a function $u\in V(\Omega)$, we define the restriction operator
12+
# $\Pi_R(s): V(\Omega) \to L^2(\Lambda)$ as
13+
#
14+
# $$
15+
# \Pi_R(u)(s) = \frac{1}{|P(s)|} \int_{\partial \Theta_R(s)} u~\mathrm{d}\gamma.
16+
# $$
17+
#
18+
# ## Mesh generation
19+
# We start by generating the two domains that we will consider in this demo.
20+
# ```{admonition} Strict inclusion
21+
# :class: dropdown
22+
# Note that $\Gamma$ should be strictly included in $\Omega$.
23+
# Additionally, we assume that $B_\Lambda \subset \Omega$.
24+
# ```
25+
# We start by defining a cube that spans $[-0.5,-0.5,-0.5]\times[0.5,0.5,0.5]$
26+
27+
# +
528
from mpi4py import MPI
6-
7-
import basix.ufl
829
import dolfinx
930
import numpy as np
31+
import basix.ufl
1032
import ufl
1133

12-
from fenicsx_ii import Average, Circle, LinearProblem
13-
14-
# We create a 3D mesh (a cube)
15-
M = 64
16-
N = M
17-
volume = dolfinx.mesh.create_box(
18-
MPI.COMM_WORLD,
34+
M = 32 # Number of elements in each spatial direction in the box
35+
N = M # Number of elements in the line
36+
comm = MPI.COMM_WORLD
37+
omega = dolfinx.mesh.create_box(
38+
comm,
1939
[[-0.5, -0.5, -0.5], [0.5, 0.5, 0.5]],
2040
[M, M, M],
2141
cell_type=dolfinx.mesh.CellType.tetrahedron,
2242
)
23-
volume.name = "volume"
24-
25-
26-
# We create a 1D mesh (a line), which is not embedded in the 3D mesh
43+
omega.name = "volume"
44+
# -
45+
46+
# Next we create a line that spans $[0,0,-0.5]\times[0,0,0.5]$.
47+
# Note that the discretized lines does not have to conform with the
48+
# 3D domain.
49+
# ```{admonition} Line embedded in 3D
50+
# Note that the geometrical dimension of the line should be 3. Therefore,
51+
# the built in mesh generators {py:func}`dolfinx.mesh.create_interval`
52+
# and {py:func}`dolfinx.mesh.create_unit_interval` does not work as
53+
# they use geometrical dimension 1.
54+
# ```
55+
56+
# +
2757
l_min = -0.5
2858
l_max = 0.5
29-
if MPI.COMM_WORLD.rank == 0:
59+
if comm.rank == 0:
3060
nodes = np.zeros((N, 3), dtype=np.float64)
3161
nodes[:, 0] = np.full(nodes.shape[0], 0)
3262
nodes[:, 1] = np.full(nodes.shape[0], 0)
33-
nodes[:, 2] = np.linspace(l_min, l_max, nodes.shape[0])[::-1]
63+
nodes[:, 2] = np.linspace(l_min, l_max, nodes.shape[0])
3464
connectivity = np.repeat(np.arange(nodes.shape[0]), 2)[1:-1].reshape(
3565
nodes.shape[0] - 1, 2
3666
)
@@ -40,22 +70,55 @@
4070
c_el = ufl.Mesh(
4171
basix.ufl.element("Lagrange", basix.CellType.interval, 1, shape=(nodes.shape[1],))
4272
)
43-
line_mesh = dolfinx.mesh.create_mesh(
44-
MPI.COMM_WORLD,
73+
lmbda = dolfinx.mesh.create_mesh(
74+
comm,
4575
x=nodes,
4676
cells=connectivity,
4777
e=c_el,
4878
partitioner=dolfinx.mesh.create_cell_partitioner(
4979
dolfinx.mesh.GhostMode.shared_facet
5080
),
5181
)
82+
# -
83+
84+
# ## Variational formulation
85+
#
86+
# We consider the following coupled 3D-1D problem, which is used in
87+
# {cite}`3D1Dmasri-masri2024coupled3d1d` and stems from
88+
# {cite}`3D1Dmasri-laurino20193d1d`:
89+
# Find $u\in V(\Omega)$, $p\in Q(\Lambda)$ such that
90+
#
91+
# $$
92+
# \begin{align}
93+
# - \nabla \cdot (\nabla u) + \xi (\Pi_R(u) - p))\delta_\Gamma &= f && \text{in } \Omega, \\
94+
# - d_s(A d_s p) + P \xi (p - \Pi(u)) &= A \hat f && \text{in } \Lambda, \\
95+
# u&=g &&\text{on } \partial\Omega,\\
96+
# A d_s p &=0 && \text{at } s\in\{0, 1\}.
97+
# \end{align}
98+
# $$
99+
#
100+
# with the variational formulation
101+
#
102+
# $$
103+
# \begin{align*}
104+
# \int_\Omega \nabla u \cdot \nabla v~\mathrm{d}x
105+
# + \int_\Gamma P\xi (\Pi_R(u) - p)\Pi_R(v)~\mathrm{d}s
106+
# &= \int_\Omega f\cdot v~\mathrm{d}x\\
107+
# \int_\Gamma d_s p \cdot d_s q~\mathrm{d}s
108+
# + \int_\Gamma P\xi (p - \Pi_R(u))q~\mathrm{d}s
109+
# &= \int_\Gamma A\hat f\cdot q~\mathrm{d}s
110+
# \end{align*}
111+
# $$
112+
113+
from fenicsx_ii import Average, Circle, LinearProblem
114+
52115

53116

54117
# We define the appropriate function spaces on each mesh
55118

56119
degree = 1
57-
V = dolfinx.fem.functionspace(volume, ("Lagrange", degree))
58-
Q = dolfinx.fem.functionspace(line_mesh, ("Lagrange", degree))
120+
V = dolfinx.fem.functionspace(omega, ("Lagrange", degree))
121+
Q = dolfinx.fem.functionspace(lmbda, ("Lagrange", degree))
59122

60123
# We define a mixed function space, to automate block extraction of the system
61124

@@ -69,21 +132,21 @@
69132

70133
R = 0.05
71134
q_degree = 20
72-
restriction_trial = Circle(line_mesh, R, degree=q_degree)
73-
restriction_test = Circle(line_mesh, R, degree=q_degree)
135+
restriction_trial = Circle(lmbda, R, degree=q_degree)
136+
restriction_test = Circle(lmbda, R, degree=q_degree)
74137

75138
# Next, we define the integration measures on each mesh
76139

77-
dx_3D = ufl.Measure("dx", domain=volume)
78-
dx_1D = ufl.Measure("dx", domain=line_mesh)
140+
dx_3D = ufl.Measure("dx", domain=omega)
141+
dx_1D = ufl.Measure("dx", domain=lmbda)
79142

80143
# We can define the intermediate space that we interpolate the
81144
# 3D arguments into
82145

83146
q_el = basix.ufl.quadrature_element(
84-
line_mesh.basix_cell(), value_shape=(), degree=q_degree
147+
lmbda.basix_cell(), value_shape=(), degree=q_degree
85148
)
86-
Rs = dolfinx.fem.functionspace(line_mesh, q_el)
149+
Rs = dolfinx.fem.functionspace(lmbda, q_el)
87150

88151

89152
# Next, we define the averaging operator of each of the test and trial functions
@@ -93,7 +156,7 @@
93156

94157
# We define the various constants used in the variational formulation
95158

96-
Alpha1 = dolfinx.fem.Constant(volume, 1.0)
159+
Alpha1 = dolfinx.fem.Constant(omega, 1.0)
97160
A = ufl.pi * R**2
98161

99162
# and the variational form itself
@@ -119,38 +182,38 @@ def x(mesh: dolfinx.mesh.Mesh):
119182
P = 2 * ufl.pi * R
120183

121184
a = Alpha1 * ufl.inner(ufl.grad(u), ufl.grad(v)) * dx_3D
122-
a += P * xi(line_mesh) * ufl.inner(avg_u - p, avg_v) * dx_1D
185+
a += P * xi(lmbda) * ufl.inner(avg_u - p, avg_v) * dx_1D
123186
a += A * ufl.inner(ufl.grad(p), ufl.grad(q)) * dx_1D
124-
a += P * xi(line_mesh) * ufl.inner(p - avg_u, q) * dx_1D
187+
a += P * xi(lmbda) * ufl.inner(p - avg_u, q) * dx_1D
125188

126-
vol_rate = xi(volume) / (xi(volume) + 1)
189+
vol_rate = xi(omega) / (xi(omega) + 1)
127190

128-
u_inside = vol_rate * u_line(x(volume))
129-
r = ufl.sqrt(x(volume)[0] ** 2 + x(volume)[1] ** 2)
130-
u_outside = vol_rate * (1 - R * ufl.ln(r / R)) * u_line(x(volume))
191+
u_inside = vol_rate * u_line(x(omega))
192+
r = ufl.sqrt(x(omega)[0] ** 2 + x(omega)[1] ** 2)
193+
u_outside = vol_rate * (1 - R * ufl.ln(r / R)) * u_line(x(omega))
131194
u_ex = ufl.conditional(r < R, u_inside, u_outside)
132195

133-
p_ex = ufl.sin(ufl.pi * x(line_mesh)[2]) + 2
196+
p_ex = ufl.sin(ufl.pi * x(lmbda)[2]) + 2
134197

135-
f_vol_in = vol_rate * ufl.pi**2 * ufl.sin(ufl.pi * x(volume)[2])
198+
f_vol_in = vol_rate * ufl.pi**2 * ufl.sin(ufl.pi * x(omega)[2])
136199
f_vol_out = (
137-
vol_rate * (1 - R * ufl.ln(r / R)) * ufl.pi**2 * ufl.sin(ufl.pi * x(volume)[2])
200+
vol_rate * (1 - R * ufl.ln(r / R)) * ufl.pi**2 * ufl.sin(ufl.pi * x(omega)[2])
138201
)
139202
f_vol = ufl.conditional(r < R, f_vol_in, f_vol_out)
140203
# - ufl.div(ufl.grad(u_ex))
141204

142-
line_rate = xi(line_mesh) / (xi(line_mesh) + 1)
205+
line_rate = xi(lmbda) / (xi(lmbda) + 1)
143206
# f_line = - A*ufl.div(ufl.grad(p_ex)) + P * line_rate * p_ex
144-
f_line = A * ufl.sin(ufl.pi * x(line_mesh)[2]) * ufl.pi**2 + P * line_rate * p_ex
207+
f_line = A * ufl.sin(ufl.pi * x(lmbda)[2]) * ufl.pi**2 + P * line_rate * p_ex
145208

146209
L = f_vol * v * dx_3D
147210
L += f_line * q * dx_1D
148211

149212
# Exerior boundary conditions
150-
volume.topology.create_connectivity(volume.topology.dim - 1, volume.topology.dim)
151-
exterior_facets = dolfinx.mesh.exterior_facet_indices(volume.topology)
213+
omega.topology.create_connectivity(omega.topology.dim - 1, omega.topology.dim)
214+
exterior_facets = dolfinx.mesh.exterior_facet_indices(omega.topology)
152215
exterior_dofs = dolfinx.fem.locate_dofs_topological(
153-
V, volume.topology.dim - 1, exterior_facets
216+
V, omega.topology.dim - 1, exterior_facets
154217
)
155218
bc_expr = dolfinx.fem.Expression(u_ex, V.element.interpolation_points)
156219
u_bc = dolfinx.fem.Function(V)
@@ -180,12 +243,12 @@ def x(mesh: dolfinx.mesh.Mesh):
180243
# We move the solution to a Function and save to file
181244

182245

183-
with dolfinx.io.VTXWriter(volume.comm, "u_3D_solver.bp", [uh]) as vtx:
246+
with dolfinx.io.VTXWriter(omega.comm, "u_3D_solver.bp", [uh]) as vtx:
184247
vtx.write(0.0)
185-
with dolfinx.io.VTXWriter(line_mesh.comm, "p_1D_solver.bp", [ph]) as vtx:
248+
with dolfinx.io.VTXWriter(lmbda.comm, "p_1D_solver.bp", [ph]) as vtx:
186249
vtx.write(0.0)
187250

188-
with dolfinx.io.VTXWriter(volume.comm, "u_bc.bp", [u_bc]) as vtx:
251+
with dolfinx.io.VTXWriter(omega.comm, "u_bc.bp", [u_bc]) as vtx:
189252
vtx.write(0.0)
190253

191254

@@ -217,24 +280,24 @@ def H1(f: ufl.core.expr.Expr, dx: ufl.Measure):
217280
H1_error_u = H1(uh - u_ex, dx_3D)
218281
H1_error_p = H1(ph - p_ex, dx_1D)
219282

220-
h_vol = volume.comm.allreduce(
283+
h_vol = omega.comm.allreduce(
221284
np.max(
222-
volume.h(
223-
volume.topology.dim,
285+
omega.h(
286+
omega.topology.dim,
224287
np.arange(
225-
volume.topology.index_map(volume.topology.dim).size_local,
288+
omega.topology.index_map(omega.topology.dim).size_local,
226289
dtype=np.int32,
227290
),
228291
)
229292
),
230293
op=MPI.MAX,
231294
)
232-
h_line = line_mesh.comm.allreduce(
295+
h_line = lmbda.comm.allreduce(
233296
np.max(
234-
line_mesh.h(
235-
line_mesh.topology.dim,
297+
lmbda.h(
298+
lmbda.topology.dim,
236299
np.arange(
237-
line_mesh.topology.index_map(line_mesh.topology.dim).size_local,
300+
lmbda.topology.index_map(lmbda.topology.dim).size_local,
238301
dtype=np.int32,
239302
),
240303
)
@@ -248,3 +311,12 @@ def H1(f: ufl.core.expr.Expr, dx: ufl.Measure):
248311
print(f"L2-error p: {L2_error_p:.6e}")
249312
print(f"H1-error u: {H1_error_u:.6e}")
250313
print(f"H1-error p: {H1_error_p:.6e}")
314+
315+
316+
## References
317+
318+
# ```{bibliography}
319+
# :filter: cited
320+
# :labelprefix:
321+
# :keyprefix: 3D1Dmasri-
322+
# ```

references.bib

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ @article{masri2024coupled3d1d
1212

1313

1414
@article{dangelo20083d1d,
15-
author = {D'ANGELO, C. and QUARTERONI, A.},
15+
author = {D'Angelo, C. and Quarteroni, A.},
1616
title = {ON THE COUPLING OF 1D AND 3D DIFFUSION-REACTION EQUATIONS: APPLICATION TO TISSUE PERFUSION PROBLEMS},
1717
journal = {Mathematical Models and Methods in Applied Sciences},
1818
volume = {18},
@@ -39,5 +39,16 @@ @InProceedings{kutcha2021trace
3939
}
4040

4141

42+
@article{laurino20193d1d,
43+
author = {Laurino, Federica and Zunino, Paolo},
44+
title = {Derivation and analysis of coupled {PDEs} on manifolds with high dimensionality gap arising from topological model reduction},
45+
DOI= "10.1051/m2an/2019042",
46+
journal = {ESAIM: M2AN},
47+
year = 2019,
48+
volume = 53,
49+
number = 6,
50+
pages = "2047-2080",
51+
}
52+
4253

4354

0 commit comments

Comments
 (0)