Skip to content

Commit 9db6a5a

Browse files
committed
Added new tutorials
1 parent 7aff9a5 commit 9db6a5a

4 files changed

Lines changed: 579 additions & 1 deletion

File tree

Project.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ GridapGmsh = "3025c34a-b394-11e9-2a55-3fee550c04c8"
1717
GridapMakie = "41f30b06-6382-4b60-a5f7-79d86b35bf5d"
1818
GridapP4est = "c2c8e14b-f5fd-423d-9666-1dd9ad120af9"
1919
GridapPETSc = "bcdc36c2-0c3e-11ea-095a-c9dadae499f1"
20+
GridapSolvers = "6d3209ee-5e3c-4db7-a716-942eb12ed534"
2021
IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a"
2122
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
2223
LineSearches = "d3d80556-e9d4-5f37-9878-2ab0fcc64255"
@@ -35,11 +36,12 @@ Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f"
3536

3637
[compat]
3738
DataStructures = "0.18.22"
38-
Gridap = "0.18"
39+
Gridap = "0.19"
3940
GridapDistributed = "0.4"
4041
GridapGmsh = "0.7"
4142
GridapP4est = "0.3"
4243
GridapPETSc = "0.5"
44+
GridapSolvers = "0.5.0"
4345
MPI = "0.20"
4446
SpecialFunctions = "1"
4547
julia = "1.6"

