truexxxxxxxxxxbegin     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);endFinite 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 - Modifiy matrix such that equations at boundary exactly result in Dirichlet values - Penalty 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 
- Fix the value of 
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 
- Loss of symmetry 
Penalty method: the "lazy" way
This corresponds to replacing the Dirichlet boundary condition 
In practice we perform this operation on a discrete level:
- If 
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)xxxxxxxxxxfunction 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    endendCalculation 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.jlfunction 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)    builderendTriangulate
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.00×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
xxxxxxxxxxbuilder=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)xxxxxxxxxxf(x,y)=sinpi(x)*sinpi(y)g (generic function with 1 method)xxxxxxxxxx g(x,y)=01Data 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)xxxxxxxxxxfunction 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\rhsend 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
xxxxxxxxxxsolution=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 solutionfunction 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));endRun convergence test for a number of grid refinement levels
convergence_test (generic function with 1 method)xxxxxxxxxxfunction 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,allh1end0.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
xxxxxxxxxxallh,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.