Constrained Poisson equation (FE)

In this example, a Poisson equation with Neumann boundary conditions is solved using a boundary integral constraint.

Theory

Consider the following Poisson equation on the unit disk (noted $\Omega$ in this example, its boundary is noted $\Gamma$):

\[\begin{cases} - \Delta u = f \, \, \forall x \in \Omega \\ \frac{\partial u}{\partial n} = 0 \, \, \forall x \in \Gamma \end{cases}\]

Poisson's equation can be written in the form of a minimisation problem:

\[ \min_{u} J(u) = \frac{1}{2} \int_{\Omega} \nabla u \cdot \nabla u \, dV - \int_{\Omega} f u \, dV\]

There is no unique solution to this problem (adding a constant to any solution will also be a solution). Uniqueness can be recovered by adding a constraint to the problem. In this example the following constraint is added:

\[ \int_{\Gamma} u \, d \gamma = 2 \pi\]

To solve this constrained minimisation problem, the following lagragian is introduced:

\[ \mathcal{L}(u, \lambda_u) = \frac{1}{2} \int_{\Omega} \nabla u \cdot \nabla u \, dV - \int_{\Omega} f u \, dV + \lambda_u ( \int_{\Gamma} u \, d \gamma - 2 \pi)\]

where $\lambda_u$ is a Lagrange multiplier. The first order optimality conditions translate to the problem: find $(u, \lambda_u)$ such that for all $(v, \lambda_v)$:

\[ \int_{\Omega} \nabla u \cdot \nabla v \, dV + \lambda_u \int_\Gamma v \, d \gamma = \int_\Omega f v \, dV\]

\[ \lambda_v \int_\Gamma u \, d\gamma = 2 \pi \lambda_v\]

This problem can be assembled by introducing a MultiplierFESpace and combining it with the usual FESpace using a MultiFESpace. In this example, the manufactured solution $u(x,y)=cos(4\pi(x^2 + y^2))$ is used to test the method.

Commented code

import necessary packages

using Bcube
using LinearAlgebra
using SparseArrays
using WriteVTK

const outputpath = joinpath(@__DIR__, "..", "..", "..", "myout", "constrained_poisson")
mkpath(outputpath)

Read 2D mesh

mesh_path = joinpath(outputpath, "mesh.msh")
gen_disk_mesh(mesh_path; lc = 3.2e-2)
mesh = read_msh(mesh_path)

Choose degree and define function space, trial space and test space

const degree = 2
fs = FunctionSpace(:Lagrange, degree)
U = TrialFESpace(fs, mesh)
V = TestFESpace(U)

Define the multiplier trial space and corresponding test space The second argument of MultiplierFESpace specifies the number of scalar Lagrange multipliers that are to be used for the problem.

Λᵤ = MultiplierFESpace(mesh, 1)
Λᵥ = TestFESpace(Λᵤ)

The usual trial FE space and multiplier space are combined into a MultiFESpace

P = MultiFESpace(U, Λᵤ)
Q = MultiFESpace(V, Λᵥ)

Define volume and boundary measures

dΩ = Measure(CellDomain(mesh), 2 * degree + 1)
Γ = BoundaryFaceDomain(mesh, ("BORDER",))
dΓ = Measure(Γ, 2 * degree + 1)

Define solution FE Function

ϕ = FEFunction(U)

Define source term function (deduced from manufactured solution)

f = PhysicalFunction(
    x ->
        64.0 * π^2 * (x[1]^2 + x[2]^2) * cos(4.0 * π * (x[1]^2 + x[2]^2)) +
        16.0 * π * sin(4.0 * π * (x[1]^2 + x[2]^2)),
)

volume = sum(Bcube.compute(∫(PhysicalFunction(x -> 1.0))dΩ))

Define bilinear and linear forms

function a((u, λᵤ), (v, λᵥ))
    ∫(∇(u) ⋅ ∇(v))dΩ + ∫(side⁻(λᵤ) * side⁻(v))dΓ + ∫(side⁻(λᵥ) * side⁻(u))dΓ
end

For the time being only functionals in the form of integrals can be assembled. A temporary workaround is to put the multiplier in the integral and divide by the volume (the multiplier does not vary in space).

l((v, λᵥ)) = ∫(f * v + 2.0 * π * λᵥ / volume)dΩ

Assemble to get matrices and vectors

A = assemble_bilinear(a, P, Q)
L = assemble_linear(l, Q)

Solve problem

sol = A \ L

ϕ = FEFunction(Q)

Write solution and compare to analytical solution

set_dof_values!(ϕ, sol)
u, λ = ϕ

println("Value of Lagrange multiplier : ", λ.dofValues[1])

u_ref = PhysicalFunction(x -> cos(4.0 * π * (x[1]^2 + x[2]^2)))
error = u_ref - u

vars = Dict("Numerical solution" => u, "Analytical solution" => u_ref, "error" => error)
filename = joinpath(outputpath, "output")
Bcube.write_vtk_lagrange(filename, vars, mesh, U)

l2(u) = sqrt(sum(Bcube.compute(∫(u ⋅ u)dΩ)))
el2 = l2(error) / l2(u_ref)
tol = 1.e-3
println("L2 relative error : ", el2)


This page was generated using Literate.jl.