Skip to content

Commit 9f65647

Browse files
authored
Merge pull request #190 from gridap/lagrange-multipliers
Lagrange multipliers tutorial
2 parents dd9eae3 + 7aff9a5 commit 9f65647

File tree

6 files changed

+152
-24
lines changed

6 files changed

+152
-24
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
arch: ${{ matrix.arch }}
2626
- uses: julia-actions/cache@v2
2727
- uses: julia-actions/julia-buildpkg@v1
28-
- run: cd test/; julia --project=.. --color=yes --check-bounds=yes runtests.jl poisson.jl validation.jl elasticity.jl
28+
- run: cd test/; julia --project=.. --color=yes --check-bounds=yes runtests.jl poisson.jl validation.jl elasticity.jl lagrange_multipliers.jl
2929
tutorials_set_2:
3030
name: Tutorials2 ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}
3131
runs-on: ${{ matrix.os }}
@@ -46,7 +46,7 @@ jobs:
4646
arch: ${{ matrix.arch }}
4747
- uses: julia-actions/cache@v2
4848
- uses: julia-actions/julia-buildpkg@v1
49-
- run: cd test/; julia --project=.. --color=yes --check-bounds=yes runtests.jl p_laplacian.jl hyperelasticity.jl dg_discretization.jl fsi_tutorial.jl
49+
- run: cd test/; julia --project=.. --color=yes --check-bounds=yes runtests.jl p_laplacian.jl hyperelasticity.jl dg_discretization.jl fsi_tutorial.jl poisson_amr.jl
5050
tutorials_set_3:
5151
name: Tutorials3 ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}
5252
runs-on: ${{ matrix.os }}

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ version = "0.17.0"
77
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
88
ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
99
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
10+
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
1011
DrWatson = "634d3b9d-ee7a-5ddf-bec9-22491ea816e1"
1112
FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b"
1213
Gridap = "56d4f2e9-7ea1-5844-9cf6-b9c51ca7ce8e"
@@ -33,6 +34,7 @@ SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"
3334
Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f"
3435

3536
[compat]
37+
DataStructures = "0.18.22"
3638
Gridap = "0.18"
3739
GridapDistributed = "0.4"
3840
GridapGmsh = "0.7"

deps/build.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ files = [
2626
"Topology optimization"=>"TopOptEMFocus.jl",
2727
"Poisson on unfitted meshes"=>"poisson_unfitted.jl",
2828
"Poisson with AMR"=>"poisson_amr.jl",
29+
"Lagrange multipliers" => "lagrange_multipliers.jl",
2930
"Low-level API - Poisson equation"=>"poisson_dev_fe.jl",
3031
"Low-level API - Geometry" => "geometry_dev.jl",
3132
]

src/lagrange_multipliers.jl

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# In this tutorial, we will learn
2+
#
3+
# - How to enforce constraints using Lagrange multipliers
4+
# - How to work with `ConstantFESpace`
5+
#
6+
# ## Problem statement
7+
#
8+
# In this tutorial, we solve the Poisson equation with pure Neumann boundary conditions.
9+
# This problem is well-known to be singular since the solution is defined up to a constant,
10+
# which we have to fix to obtain a unique solution.
11+
# Here, we will use a Lagrange multiplier to enforce that the mean value of the solution
12+
# equals a given constant.
13+
#
14+
# The problem reads: find $u$ and $λ$ such that
15+
#
16+
# ```math
17+
# \left\lbrace
18+
# \begin{aligned}
19+
# -\Delta u = f \ &\text{in} \ \Omega,\\
20+
# \nabla u\cdot n = g \ &\text{on}\ \Gamma,\\
21+
# \int_{\Omega} u \ {\rm d}\Omega = \bar{u},\\
22+
# \end{aligned}
23+
# \right.
24+
# ```
25+
#
26+
# where $\Omega$ is our domain, $\Gamma$ is its boundary, $n$ is the outward unit normal vector,
27+
# and $\bar{u}$ is a given constant that fixes the mean value of the solution.
28+
#
29+
# ## Numerical scheme
30+
#
31+
# The weak form of this problem using Lagrange multipliers reads:
32+
# find $(u,λ) \in V \times \Lambda$ such that
33+
#
34+
# ```math
35+
# \begin{aligned}
36+
# \int_{\Omega} \nabla u \cdot \nabla v \ {\rm d}\Omega +
37+
# \int_{\Omega} λv \ {\rm d}\Omega +
38+
# \int_{\Omega} uμ \ {\rm d}\Omega =
39+
# \int_{\Omega} fv \ {\rm d}\Omega +
40+
# \int_{\Gamma} v(g\cdot n) \ {\rm d}\Gamma +
41+
# \int_{\Omega} μ\bar{u} \ {\rm d}\Omega
42+
# \end{aligned}
43+
# ```
44+
#
45+
# for all $(v,μ) \in V \times \Lambda$, where $V = H^1(\Omega)$ and $\Lambda = \mathbb{R}$.
46+
#
47+
# ## Implementation
48+
#
49+
# First, we load the Gridap package and define the exact solution that we will use to
50+
# manufacture the source term and boundary condition:
51+
52+
using Gridap
53+
54+
u_exact(x) = sin(x[1]) * cos(x[2])
55+
56+
# Now we can create a simple Cartesian mesh of the unit square:
57+
58+
model = CartesianDiscreteModel((0,1,0,1),(8,8))
59+
60+
# We will use first order Lagrangian finite elements for the primal variable u.
61+
62+
order = 1
63+
reffe = ReferenceFE(lagrangian, Float64, order)
64+
V = FESpace(model, reffe)
65+
66+
# For the Lagrange multiplier λ, we need a space of constant functions, since λ ∈ ℝ.
67+
# In Gridap, we can create such a space using `ConstantFESpace`:
68+
69+
Λ = ConstantFESpace(model)
70+
71+
# Conceptually, a `ConstantFESpace` is a space defined on the whole domain with a
72+
# single degree of freedom, which is what we need for the Lagrange multiplier λ.
73+
# We finally bundle both spaces into a multi-field space:
74+
75+
X = MultiFieldFESpace([V, Λ])
76+
77+
# ## Integration
78+
#
79+
# We need to create the triangulation and measures for both domain and boundary
80+
# integration:
81+
82+
Ω = Triangulation(model)
83+
Γ = BoundaryTriangulation(model)
84+
= Measure(Ω, 2*order)
85+
= Measure(Γ, 2*order)
86+
87+
# Next, we manufacture the source term f and Neumann boundary condition g
88+
# from the exact solution. We also compute the mean value ū that we want
89+
# to enforce:
90+
91+
f(x) = -Δ(u_exact)(x)
92+
g(x) = (u_exact)(x)
93+
ū = sum((u_exact)dΩ)
94+
= get_normal_vector(Γ)
95+
96+
# ## Weak Form
97+
#
98+
# We can now define the bilinear and linear forms of our problem.
99+
# Note how the forms take tuples as arguments, representing the
100+
# multi-field nature of our solution:
101+
102+
a((u,λ),(v,μ)) = ((u)(v) + λ*v + u*μ)dΩ
103+
l((v,μ)) = (f*v + μ*ū)dΩ + (v*(gnΓ))*
104+
105+
# ## Solution
106+
#
107+
# We can now create the FE operator and solve the system:
108+
109+
op = AffineFEOperator(a, l, X, X)
110+
uh, λh = solve(op)
111+
112+
# Note how we get two values from solve: the primal solution uh and
113+
# the Lagrange multiplier λh. Finally, we compute the L2 error and
114+
# verify that the mean value constraint is satisfied:
115+
116+
eh = uh - u_exact
117+
l2_error = sqrt(sum((eheh)*dΩ))
118+
ūh = sum((uh)*dΩ)
119+
120+
# The L2 error should be small (of order h²) and ūh should be very close to ū,
121+
# showing that both the equation and the constraint are well satisfied.
122+
123+
# ## Visualization
124+
#
125+
# We can visualize the solution and error by writing them to a VTK file:
126+
127+
writevtk(Ω, "results", cellfields=["uh"=>uh, "error"=>eh])

