Finite element method with transparent boundary condition
Introduction
Consider a flat surface illuminated by a plane wave. The incident field is given by
\[u^{\text{in}} = e^{i\alpha x_{1} - i\beta x_{2}},\]
where $\alpha = k\sin\theta, \beta = k\cos\theta$; $k > 0$ is the wave number, and $\theta \in (-\pi/2, \pi/2)$ is the incident angle. We assume that the total field $u = u^{\text{in}} + u^{\text{sc}}$, composed of the incident field $u^{\text{in}}$ and the scattered field $u^{\text{sc}}$, satisfies the Dirichelt boundary condition on the bottom $x_{2} = 0$.
Due to the periodic structure and incident plane wave, we can work on periodic function space, that is, $v = e^{-i\alpha x_{1}}u$. So the exact solution is $v_{\text{exact}} = -2i\sin(\beta x_{2})$.
Numerical examples
First, we load Nmrc and Ferrite (Ferrite is used to define the interpolation).
using Nmrc, Ferrite
Parameters
We assume that the wavenumber is $1.0$ and the incident angle is $\pi/3$.
# incident plane wave
k = 1.0
θ = π/3
inc = Incident(k, θ)
# Number of the truncated terms
N = 10;
# Height of the artificial boundary
height = 2.0;
Generating a mesh by Gmsh
We generate a mesh on a periodic cell which is a rectangle $(0.0, 2\pi) \times (0.0, 2.0)$.
# Generate the mesh in a periodic cell
grid = periodic_cell(lc=0.1, period=2π, height=height)
Ferrite.Grid{2, Ferrite.Triangle, Float64} with 2980 Ferrite.Triangle cells and 1574 nodes
Setting up finite element space and boundary conditions
We choose $P1$ element here. Then CellValues
and FacetValues
can be constructed by the function setup_vals
.
ip = Lagrange{RefTriangle, 1}()
cv, fv = setup_vals(ip)
(Ferrite.CellValues{Ferrite.FunctionValues{1, Ferrite.Lagrange{Ferrite.RefTriangle, 1}, Matrix{Float64}, Matrix{Tensors.Vec{2, Float64}}, Matrix{Tensors.Vec{2, Float64}}, Nothing, Nothing}, Ferrite.GeometryMapping{1, Ferrite.Lagrange{Ferrite.RefTriangle, 1}, Matrix{Float64}, Matrix{Tensors.Vec{2, Float64}}, Nothing}, Ferrite.QuadratureRule{Ferrite.RefTriangle, Vector{Float64}, Vector{Tensors.Vec{2, Float64}}}, Vector{Float64}}(Ferrite.FunctionValues{1, Ferrite.Lagrange{Ferrite.RefTriangle, 1}, Matrix{Float64}, Matrix{Tensors.Vec{2, Float64}}, Matrix{Tensors.Vec{2, Float64}}, Nothing, Nothing}(Ferrite.Lagrange{Ferrite.RefTriangle, 1}(), [0.16666666666667 0.16666666666667 0.66666666666667; 0.16666666666667 0.66666666666667 0.16666666666667; 0.6666666666666601 0.16666666666666008 0.16666666666666005], [0.16666666666667 0.16666666666667 0.66666666666667; 0.16666666666667 0.66666666666667 0.16666666666667; 0.6666666666666601 0.16666666666666008 0.16666666666666005], Tensors.Vec{2, Float64}[[NaN, NaN] [NaN, NaN] [NaN, NaN]; [NaN, NaN] [NaN, NaN] [NaN, NaN]; [NaN, NaN] [NaN, NaN] [NaN, NaN]], Tensors.Vec{2, Float64}[[1.0, 0.0] [1.0, 0.0] [1.0, 0.0]; [0.0, 1.0] [0.0, 1.0] [0.0, 1.0]; [-1.0, -1.0] [-1.0, -1.0] [-1.0, -1.0]], nothing, nothing), Ferrite.GeometryMapping{1, Ferrite.Lagrange{Ferrite.RefTriangle, 1}, Matrix{Float64}, Matrix{Tensors.Vec{2, Float64}}, Nothing}(Ferrite.Lagrange{Ferrite.RefTriangle, 1}(), [0.16666666666667 0.16666666666667 0.66666666666667; 0.16666666666667 0.66666666666667 0.16666666666667; 0.6666666666666601 0.16666666666666008 0.16666666666666005], Tensors.Vec{2, Float64}[[1.0, 0.0] [1.0, 0.0] [1.0, 0.0]; [0.0, 1.0] [0.0, 1.0] [0.0, 1.0]; [-1.0, -1.0] [-1.0, -1.0] [-1.0, -1.0]], nothing), Ferrite.QuadratureRule{Ferrite.RefTriangle, Vector{Float64}, Vector{Tensors.Vec{2, Float64}}}([0.166666666666665, 0.166666666666665, 0.166666666666665], Tensors.Vec{2, Float64}[[0.16666666666667, 0.16666666666667], [0.16666666666667, 0.66666666666667], [0.66666666666667, 0.16666666666667]]), [NaN, NaN, NaN]), Ferrite.FacetValues{Ferrite.FunctionValues{1, Ferrite.Lagrange{Ferrite.RefTriangle, 1}, Matrix{Float64}, Matrix{Tensors.Vec{2, Float64}}, Matrix{Tensors.Vec{2, Float64}}, Nothing, Nothing}, Ferrite.GeometryMapping{1, Ferrite.Lagrange{Ferrite.RefTriangle, 1}, Matrix{Float64}, Matrix{Tensors.Vec{2, Float64}}, Nothing}, Ferrite.FacetQuadratureRule{Ferrite.RefTriangle, Vector{Ferrite.QuadratureRule{Ferrite.RefTriangle, Vector{Float64}, Vector{Tensors.Vec{2, Float64}}}}}, Vector{Float64}, Vector{Tensors.Vec{2, Float64}}, Vector{Ferrite.FunctionValues{1, Ferrite.Lagrange{Ferrite.RefTriangle, 1}, Matrix{Float64}, Matrix{Tensors.Vec{2, Float64}}, Matrix{Tensors.Vec{2, Float64}}, Nothing, Nothing}}, Vector{Ferrite.GeometryMapping{1, Ferrite.Lagrange{Ferrite.RefTriangle, 1}, Matrix{Float64}, Matrix{Tensors.Vec{2, Float64}}, Nothing}}}(Ferrite.FunctionValues{1, Ferrite.Lagrange{Ferrite.RefTriangle, 1}, Matrix{Float64}, Matrix{Tensors.Vec{2, Float64}}, Matrix{Tensors.Vec{2, Float64}}, Nothing, Nothing}[Ferrite.FunctionValues{1, Ferrite.Lagrange{Ferrite.RefTriangle, 1}, Matrix{Float64}, Matrix{Tensors.Vec{2, Float64}}, Matrix{Tensors.Vec{2, Float64}}, Nothing, Nothing}(Ferrite.Lagrange{Ferrite.RefTriangle, 1}(), [0.7886751345948129 0.21132486540518713; 0.21132486540518713 0.7886751345948129; 0.0 0.0], [0.7886751345948129 0.21132486540518713; 0.21132486540518713 0.7886751345948129; 0.0 0.0], Tensors.Vec{2, Float64}[[NaN, NaN] [NaN, NaN]; [NaN, NaN] [NaN, NaN]; [NaN, NaN] [NaN, NaN]], Tensors.Vec{2, Float64}[[1.0, 0.0] [1.0, 0.0]; [0.0, 1.0] [0.0, 1.0]; [-1.0, -1.0] [-1.0, -1.0]], nothing, nothing), Ferrite.FunctionValues{1, Ferrite.Lagrange{Ferrite.RefTriangle, 1}, Matrix{Float64}, Matrix{Tensors.Vec{2, Float64}}, Matrix{Tensors.Vec{2, Float64}}, Nothing, Nothing}(Ferrite.Lagrange{Ferrite.RefTriangle, 1}(), [0.0 0.0; 0.7886751345948129 0.21132486540518713; 0.21132486540518713 0.7886751345948129], [0.0 0.0; 0.7886751345948129 0.21132486540518713; 0.21132486540518713 0.7886751345948129], Tensors.Vec{2, Float64}[[NaN, NaN] [NaN, NaN]; [NaN, NaN] [NaN, NaN]; [NaN, NaN] [NaN, NaN]], Tensors.Vec{2, Float64}[[1.0, 0.0] [1.0, 0.0]; [0.0, 1.0] [0.0, 1.0]; [-1.0, -1.0] [-1.0, -1.0]], nothing, nothing), Ferrite.FunctionValues{1, Ferrite.Lagrange{Ferrite.RefTriangle, 1}, Matrix{Float64}, Matrix{Tensors.Vec{2, Float64}}, Matrix{Tensors.Vec{2, Float64}}, Nothing, Nothing}(Ferrite.Lagrange{Ferrite.RefTriangle, 1}(), [0.21132486540518713 0.7886751345948129; 0.0 0.0; 0.7886751345948129 0.21132486540518713], [0.21132486540518713 0.7886751345948129; 0.0 0.0; 0.7886751345948129 0.21132486540518713], Tensors.Vec{2, Float64}[[NaN, NaN] [NaN, NaN]; [NaN, NaN] [NaN, NaN]; [NaN, NaN] [NaN, NaN]], Tensors.Vec{2, Float64}[[1.0, 0.0] [1.0, 0.0]; [0.0, 1.0] [0.0, 1.0]; [-1.0, -1.0] [-1.0, -1.0]], nothing, nothing)], Ferrite.GeometryMapping{1, Ferrite.Lagrange{Ferrite.RefTriangle, 1}, Matrix{Float64}, Matrix{Tensors.Vec{2, Float64}}, Nothing}[Ferrite.GeometryMapping{1, Ferrite.Lagrange{Ferrite.RefTriangle, 1}, Matrix{Float64}, Matrix{Tensors.Vec{2, Float64}}, Nothing}(Ferrite.Lagrange{Ferrite.RefTriangle, 1}(), [0.7886751345948129 0.21132486540518713; 0.21132486540518713 0.7886751345948129; 0.0 0.0], Tensors.Vec{2, Float64}[[1.0, 0.0] [1.0, 0.0]; [0.0, 1.0] [0.0, 1.0]; [-1.0, -1.0] [-1.0, -1.0]], nothing), Ferrite.GeometryMapping{1, Ferrite.Lagrange{Ferrite.RefTriangle, 1}, Matrix{Float64}, Matrix{Tensors.Vec{2, Float64}}, Nothing}(Ferrite.Lagrange{Ferrite.RefTriangle, 1}(), [0.0 0.0; 0.7886751345948129 0.21132486540518713; 0.21132486540518713 0.7886751345948129], Tensors.Vec{2, Float64}[[1.0, 0.0] [1.0, 0.0]; [0.0, 1.0] [0.0, 1.0]; [-1.0, -1.0] [-1.0, -1.0]], nothing), Ferrite.GeometryMapping{1, Ferrite.Lagrange{Ferrite.RefTriangle, 1}, Matrix{Float64}, Matrix{Tensors.Vec{2, Float64}}, Nothing}(Ferrite.Lagrange{Ferrite.RefTriangle, 1}(), [0.21132486540518713 0.7886751345948129; 0.0 0.0; 0.7886751345948129 0.21132486540518713], Tensors.Vec{2, Float64}[[1.0, 0.0] [1.0, 0.0]; [0.0, 1.0] [0.0, 1.0]; [-1.0, -1.0] [-1.0, -1.0]], nothing)], Ferrite.FacetQuadratureRule{Ferrite.RefTriangle, Vector{Ferrite.QuadratureRule{Ferrite.RefTriangle, Vector{Float64}, Vector{Tensors.Vec{2, Float64}}}}}(Ferrite.QuadratureRule{Ferrite.RefTriangle, Vector{Float64}, Vector{Tensors.Vec{2, Float64}}}[Ferrite.QuadratureRule{Ferrite.RefTriangle, Vector{Float64}, Vector{Tensors.Vec{2, Float64}}}([0.4999999999999999, 0.4999999999999999], Tensors.Vec{2, Float64}[[0.7886751345948129, 0.21132486540518713], [0.21132486540518713, 0.7886751345948129]]), Ferrite.QuadratureRule{Ferrite.RefTriangle, Vector{Float64}, Vector{Tensors.Vec{2, Float64}}}([0.4999999999999999, 0.4999999999999999], Tensors.Vec{2, Float64}[[0.0, 0.7886751345948129], [0.0, 0.21132486540518713]]), Ferrite.QuadratureRule{Ferrite.RefTriangle, Vector{Float64}, Vector{Tensors.Vec{2, Float64}}}([0.4999999999999999, 0.4999999999999999], Tensors.Vec{2, Float64}[[0.21132486540518713, 0.0], [0.7886751345948129, 0.0]])]), [NaN, NaN], Tensors.Vec{2, Float64}[[NaN, NaN], [NaN, NaN]], 1))
DofHandler
handles numbering and distribution of degrees of freedom (dofs) of our solution.
dh = setup_dh(grid, ip)
Ferrite.DofHandler{2, Ferrite.Grid{2, Ferrite.Triangle, Float64}}
Fields:
:u, Ferrite.Lagrange{Ferrite.RefTriangle, 1}()
Dofs per cell: 3
Total dofs: 1574
We use setup_bcs
to construct our ConstraintHandler
which contains the information of the boundary conditions. In this tutorial, we impose the Dirichelt boundary condition on the lower boundary and the periodic boundary condition on the left and right boundaries.
cst = setup_bcs(dh; period=2π)
ConstraintHandler:
BCs:
Field: u, Components: [1]
Assembling the stiffness matrix
We allocate the stiffness matrix $A$, the TBC matrix $F$ and the load vector $f$. Due to the DtN map, we need to extract the dofs associated to the artificial boundary.
# Extract dofs on the "top" boundary
top = getfacetset(grid, "top")
dofsDtN = dofs_on_dtn(dh, :u, top)
64-element Vector{Int64}:
10
109
111
122
143
144
166
191
217
233
⋮
1494
1497
1501
1508
1538
1542
1543
1565
1566
Then we create the sparse pattern of $A$ and $F$. For more details, please refer to allocate_stiff_matrix
.
# Allocate the stiffness matrix and the load vector
A = allocate_stiff_matrix(dh, cst, dofsDtN)
F = allocate_stiff_matrix(dh, cst, dofsDtN)
f = zeros(ComplexF64, ndofs(dh));
# Assemble the load vector f
f = assemble_load(fv, dh, top, f, inc, height)
# Assemble the matrix A
A = assemble_A(cv, dh, A, inc)
# Assemble the TBC matrix
F = assemble_tbc(fv, dh, inc, top, F, N, dofsDtN)
1574×1574 SparseArrays.SparseMatrixCSC{ComplexF64, Int64} with 14666 stored entries:
⎡⠿⣧⣿⣿⣣⣩⣟⣜⠈⠀⠄⠔⠠⠐⠴⠴⠈⣰⠤⠀⣅⡂⠀⠀⢠⢘⢐⢣⢂⡒⡆⣟⣿⠁⢊⢩⢭⠸⢹⡊⎤
⎢⣿⣿⣿⣿⣿⣿⣧⢷⠶⠁⠄⠁⠈⡠⢓⠁⠨⢃⠁⠠⣤⡓⠁⠈⢠⡀⢰⡆⠡⢋⡀⣿⣿⡇⣸⢼⣶⣺⣾⣾⎥
⎢⡍⣺⣿⣿⣿⣿⡿⣿⢟⡁⠂⡡⠀⡤⡄⡉⡶⢈⢅⠈⣄⡢⢸⠀⠉⠩⡸⠐⠑⢀⠐⣭⣯⡅⢬⢨⣽⢨⣿⣯⎥
⎢⣛⢽⢭⣟⣿⣯⢻⣶⣆⠇⡀⠀⠈⠬⡍⡻⢦⢧⠏⠘⢲⠄⠐⠈⢐⢀⡀⢈⠀⣖⡓⣭⢻⠁⠈⠬⢯⠬⢽⣫⎥
⎢⠂⠀⠜⠃⠟⠱⠬⠝⠿⣧⣤⣝⡯⢘⣐⣎⢵⢿⣱⠽⡯⠷⠤⠬⢐⠄⠀⡃⠃⢁⣍⠤⢑⠀⠐⠀⠘⠯⠩⠢⎥
⎢⢀⠅⠄⠁⠌⡠⠀⠈⣄⢿⠿⣧⣿⣸⣦⠶⠚⣢⣦⣞⡌⠁⠈⢠⡧⠇⢠⠽⡆⢈⣨⠀⠈⠋⡄⠶⠤⠂⠸⢠⎥
⎢⢀⠂⠂⡠⠀⡤⡂⡄⣋⢋⣛⣻⣿⣿⣿⣽⡞⣫⣯⣾⡴⢒⡐⣀⠢⠽⠀⠀⠁⡑⢋⠆⠀⠞⠹⣔⢑⡰⠁⢀⎥
⎢⢐⡇⠝⠐⡄⠩⣧⡩⡰⢼⢨⡟⣟⣿⢿⣷⣿⣷⡿⣇⠖⠰⠂⠀⡨⢵⠀⣢⠨⣟⣥⠗⢸⠁⠰⣍⢣⠀⠀⠹⎥
⎢⢂⣠⠦⢂⡘⢋⠬⣗⣵⣗⠺⣠⡾⣩⢿⣿⣿⣿⣿⣿⣦⡮⢄⠌⣰⣨⠨⡊⢀⡟⠘⠀⠐⢬⡫⡋⣀⣃⠒⠀⎥
⎢⠀⠃⠁⡀⡁⠑⣋⠁⣕⡞⣨⢿⣫⣿⠿⢯⣿⣿⣿⣿⣧⡾⣷⡛⠲⣀⠂⠲⠨⡌⠌⠄⠀⢰⡭⣿⠐⡤⣿⠈⎥
⎢⠡⠹⢤⠻⠠⡹⠘⠖⢯⡏⠆⠉⢰⢋⢘⡁⡨⡿⣩⡿⢿⣷⣿⣧⣾⣸⣎⢪⠐⣼⣯⠀⠰⡌⣽⡍⢉⡟⠉⠊⎥
⎢⠀⠀⡁⠀⠒⠒⡐⠀⡀⡇⠂⣀⠐⢨⠈⠀⡀⠕⣽⠻⠿⣿⣿⣿⣿⣻⣟⢹⡀⠣⣯⠀⠨⣟⣉⠁⠉⡁⠗⠂⎥
⎢⣀⢒⠀⠲⡇⡀⠐⢐⠐⠔⠭⠏⣌⡆⢆⣎⡐⣺⠘⢢⣚⣻⣿⣻⣿⣿⣧⣿⣶⣽⡟⠀⠠⠿⢟⢉⣖⣁⡁⠆⎥
⎢⠴⣐⠰⠶⢒⠊⡀⢈⠤⠠⣄⡖⠀⠀⠠⣠⡢⠢⢨⡀⡪⣙⣟⣙⣭⣿⣿⣿⣿⣿⣿⠀⠈⡋⡎⡔⢱⢎⠷⠁⎥
⎢⢨⠰⡥⢂⠑⢀⢠⢤⠍⢀⡈⢉⢅⠠⣦⢦⣤⠴⡂⠦⣐⣤⠤⡈⣜⣿⣿⣿⣿⣿⣿⡄⢰⣲⡿⡆⡀⠠⠆⠄⎥
⎢⣬⢭⣤⣬⡔⣤⡝⣬⠃⡝⠂⠚⠫⠔⢥⠟⠒⠀⠂⠅⠋⠛⠋⠛⠛⠉⠛⠛⠛⠿⠟⣥⣤⡿⢳⢰⣤⢠⣤⣤⎥
⎢⠟⠛⠿⠿⠏⠿⠟⠒⠑⠐⡦⠀⣠⠄⠖⠒⡐⣄⢀⣀⡐⠦⣦⢦⣤⡆⡦⠠⢰⣲⣤⡿⠿⣧⣼⣾⣿⢼⡿⠻⎥
⎢⡎⣐⣒⣞⡂⣓⡂⡄⠐⠀⢠⡍⢓⢦⡔⢦⡯⠪⣧⣯⡗⠿⠇⠘⡟⢑⢊⠭⠻⠯⢙⣒⣲⣿⣿⣿⣿⣿⣷⣐⎥
⎢⣃⡓⣸⣻⡓⣛⡋⡗⡶⡄⠠⠃⢑⡰⠉⠒⠤⢸⠐⡤⣧⠴⠇⠠⠜⢹⡱⢖⠀⡈⠀⣛⣛⣟⣿⣿⣿⣿⣿⣘⎥
⎣⡳⠲⣺⣿⡿⣿⡷⣳⠣⡂⠒⣂⠁⢀⣄⡀⠘⠀⡛⠛⡣⠀⠹⠁⠡⠌⠝⠃⠈⠅⠀⣿⣿⡋⢙⢻⣛⢻⣿⣿⎦
Be careful! Calculations between two sparse matrices may destory the structure of sparse matrices. Then imposing the boundary condition may fail. So we use sub_preserve_structure
to subtract $F$ from $A$ with keeping the structure of $A$.
# Add the TBC matrix to A = A - F
A = sub_preserve_structure(A, F)
# Impose the boundary condition
apply!(A, f, cst)
# Solve the linear system
u = A\f
# Apply the boundary condition again
apply!(u, cst)
1574-element Vector{ComplexF64}:
3.158683268810116e-5 - 0.15317244858344584im
1.7158948105089617e-5 - 0.09651732153593437im
3.79994723326924e-5 - 0.20674399138797583im
-2.5584384023723487e-5 - 0.09624018671542418im
-5.393305399234056e-5 - 0.20446036151544966im
-4.0360981259559305e-5 - 0.15296601009255503im
0.00010349732894413891 - 0.18883980316651755im
4.906580304024992e-5 - 0.08636434114583684im
4.5931041288458144e-5 - 0.08636330998468002im
5.562684550090564e-5 - 1.6827193257988013im
⋮
-0.010124949146285431 - 1.682889105761293im
0.001299890206179951 - 1.6419189991224463im
0.0 + 0.0im
0.0 + 0.0im
5.812820521565083e-5 - 0.23554401819482904im
-3.317216149964981e-5 - 0.14140319196319232im
0.00016500884652190343 - 0.22843204821949303im
0.0003688976164628844 - 0.49466242858564047im
0.0002486683289360293 - 0.32632210400148437im
Export the VTK
Finally, we write the real part and imaginary part of the solution into vtk files which can be visualized by Paraview.
VTKGridFile("real_u", grid) do vtk
write_solution(vtk, dh, real.(u))
end;
VTKGridFile("imag_u", grid) do vtk
write_solution(vtk, dh, imag.(u))
end;
This page was generated using Literate.jl.