Tutorial 25: Lagrange multipliers
In this tutorial, we will learn
- How to enforce constraints using Lagrange multipliers
- How to work with
ConstantFESpace
Problem statement
In this tutorial, we solve the Poisson equation with pure Neumann boundary conditions. This problem is well-known to be singular since the solution is defined up to a constant, which we have to fix to obtain a unique solution. Here, we will use a Lagrange multiplier to enforce that the mean value of the solution equals a given constant.
The problem reads: find $u$ and $λ$ such that
where $\Omega$ is our domain, $\Gamma$ is its boundary, $n$ is the outward unit normal vector, and $\bar{u}$ is a given constant that fixes the mean value of the solution.
Numerical scheme
The weak form of this problem using Lagrange multipliers reads: find $(u,λ) \in V \times \Lambda$ such that
for all $(v,μ) \in V \times \Lambda$, where $V = H^1(\Omega)$ and $\Lambda = \mathbb{R}$.
Implementation
First, we load the Gridap package and define the exact solution that we will use to manufacture the source term and boundary condition:
using Gridap
u_exact(x) = sin(x[1]) * cos(x[2])
Now we can create a simple Cartesian mesh of the unit square:
model = CartesianDiscreteModel((0,1,0,1),(8,8))
We will use first order Lagrangian finite elements for the primal variable u.
order = 1
reffe = ReferenceFE(lagrangian, Float64, order)
V = FESpace(model, reffe)
For the Lagrange multiplier λ, we need a space of constant functions, since λ ∈ ℝ. In Gridap, we can create such a space using ConstantFESpace
:
Λ = ConstantFESpace(model)
Conceptually, a ConstantFESpace
is a space defined on the whole domain with a single degree of freedom, which is what we need for the Lagrange multiplier λ. We finally bundle both spaces into a multi-field space:
X = MultiFieldFESpace([V, Λ])
Integration
We need to create the triangulation and measures for both domain and boundary integration:
Ω = Triangulation(model)
Γ = BoundaryTriangulation(model)
dΩ = Measure(Ω, 2*order)
dΓ = Measure(Γ, 2*order)
Next, we manufacture the source term f and Neumann boundary condition g from the exact solution. We also compute the mean value ū that we want to enforce:
f(x) = -Δ(u_exact)(x)
g(x) = ∇(u_exact)(x)
ū = sum(∫(u_exact)dΩ)
nΓ = get_normal_vector(Γ)
Weak Form
We can now define the bilinear and linear forms of our problem. Note how the forms take tuples as arguments, representing the multi-field nature of our solution:
a((u,λ),(v,μ)) = ∫(∇(u)⋅∇(v) + λ*v + u*μ)dΩ
l((v,μ)) = ∫(f*v + μ*ū)dΩ + ∫(v*(g⋅nΓ))*dΓ
Solution
We can now create the FE operator and solve the system:
op = AffineFEOperator(a, l, X, X)
uh, λh = solve(op)
Note how we get two values from solve: the primal solution uh and the Lagrange multiplier λh. Finally, we compute the L2 error and verify that the mean value constraint is satisfied:
eh = uh - u_exact
l2_error = sqrt(sum(∫(eh⋅eh)*dΩ))
ūh = sum(∫(uh)*dΩ)
The L2 error should be small (of order h²) and ūh should be very close to ū, showing that both the equation and the constraint are well satisfied.
Visualization
We can visualize the solution and error by writing them to a VTK file:
writevtk(Ω, "results", cellfields=["uh"=>uh, "error"=>eh])
This page was generated using Literate.jl.