src/poisson_amr.jl

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,11 @@ function LShapedModel(n)
7979
cell_coords = map(mean,get_cell_coordinates(model))
8080
l_shape_filter(x) = (x[1] < 0.5) || (x[2] < 0.5)
8181
mask = map(l_shape_filter,cell_coords)
82-
return simplexify(DiscreteModelPortion(model,mask))
82+
model = simplexify(DiscreteModelPortion(model,mask))
83+
84+
grid = get_grid(model)
85+
topo = get_grid_topology(model)
86+
return UnstructuredDiscreteModel(grid, topo, FaceLabeling(topo))
8387
end
8488

8589
# Define the L2 norm for error estimation.
@@ -179,8 +183,8 @@ for i in 1:nsteps
179183
)
180184

181185
println("Error: $error, Error η: $(sum(η))")
182-
last_error = error
183-
model = fmodel
186+
global last_error = error
187+
global model = fmodel
184188
end
185189

186190
# The final mesh gives the following result:

test/runtests.jl

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,17 @@ else
1717
end
1818

1919
for (title,filename) in files
20-
# Create temporal modules to isolate and protect test scopes
21-
tmpdir=mktempdir(;cleanup=true)
22-
filename_wo_extension=split(filename,".")[1]
23-
tmpmod = filename_wo_extension
24-
tmpfile = joinpath(tmpdir,tmpmod)
25-
isfile(tmpfile) && error("File $tmpfile already exists!")
26-
testpath = joinpath(@__DIR__,"../src", filename)
27-
open(tmpfile,"w") do f
28-
println(f, "# This file is automatically generated")
29-
println(f, "# Do not edit")
30-
println(f)
31-
println(f, "module $tmpmod include(\"$testpath\") end")
32-
end
33-
@time @testset "$title" begin include(tmpfile) end
20+
# Create temporal modules to isolate and protect test scopes
21+
tmpdir = mktempdir(;cleanup=true)
22+
tmpmod = split(filename,".")[1]
23+
tmpfile = joinpath(tmpdir,tmpmod)
24+
isfile(tmpfile) && error("File $tmpfile already exists!")
25+
testpath = joinpath(@__DIR__,"../src", filename)
26+
open(tmpfile,"w") do f
27+
println(f, "# This file is automatically generated")
28+
println(f, "# Do not edit")
29+
println(f)
30+
println(f, "module $tmpmod include(\"$testpath\") end")
31+
end
32+
@time @testset "$title" begin include(tmpfile) end
3433
end
35-
36-
# module fsi_tutorial
37-
# using Test
38-
# @time @testset "fsi_tutorial" begin include("../src/fsi_tutorial.jl") end
39-
# end # module

0 commit comments

Comments
 (0)