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+ # +
528from mpi4py import MPI
6-
7- import basix .ufl
829import dolfinx
930import numpy as np
31+ import basix .ufl
1032import 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+ # +
2757l_min = - 0.5
2858l_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 )
4070c_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
56119degree = 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
69132
70133R = 0.05
71134q_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
83146q_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
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 )
97160A = ufl .pi * R ** 2
98161
99162# and the variational form itself
@@ -119,38 +182,38 @@ def x(mesh: dolfinx.mesh.Mesh):
119182P = 2 * ufl .pi * R
120183
121184a = 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
123186a += 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 ))
131194u_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 ])
136199f_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)
139202f_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
146209L = f_vol * v * dx_3D
147210L += 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 )
152215exterior_dofs = dolfinx .fem .locate_dofs_topological (
153- V , volume .topology .dim - 1 , exterior_facets
216+ V , omega .topology .dim - 1 , exterior_facets
154217)
155218bc_expr = dolfinx .fem .Expression (u_ex , V .element .interpolation_points )
156219u_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):
217280H1_error_u = H1 (uh - u_ex , dx_3D )
218281H1_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+ # ```
0 commit comments