Hello, World!

Problem statement
In this example, we show how to solve the "Hello, world" PDE example: the Laplace equation on the unit hyper-cube $\Omega =[0,1]^d$, $d=3$, with Dirichlet boundary conditions.
\[\left\lbrace \begin{aligned} -\Delta u = f \ &\text{in} \ \Omega,\\ u = g \ &\text{on}\ \partial\Omega,\\ \end{aligned} \right.\]
with $f=0$ and $g(x)=\text{sum}(x)$. In this case, we know that the solution is $u=g$ which allows us to check that we solve the problem correctly, by integration an error norm.
Numerical scheme
We use a conventional Galerkin finite element (FE) method with conforming Lagrangian FE spaces (see, e.g., [1]). The weak form equation we solve consists in finding $u_h\in V_g$ such that $a(u_h,v) = \ell(v)$ for all $v\in V_0$. To this end we build a space $V$ spanned by continuous and piece-wise Lagrangian basis functions. The auxiliary spaces $V_g$ and $V_0$ are the subsets of $V$ that fulfill the Dirichlet boundary condition $g$ and $0$ on $\partial\Omega$ respectively. The bilinear and linear forms are
\[ a(u,v) \doteq \int_{\Omega} \nabla v \cdot \nabla u \ {\rm d}\Omega, \quad b(v) \doteq \int_{\Omega} v\ f \ {\rm d}\Omega.\]
This equation results in a system of linear algebraic equations that is solved using an external linear solver from LinearSolve.jl.
Implementation
using LinearAlgebra
import GalerkinToolkit as GT
import ForwardDiff
import GLMakie as Makie
import LinearSolve
#Geometry
domain = (0,1,0,1,0,1)
cells = (10,10,10)
mesh = GT.cartesian_mesh(domain,cells)
Ω = GT.interior(mesh)
Γd = GT.boundary(mesh)
#Functions
∇ = ForwardDiff.gradient
g = GT.analytical_field(sum,Ω)
f = GT.analytical_field(x->0,Ω)
#Interpolation
k = 1
V = GT.lagrange_space(Ω,k;dirichlet_boundary=Γd)
T = Float64
uhd = GT.zero_dirichlet_field(T,V)
GT.interpolate_dirichlet!(g,uhd)
#Weak form
degree = 2*k
dΩ = GT.measure(Ω,degree)
a = (u,v) -> GT.∫( x->∇(u,x)⋅∇(v,x), dΩ)
l = v -> GT.∫( x->v(x)*f(x), dΩ)
#Linear problem
p = GT.SciMLBase_LinearProblem(uhd,a,l)
sol = LinearSolve.solve(p)
uh = GT.solution_field(uhd,sol)
#Error check
eh = x -> uh(x) - g(x)
el2 = GT.∫( x->abs2(eh(x)), dΩ) |> sum |> sqrt
@assert el2 < 1.0e-9
#Visualization
fig = Makie.Figure()
ax = Makie.Axis3(fig[1,1],aspect=:data)
Makie.hidespines!(ax)
Makie.hidedecorations!(ax)
GT.makie_surfaces!(Ω;color=uh)Explicit integration loops
This other code version implements the integration loops manually instead of relying on the underlying automatic code generation.
#Manually written matrix assembly function
#Always use a function, never the global scope
function assemble_matrix!(A_alloc,Ad_alloc,V,dΩ)
∇ = ForwardDiff.gradient
#Iterators to the quantities on the
#integration points
V_faces = GT.each_face(V,dΩ;tabulate=(∇,))
#Temporaries
n = GT.max_num_reference_dofs(V)
T = Float64
Auu = zeros(T,n,n)
#Numerical integration loop
for V_face in V_faces
#Get quantities at current face
dofs = GT.dofs(V_face)
#Reset face matrix
fill!(Auu,zero(T))
#Loop over integration points
for V_point in GT.each_point(V_face)
#Get quantities at current integration point
dV = GT.weight(V_point)
dof_∇s = GT.shape_functions(∇,V_point)
#Fill in face matrix
for (i,dofi) in enumerate(dofs)
∇v = dof_∇s[i]
for (j,dofj) in enumerate(dofs)
∇u = dof_∇s[j]
Auu[i,j] += ∇v⋅∇u*dV
end
end
end
#Add face contribution to the
#global allocations
GT.contribute!(A_alloc,Auu,dofs,dofs)
GT.contribute!(Ad_alloc,Auu,dofs,dofs)
end
end
#Manually written integration of the error
#Always use a function, never the global scope
function integrate_l2_error(g,uh,dΩ)
#Iterators to the quantities on the
#integration points
tabulate = (GT.value,)
compute = (GT.coordinate,)
uh_faces = GT.each_face(uh,dΩ;tabulate,compute)
#Numerical integration loop
s = 0.0
for uh_face in uh_faces
for uh_point in GT.each_point(uh_face)
#Get quantities at current integration point
x = GT.coordinate(uh_point)
dV = GT.weight(uh_point)
uhx = GT.field(GT.value,uh_point)
#Add contribution
s += abs2(uhx-g.definition(x))*dV
end
end
#Compute the l2 norm
el2 = sqrt(s)
end
#Allocate matrix for free columns
A_alloc = GT.allocate_matrix(T,V,V,Ω)
#Allocate matrix for dirichlet columns
free_or_dirichlet=(GT.FREE,GT.DIRICHLET)
Ad_alloc = GT.allocate_matrix(T,V,V,Ω;free_or_dirichlet)
#Fill allocations with the function we wrote above
assemble_matrix!(A_alloc,Ad_alloc,V,dΩ)
#Compress matrix into the final format
A = GT.compress(A_alloc)
Ad = GT.compress(Ad_alloc)
#Build the linear system
xd = GT.dirichlet_values(uhd)
b = -Ad*xd
p = LinearSolve.LinearProblem(A,b)
#Solve the problem
sol = LinearSolve.solve(p)
uh = GT.solution_field(uhd,sol)
#Integrate the error l2 norm
#with the function we wrote above
el2 = integrate_l2_error(g,uh,dΩ)
@assert el2 < 1.0e-9This page was generated using Literate.jl.