src/poisson_hdg.jl

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# # [Tutorial: Hybridizable Discontinuous Galerkin Method for the Poisson equation]
2+
#
3+
# In this tutorial, we will implement a Hybridizable Discontinuous Galerkin (HDG) method
4+
# for solving the Poisson equation. The HDG method is an efficient variant of DG methods
5+
# that introduces an auxiliary variable λ on mesh interfaces to reduce the global system size.
6+
#
7+
# ## The Poisson Problem
8+
#
9+
# We consider the Poisson equation with Dirichlet boundary conditions:
10+
#
11+
# ```math
12+
# \begin{aligned}
13+
# -\Delta u &= f \quad \text{in} \quad \Omega\\
14+
# u &= g \quad \text{in} \quad \partial\Omega
15+
# \end{aligned}
16+
# ```
17+
#
18+
# ## HDG Discretization
19+
#
20+
# The HDG method first rewrites the problem as a first-order system:
21+
#
22+
# ```math
23+
# \begin{aligned}
24+
# \boldsymbol{q} + \nabla u &= \boldsymbol{0} \quad \text{in} \quad \Omega\\
25+
# \nabla \cdot \boldsymbol{q} &= -f \quad \text{in} \quad \Omega\\
26+
# u &= g \quad \text{on} \quad \partial\Omega
27+
# \end{aligned}
28+
# ```
29+
#
30+
# The HDG discretization introduces three variables:
31+
# - ``\boldsymbol{q}_h``: the approximation to the flux ``\boldsymbol{q}``
32+
# - ``u_h``: the approximation to the solution ``u``
33+
# - ``\lambda_h``: the approximation to the trace of ``u`` on element faces
34+
#
35+
# The method is characterized by:
36+
# 1. Local discontinuous approximation for ``(\boldsymbol{q}_h,u_h)`` in each element
37+
# 2. Continuous approximation for ``\lambda_h`` on element faces
38+
# 3. Numerical flux ``\widehat{\boldsymbol{q}}_h = \boldsymbol{q}_h + \tau(u_h - \lambda_h)\boldsymbol{n}``
39+
#
40+
# where τ is a stabilization parameter.
41+
#
42+
# First, let's load the required Gridap packages:
43+
44+
using Gridap
45+
using Gridap.Geometry
46+
using Gridap.FESpaces
47+
using Gridap.MultiField
48+
using Gridap.CellData
49+
50+
# ## Manufactured Solution
51+
#
52+
# We use the method of manufactured solutions to verify our implementation.
53+
# We choose a solution u and derive the corresponding source term f:
54+
55+
u(x) = sin(2*π*x[1])*sin(2*π*x[2])
56+
q(x) = -(u)(x) # Define the flux q = -∇u
57+
f(x) = (∇ q)(x) # Source term f = -Δu = -∇⋅(∇u)
58+
59+
# ## Mesh Generation
60+
#
61+
# Create a 3D simplicial mesh from a Cartesian grid:
62+
63+
D = 2 # Problem dimension
64+
nc = Tuple(fill(8, D)) # 4 cells in each direction
65+
domain = Tuple(repeat([0, 1], D)) # Unit cube domain
66+
model = simplexify(CartesianDiscreteModel(domain,nc))
67+
68+
# ## Volume and Interface Triangulations
69+
#
70+
# HDG methods require two types of meshes:
71+
# 1. Volume mesh for element interiors
72+
# 2. Skeleton mesh for element interfaces
73+
#
74+
# We also need patch-wise triangulations for local computations:
75+
76+
Ω = Triangulation(ReferenceFE{D}, model) # Volume triangulation
77+
Γ = Triangulation(ReferenceFE{D-1}, model) # Skeleton triangulation
78+
79+
ptopo = Geometry.PatchTopology(model)
80+
Ωp = Geometry.PatchTriangulation(model,ptopo) # Patch volume triangulation
81+
Γp = Geometry.PatchBoundaryTriangulation(model,ptopo) # Patch skeleton triangulation
82+
83+
# ## Reference Finite Elements
84+
#
85+
# HDG uses three different finite element spaces:
86+
# 1. Vector-valued space for the flux q (Q)
87+
# 2. Scalar space for the solution u (V)
88+
# 3. Scalar space for the interface variable λ (M)
89+
90+
order = 1 # Polynomial order
91+
reffe_Q = ReferenceFE(lagrangian, VectorValue{D, Float64}, order; space=:P)
92+
reffe_V = ReferenceFE(lagrangian, Float64, order; space=:P)
93+
reffe_M = ReferenceFE(lagrangian, Float64, order; space=:P)
94+
95+
# ## Test and Trial Spaces
96+
#
97+
# Create discontinuous cell spaces for volume variables (q,u) and
98+
# a discontinous face space for the interface variable λ:
99+
100+
# Test spaces
101+
V = TestFESpace(Ω, reffe_V; conformity=:L2) # Discontinuous vector space
102+
Q = TestFESpace(Ω, reffe_Q; conformity=:L2) # Discontinuous scalar space
103+
M = TestFESpace(Γ, reffe_M; conformity=:L2, dirichlet_tags="boundary") # Interface space
104+
105+
# Only the skeleton space has Dirichlet BC
106+
N = TrialFESpace(M, u)
107+
108+
# ## MultiField Structure
109+
#
110+
# Group the spaces for q, u, and λ using MultiField:
111+
112+
mfs = MultiField.BlockMultiFieldStyle(2,(2,1)) # Special blocking for efficient static condensation
113+
X = MultiFieldFESpace([V, Q, N];style=mfs)
114+
115+
# ## Integration Setup
116+
#
117+
# Define measures for volume and face integrals:
118+
119+
degree = 2*(order+1) # Integration degree
120+
dΩp = Measure(Ωp,degree) # Volume measure
121+
dΓp = Measure(Γp,degree) # Surface measure
122+
123+
# ## HDG Parameters
124+
#
125+
# The stabilization parameter τ affects the stability and accuracy of the method:
126+
127+
τ = 1.0 # HDG stabilization parameter
128+
129+
# ## Weak Form
130+
#
131+
# The HDG weak form consists of three equations coupling q, u, and λ.
132+
# We need operators to help define the weak form:
133+
134+
n = get_normal_vector(Γp) # Face normal vector
135+
Πn(u) = un # Normal component
136+
Π(u) = change_domain(u,Γp,DomainStyle(u)) # Project to skeleton
137+
138+
# The bilinear and linear forms are:
139+
# 1. Volume integrals for flux and primal equations
140+
# 2. Interface integrals for hybridization
141+
# 3. Stabilization terms with parameter τ
142+
143+
a((uh,qh,sh),(vh,wh,lh)) = ( qhwh - uh*(∇wh) - qh(vh) )dΩp + (sh*Πn(wh))dΓp +
144+
((Πn(qh) + τ*(Π(uh) - sh))*(Π(vh) + lh))dΓp
145+
l((vh,wh,lh)) = ( f*vh )dΩp
146+
147+
# ## Static Condensation and Solution
148+
#
149+
# A key feature of HDG is static condensation - eliminating volume variables
150+
# to get a smaller system for λ only:
151+
152+
op = MultiField.StaticCondensationOperator(ptopo,X,a,l)
153+
uh, qh, sh = solve(op)
154+
155+
# ## Error Analysis
156+
#
157+
# Compute the L2 error between numerical and exact solutions:
158+
159+
= Measure(Ω,degree)
160+
eh = uh - u
161+
l2_uh = sqrt(sum((eheh)*dΩ))
162+
163+
writevtk(Ω,"results",cellfields=["uh"=>uh,"qh"=>qh,"eh"=>eh])
164+
165+
# ## Going Further
166+
#
167+
# By modifying the stabilisation term, HDG can also work on polytopal meshes. An driver
168+
# solving the same problem on a polytopal mesh is available [in the Gridap repository](https://github.com/gridap/Gridap.jl/blob/75efc9a7a7e286c27e7ca3ddef5468e591845484/test/GridapTests/HDGPolytopalTests.jl).
169+
# A tutorial for HHO on polytopal meshes is also available.
170+
#

