true
xxxxxxxxxx
begin
ENV["LC_NUMERIC"]="C"
using Pkg
Pkg.activate(mktempdir())
Pkg.add(["PyPlot","PlutoUI","Triangulate","SimplexGridFactory",
"ExtendableGrids","GridVisualize","ExtendableSparse"])
using PlutoUI,PyPlot, Triangulate,SimplexGridFactory,ExtendableGrids,ExtendableSparse,GridVisualize,SparseArrays, Printf
PyPlot.svg(true);
end
Finite volume method: further aspects
Julia packages supporting PDE solution
Up to now we used the Triangulate.jl in order to access mesh generation, for all other functionality, standard Julia packages were used.
There are a number of PDE solution packages in Julia, in particular for the finite element method. During this course, we will use a number of recently developed packages supporting basic functionality for the solution of PDEs. They emerged from the WIAS pdelib project and from scientific computing courses from previous years. These are:
ExtendableGrids.jl: unstructured grid management library
GridVisualize.jl: grid and function visualization related to ExtendableGrids.jl
SimplexGridFactory.jl: unified high level mesh generator interface
ExtendableSparse.jl: convenient and efficient sparse matrix assembly
We will use all of them in this lecture.
Contents
Dirichlet boundary conditions
Three main possibilities to implement Dirichlet boundary conditions:
Eliminate Dirichlet BC algebraically after building of the matrix, i.e. fix ``known unknowns'' at the Dirichlet boundary
highly technicalModifiy matrix such that equations at boundary exactly result in Dirichlet values
loss of symmetry of the matrixPenalty method: replace the Dirichlet boundary condition by a Robin boundary condition with high transfer coefficient
We discuss these possibilities for a 1D problem in
Algebraic manipulation
Matrix
of homogeneous Neumann problem - no regard to boundary values.
is diagonally dominant, but neither idd, nor sdd.Fix the value of
and eliminate the corresponding equation:
is idd and stays symmetric
This operation is quite technical to implement, even more so for triangular meshes or for systems with multiple PDEs.
Modification of boundary equations
Modify equation at boundary to exactly represent Dirichlet values
is idd ?Loss of symmetry
problem e.g. with CG method
Penalty method: the "lazy" way
This corresponds to replacing the Dirichlet boundary condition
In practice we perform this operation on a discrete level:
is idd, symmetric, and the realization is technically easy.If
is small enough, will be satisfied exactly within
floating point accuracy.
Drawback: this creates a large condition number
Iterative methods should be initialized with Dirichlet values, so we start in a subspace where this is not relevant
Works also nonlinear problems, finite volume methods
Matrix assembly
trifactors! (generic function with 1 method)
bfacefactors! (generic function with 1 method)
assemble! (generic function with 1 method)
xxxxxxxxxx
function assemble!(matrix, # System matrix
rhs, # Right hand side vector
δ, # heat conduction coefficient
f::Function, # Source/sink function
g::Function, # boundary condition function
pointlist,
trianglelist,
segmentlist)
penalty=1.0e30
num_nodes_per_cell=3;
num_edges_per_cell=3;
num_nodes_per_bface=2
ntri=size(trianglelist,2)
nbface=size(segmentlist,2)
# Local edge-node connectivity
local_edgenodes=[ 2 3; 3 1; 1 2]'
# Storage for form factors
e=zeros(num_nodes_per_cell)
ω=zeros(num_edges_per_cell)
γ=zeros(num_nodes_per_bface)
# Initialize right hand side to zero
rhs.=0.0
# Loop over all triangles
for itri=1:ntri
trifactors!(ω,e,itri,pointlist,trianglelist)
# Assemble nodal contributions to right hand side
for k_local=1:num_nodes_per_cell
k_global=trianglelist[k_local,itri]
x=pointlist[1,k_global]
y=pointlist[2,k_global]
rhs[k_global]+=f(x,y)*ω[k_local]
end
# Assemble edge contributions to matrix
for iedge=1:num_edges_per_cell
k_global=trianglelist[local_edgenodes[1,iedge],itri]
l_global=trianglelist[local_edgenodes[2,iedge],itri]
matrix[k_global,k_global]+=δ*e[iedge]
matrix[l_global,k_global]-=δ*e[iedge]
matrix[k_global,l_global]-=δ*e[iedge]
matrix[l_global,l_global]+=δ*e[iedge]
end
end
# Assemble boundary conditions
for ibface=1:nbface
bfacefactors!(γ,ibface, pointlist, segmentlist)
for k_local=1:num_nodes_per_bface
k_global=segmentlist[k_local,ibface]
matrix[k_global,k_global]+=penalty
x=pointlist[1,k_global]
y=pointlist[2,k_global]
rhs[k_global]+=penalty*g(x,y)
end
end
end
Calculation example
Now we are able to solve our intended problem.
Grid generation
describe_grid (generic function with 1 method)
xxxxxxxxxx
# We use the SimplexGridBuilder from SimplexGridFactory.jl
function describe_grid()
# Create a SimplexGridBuilder structure which can collect
# geometry information
builder=SimplexGridBuilder(Generator=Triangulate)
# Add points, record their numbers
p1=point!(builder,-1,-1)
p2=point!(builder,1,-1)
p3=point!(builder,1,1)
p4=point!(builder,-1,1)
# Connect points by respective facets (segments)
facetregion!(builder,1)
facet!(builder,p1,p2)
facetregion!(builder,2)
facet!(builder,p2,p3)
facetregion!(builder,3)
facet!(builder,p3,p4)
facetregion!(builder,4)
facet!(builder,p4,p1)
options!(builder,maxvolume=0.1)
builder
end
Triangulate
4
1
1.0
1.0e-12
1
2
3
4
1
2
2
3
3
4
4
1
2
1.0e-12
1.79769e308
1.79769e308
-1.79769e308
-1.79769e308
0.01
2×4 ElasticArrays.ElasticArray{Float64,2,1,Array{Float64,1}}: -1.0 1.0 1.0 -1.0 -1.0 -1.0 1.0 1.0
0×0 Array{Array{Int32,1},2}
10
1
2
3
4
5
0.05
0
0
2×0 ElasticArrays.ElasticArray{Float64,2,1,Array{Float64,1}}
:optlevel
1
:attributes
true
:quiet
true
:verbose
false
:addflags
""
:maxvolume
0.1
:confdelaunay
true
:unsuitable
nothing
:debugfacets
true
:volumecontrol
true
:quality
true
:flags
nothing
:refine
false
:PLC
true
:nosteiner
false
:minangle
20
:check
false
true
xxxxxxxxxx
builder=describe_grid()
xxxxxxxxxx
# We can plot the input and the possible output of the builder.
builderplot(builder,Plotter=PyPlot)
ExtendableGrids.ExtendableGrid{Float64,Int32};
dim: 2 nodes: 24 cells: 30 bfaces: 16
xxxxxxxxxx
# The simplexgrid method creates an object of type ExtendableGrid which is
# defined in ExtendableGrids.jl. We can overwrite the maxvolume default
# which we used in `describe_grid`.
grid=simplexgrid(builder,maxvolume=4/desired_number_of_triangles)
Desired number of triangles
From the desired number of triangles, we can calculate a value fo the maximum area constraint passed to the mesh generator: Desired number of triangles:
Solving the problem
Problem data
f (generic function with 1 method)
xxxxxxxxxx
f(x,y)=sinpi(x)*sinpi(y)
g (generic function with 1 method)
xxxxxxxxxx
g(x,y)=0
1
Data of the grid are accessed in a Dictionary like fashion. Coordinates
, CellNodes
and BFaceNodes
are abstract types defined in ExtendableGrids.jl
. Behind this is a dictionary with types as keys allowing type-stable access of the contents like in a struct and easy extension by defining additional key types. See here for more information.
solve_example (generic function with 1 method)
xxxxxxxxxx
function solve_example(grid)
# Initialize sparse matrix and right hand side
n=num_nodes(grid)
matrix=spzeros(n,n)
rhs=zeros(n)
# Call the assemble function.
assemble!(matrix,rhs,δ,f,g,
grid[Coordinates],
grid[CellNodes],
grid[BFaceNodes])
# Solve
sol=matrix\rhs
end
7.58983e-63
-1.58037e-62
1.56301e-62
-1.8106e-62
0.00546284
1.23156e-32
-2.0255e-33
5.96078e-34
-5.55078e-34
-0.0632147
-0.072424
0.0625205
-0.004051
0.0569828
2.13686e-32
-3.16074e-32
-3.16074e-32
0.00119216
-0.00111016
-3.6212e-32
-3.6212e-32
3.12603e-32
3.12603e-32
8.99076e-33
xxxxxxxxxx
solution=solve_example(grid)
xxxxxxxxxx
# scalarplot from GridVisualize.jl allows easy handling of plotting
# on unstructured grids with reasonable defaults.
scalarplot(grid,solution,Plotter=PyPlot,resolution=(300,300),isolines=11,colormap=:bwr)
Convergence test
How good is our implementation and the choice of the penalty method for Dirichlet boundary conditions ? - Perform a convergence test on ever finer grids!
For this purpose we need to calculate error norms. Based on the L2-Norm
we implement a discrete analogon for a discrete solution
Further, we implement the "h1"-norm wich measures the error in the gradient. We may discuss the details later.
fvnorms (generic function with 1 method)
xxxxxxxxxx
# ## Calculate norms of solution
function fvnorms(u,pointlist,trianglelist)
local_edgenodes=[ 2 3; 3 1; 1 2]'
num_nodes_per_cell=3;
num_edges_per_cell=3;
e=zeros(num_nodes_per_cell)
ω=zeros(num_edges_per_cell)
l2norm=0.0
h1norm=0.0
ntri=size(trianglelist,2)
for itri=1:ntri
trifactors!(ω,e,itri,pointlist,trianglelist)
for k_local=1:num_nodes_per_cell
k=trianglelist[k_local,itri]
x=pointlist[1,k]
y=pointlist[2,k]
l2norm+=u[k]^2*ω[k_local]
end
for iedge=1:num_edges_per_cell
k=trianglelist[local_edgenodes[1,iedge],itri]
l=trianglelist[local_edgenodes[2,iedge],itri]
h1norm+=(u[k]-u[l])^2*e[iedge]
end
end
return (sqrt(l2norm),sqrt(h1norm));
end
Run convergence test for a number of grid refinement levels
convergence_test (generic function with 1 method)
xxxxxxxxxx
function convergence_test(;nref0=0, nref1=1,k=1,l=1,extsparse=false)
allh=[]
alll2=[]
allh1=[]
gbc(x,y)=0
# We know the anayltical expression for the right hand side which
# corresponds to this solution
fexact(x,y)=sinpi(k*x)*sinpi(l*y);
frhs(x,y)=(k^2+l^2)*pi^2*fexact(x,y);
for iref=nref0:nref1
# define the refinement level via the maximum area constraint
area=0.1*2.0^(-2*iref)
h=sqrt(area)
grid=simplexgrid(builder,maxvolume=area)
n=num_nodes(grid)
rhs=zeros(n)
# Optionally, use the sparse matrix from ExtendableGrids
if extsparse
matrix=ExtendableSparseMatrix(n,n)
else
matrix=spzeros(n,n)
end
rhs=zeros(n)
assemble!(matrix,rhs,δ,frhs,gbc,
grid[Coordinates],grid[CellNodes],grid[BFaceNodes])
sol=matrix\rhs
uexact=map(fexact,grid)
(l2norm,h1norm)=fvnorms(uexact-sol,grid[Coordinates],grid[CellNodes])
push!(allh,h)
push!(allh1,h1norm)
push!(alll2,l2norm)
end
allh,alll2,allh1
end
0.316228
0.158114
0.0790569
0.0395285
0.0197642
0.00988212
0.00494106
0.265187
0.0612054
0.0129359
0.00308861
0.000759339
0.000192071
4.68798e-5
1.00734
0.290558
0.0796075
0.0325201
0.0154831
0.00773286
0.00380749
xxxxxxxxxx
allh,alll2,allh1=convergence_test(nref0=0,nref1=6,extsparse=false)
Conclusions
We see the second order convergence of the solution and first order convergence of the gradient. This is the typical behavior which we also would expect from the finite element method.
Concerning the complexity, the ExtendableSparseMatrix uses an intermediate data structure for collecting the matrix entries. If we directly insert data into a compressed column data structure, there is a considerable overhead for reorganization of the long arrays describing the matrix.