src/poisson_hho.jl

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
# # [Mixed-order Hybrid High-Order Method for the Poisson equation](@id poisson_hho)
2+
#
3+
# In this tutorial, we will learn how to implement a mixed-order Hybrid High-Order (HHO) method
4+
# for solving the Poisson equation. HHO methods are a class of modern hybridizable finite element methods
5+
# that provide optimal convergence rates while enabling static condensation for efficient solution.
6+
#
7+
# ## Problem statement
8+
#
9+
# We consider the Poisson equation with Dirichlet boundary conditions:
10+
#
11+
# ```math
12+
# \begin{aligned}
13+
# -\Delta u &= f \quad \text{in } \Omega \\
14+
# u &= g \quad \text{on } \partial\Omega
15+
# \end{aligned}
16+
# ```
17+
#
18+
# where Ω is a bounded domain in R², f is a source term, and g is the prescribed boundary value.
19+
#
20+
# ## HHO discretization
21+
#
22+
# The HHO method introduces two types of unknowns:
23+
# 1. Cell unknowns defined in the volume of each mesh cell
24+
# 2. Face unknowns defined on the mesh faces/edges
25+
#
26+
# This hybrid structure allows for efficient static condensation by eliminating the cell unknowns
27+
# algebraically at the element level.
28+
#
29+
# Load the required packages
30+
31+
using Gridap
32+
using Gridap.Geometry, Gridap.FESpaces, Gridap.MultiField
33+
using Gridap.CellData, Gridap.Fields, Gridap.Helpers
34+
using Gridap.ReferenceFEs
35+
using Gridap.Arrays
36+
37+
# ## Local projection operator
38+
#
39+
# Define a projection operator to map functions onto local polynomial spaces
40+
41+
function projection_operator(V, Ω, dΩ)
42+
Π(u,Ω) = change_domain(u,Ω,DomainStyle(u))
43+
mass(u,v) = (uΠ(v,Ω))dΩ
44+
V0 = FESpaces.FESpaceWithoutBCs(V)
45+
P = LocalOperator(
46+
LocalSolveMap(), V0, mass, mass; trian_out = Ω
47+
)
48+
return P
49+
end
50+
51+
# ## Reconstruction operator
52+
#
53+
# Define a reconstruction operator that maps hybrid unknowns to a higher-order polynomial space.
54+
# This operator is key for achieving optimal convergence rates.
55+
56+
function reconstruction_operator(ptopo,order,X,Ω,Γp,dΩp,dΓp)
57+
L = FESpaces.PolytopalFESpace(Ω, Float64, order+1; space=:P)
58+
Λ = FESpaces.PolytopalFESpace(Ω, Float64, 0; space=:P)
59+
60+
n = get_normal_vector(Γp)
61+
Πn(v) = (v)n
62+
Π(u,Ω) = change_domain(u,Ω,DomainStyle(u))
63+
lhs((u,λ),(v,μ)) = ( ((u)(v)) +*u) +*v) )dΩp
64+
rhs((uT,uF),(v,μ)) = ( ((uT)(v)) + (uT*μ) )dΩp + ( (uF - Π(uT,Γp))*(Πn(v)) )dΓp
65+
66+
Y = FESpaces.FESpaceWithoutBCs(X)
67+
W = MultiFieldFESpace([L,Λ];style=BlockMultiFieldStyle())
68+
R = LocalOperator(
69+
LocalPenaltySolveMap(), ptopo, W, Y, lhs, rhs; space_out = L
70+
)
71+
return R
72+
end
73+
74+
# ## Problem setup
75+
#
76+
# Define the exact solution and forcing term
77+
78+
u(x) = sin(2*π*x[1])*sin(2*π*x[2])
79+
f(x) = -Δ(u)(x)
80+
81+
# Setup the mesh and discretization parameters
82+
83+
n = 10
84+
base_model = simplexify(CartesianDiscreteModel((0,1,0,1),(n,n)))
85+
model = Geometry.voronoi(base_model)
86+
87+
D = num_cell_dims(model)
88+
Ω = Triangulation(ReferenceFE{D}, model)
89+
Γ = Triangulation(ReferenceFE{D-1}, model)
90+
91+
ptopo = Geometry.PatchTopology(model)
92+
Ωp = Geometry.PatchTriangulation(model,ptopo)
93+
Γp = Geometry.PatchBoundaryTriangulation(model,ptopo)
94+
95+
order = 1
96+
qdegree = 2*(order+1)
97+
98+
dΩp = Measure(Ωp,qdegree)
99+
dΓp = Measure(Γp,qdegree)
100+
101+
# ## FE spaces and operators
102+
#
103+
# Define the finite element spaces for cell and face unknowns
104+
105+
V = FESpaces.PolytopalFESpace(Ω, Float64, order+1; space=:P) # Bulk space
106+
M = FESpaces.PolytopalFESpace(Γ, Float64, order; space=:P, dirichlet_tags="boundary") # Skeleton space
107+
N = TrialFESpace(M,u)
108+
109+
mfs = MultiField.BlockMultiFieldStyle(2,(1,1))
110+
X = MultiFieldFESpace([V, N];style=mfs)
111+
Y = MultiFieldFESpace([V, M];style=mfs)
112+
Xp = FESpaces.PatchFESpace(X,ptopo)
113+
114+
# Setup projection and reconstruction operators
115+
116+
= projection_operator(M, Γp, dΓp)
117+
R = reconstruction_operator(ptopo,order,Y,Ωp,Γp,dΩp,dΓp)
118+
119+
# Setup assemblers
120+
121+
global_assem = SparseMatrixAssembler(X,Y)
122+
patch_assem = FESpaces.PatchAssembler(ptopo,X,Y)
123+
124+
# ## Bilinear and linear forms
125+
#
126+
# Define the bilinear form a(u,v) for the diffusion term
127+
128+
function a(u,v)
129+
Ru_Ω, Ru_Γ = R(u)
130+
Rv_Ω, Rv_Γ = R(v)
131+
return ((Ru_Ω)(Rv_Ω) + (Ru_Γ)(Rv_Ω) + (Ru_Ω)(Rv_Γ) + (Ru_Γ)(Rv_Γ))dΩp
132+
end
133+
134+
# Compute the inverse of local cell measure for stabilization
135+
136+
hTinv = CellField(1 ./ collect(get_array((1)dΩp)), Ωp)
137+
138+
# Define the stabilization term s(u,v) to weakly enforce continuity
139+
140+
function s(u,v)
141+
function (u)
142+
u_Ω, u_Γ = u
143+
return (u_Ω) - u_Γ
144+
end
145+
return (hTinv * ((u)(v)))dΓp
146+
end
147+
148+
# Define the linear form l(v) for the source term
149+
150+
l((vΩ,vΓ)) = (fvΩ)dΩp
151+
152+
# ## Problem solution
153+
#
154+
# Set up the weak form and solve using direct or static condensation
155+
156+
function weakform()
157+
u, v = get_trial_fe_basis(X), get_fe_basis(Y)
158+
data = FESpaces.collect_and_merge_cell_matrix_and_vector(
159+
(Xp, Xp, a(u,v), DomainContribution(), zero(Xp)),
160+
(X, Y, s(u,v), l(v), zero(X))
161+
)
162+
assemble_matrix_and_vector(global_assem,data)
163+
end
164+
165+
function patch_weakform()
166+
u, v = get_trial_fe_basis(X), get_fe_basis(Y)
167+
data = FESpaces.collect_and_merge_cell_matrix_and_vector(patch_assem,
168+
(Xp, Xp, a(u,v), DomainContribution(), zero(Xp)),
169+
(X, Y, s(u,v), l(v), zero(X))
170+
)
171+
return assemble_matrix_and_vector(patch_assem,data)
172+
end
173+
174+
# Direct monolithic solve
175+
176+
A, b = weakform()
177+
x = A \ b
178+
179+
ui, ub = FEFunction(X,x)
180+
eu = ui - u
181+
l2u = sqrt(sum( (eu * eu)dΩp))
182+
h1u = l2u + sqrt(sum( ((eu) (eu))dΩp))
183+
184+
# Static condensation
185+
186+
op = MultiField.StaticCondensationOperator(X,patch_assem,patch_weakform())
187+
ui, ub = solve(op)
188+
189+
eu = ui - u
190+
l2u = sqrt(sum( (eu * eu)dΩp))
191+
h1u = l2u + sqrt(sum( ((eu) (eu))dΩp))
192+
193+
# The code above demonstrates both solution approaches:
194+
#
195+
# 1. Direct solution of the full system
196+
# 2. Static condensation to eliminate cell unknowns
197+
#
198+
# Both give the same solution but static condensation is typically more efficient
199+
# for higher orders.

0 commit comments

Comments
 (0)