truexxxxxxxxxxbegin ENV["LC_NUMERIC"]="C" using Pkg Pkg.activate(mktempdir()) Pkg.add(["PyPlot","PlutoUI","Triangulate"]) using PlutoUI,PyPlot, Triangulate,SparseArrays, Printf PyPlot.svg(true);endContents
Implementation of the finite volume method
Here, we specifically introduce the Voronoi finite volume method on triangular grids.
We discuss the implementation of the method for the problem
Geometrical data for finite volumes
As seen in the previous lecture, we need to be able to calculate the contributions to the Voronoi cell data for each triangle.
PA=[
Needed data
Edge lengths
:
Contributions to lengths of the interfaces between Voronoi cells
– : length fo lines joining the corresponding edge centers with the triangle circumcenter .Practically, we need the values of the ratios
:
Triangle contributions to the Voronoi cell areas around the respective triangle nodes
Calculation steps for the interface contributions
We show the calculation steps for
Semiperimeter:
Square area (from Heron's formula):
Square circumradius:
Square of the Voronoi interface contribution via Pythagoras:
Square of interface contribution over edge length:
Interface contribution over edge length:
Calculation of the area contributions
The sign chosen implies a positive value if the angle
, and a negative value if it is obtuse. In the latter case, this corresponds to the negative length of the line between edge midpoint and circumcenter, which is exactly the value which needs to be added to the corresponding amount from the opposite triangle in order to obtain the measure of the Voronoi face.If an edge between two triangles is not locally Delaunay, the summary contribution from the two triangles with respect to this edge will become negative.
Steps to the implenentation
We describe a triangular discretization mesh by three arrays:
pointlist: floating point array of node coordinates of the triangulations.pointlist[:,i]then contains the coordinates of pointi.trianglelist: integer array describing which three nodes belong to a given triangle.trianglelist[:,i]then contains the numbers of nodes belonging to trianglei.segmentlist: integer array describing which two nodes belong to a given boundary segment.segmentlist[:,i]contains the numbers of nodes for boundary segment i.
Triangle form factors
For triangle itri, we want to calculate the corresponding form factors
trifactors! (generic function with 1 method)xxxxxxxxxxfunction trifactors!(ω, e, itri, pointlist, trianglelist) # Obtain the node numbers for triangle itri i1=trianglelist[1,itri] i2=trianglelist[2,itri] i3=trianglelist[3,itri] # Calculate triangle area: # Matrix of edge vectors V11= pointlist[1,i2]- pointlist[1,i1] V21= pointlist[2,i2]- pointlist[2,i1] V12= pointlist[1,i3]- pointlist[1,i1] V22= pointlist[2,i3]- pointlist[2,i1] V13= pointlist[1,i3]- pointlist[1,i2] V23= pointlist[2,i3]- pointlist[2,i2] # Compute determinant det=V11*V22 - V12*V21 # Area area=0.5*det # Squares of edge lengths dd1=V13*V13+V23*V23 # l32 dd2=V12*V12+V22*V22 # l31 dd3=V11*V11+V21*V21 # l21 # Contributions to e_kl=σ_kl/h_kl e[1]= (dd2+dd3-dd1)*0.125/area e[2]= (dd3+dd1-dd2)*0.125/area e[3]= (dd1+dd2-dd3)*0.125/area # Contributions to ω_k ω[1]= (e[3]*dd3+e[2]*dd2)*0.25 ω[2]= (e[1]*dd1+e[3]*dd3)*0.25 ω[3]= (e[2]*dd2+e[1]*dd1)*0.25end Boundary form factors
Here we need for an interface segment of two points
bfacefactors! (generic function with 1 method)xxxxxxxxxxfunction bfacefactors!(γ,ibface, pointlist, segmentlist) i1=segmentlist[1,ibface] i2=segmentlist[2,ibface] dx=pointlist[1,i1]-pointlist[1,i2] dy=pointlist[2,i1]-pointlist[2,i2] d=0.5*sqrt(dx*dx+dy*dy) γ[1]=d γ[2]=dendMatrix assembly
The matrix assembly consists of two loops, one over all triangles, and another one over the boundary segments.
The implementation hints at the possibility to work in different space dimensions
assemble! (generic function with 1 method)xxxxxxxxxxfunction assemble!(matrix, # System matrix rhs, # Right hand side vector δ, # heat conduction coefficient f::Function, # Source/sink function α, # boundary transfer coefficient g::Function, # boundary condition function pointlist, trianglelist, segmentlist) 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]+=α*γ[k_local] x=pointlist[1,k_global] y=pointlist[2,k_global] rhs[k_global]+=g(x,y)*γ[k_local] end endendGraphical representation
It would be nice to have a graphical representation of the solution data. We can interpret the solution as a piecewise linear function on the triangulation: each triangle has three nodes each carrying one solution value.
On the other hand, a linear function of two variables is defined by values in three points. This allows to define a piecewise linear, continuous solution function. This approach is well known for the finite element method which we will introduce later.
plot (generic function with 1 method)xxxxxxxxxxfunction plot(u, pointlist, trianglelist) cmap="coolwarm" # color map for color coding function values num_isolines=10 # number of isolines for plot ax=gca(); ax.set_aspect(1) # don't distort the plot # bring data into format understood by PyPlot x=view(pointlist,1,:) y=view(pointlist,2,:) t=transpose(triout.trianglelist.-1) # Many (50) filled contour lines give the impression of a smooth color scale tricontourf(x,y,t,u,levels=50,cmap=cmap) colorbar(shrink=0.5) # Put a color bar next to the plot # Overlay the plot with isolines tricontour(x,y,t,u,levels=num_isolines,colors="k")endAn alternative way of showing the result is the 3D plot of the function graph:
plot3d (generic function with 1 method)xxxxxxxxxxfunction plot3d(u, pointlist, trianglelist) cmap="coolwarm" fig=figure(2) x=view(pointlist,1,:) y=view(pointlist,2,:) t=transpose(triout.trianglelist.-1) plot_trisurf(x,y,t,u,cmap=cmap)endCalculation example
Now we are able to solve our intended problem.
Grid generation
make_grid (generic function with 1 method)xxxxxxxxxxfunction make_grid(;maxarea=0.01) triin=TriangulateIO() triin.pointlist=Matrix{Cdouble}([-1.0 -1.0; 1.0 -1.0 ; 1.0 1.0 ; -1.0 1.0 ]') triin.segmentlist=Matrix{Cint}([1 2 ; 2 3 ; 3 4 ; 4 1 ]') triin.segmentmarkerlist=Vector{Int32}([1, 2, 3, 4]) a=("%f",maxarea) (triout, vorout)=triangulate("pqAa$(a)qQD", triin) triin, trioutendTriangulateIO( pointlist=[-1.0 1.0 1.0 -1.0; -1.0 -1.0 1.0 1.0], segmentlist=Int32[1 2 3 4; 2 3 4 1], segmentmarkerlist=Int32[1, 2, 3, 4], )
TriangulateIO( pointlist=[-1.0 1.0 … 0.5 -0.5; -1.0 -1.0 … 1.0 -1.0], pointmarkerlist=Int32[1, 1, 2, 3, 0, 4, 1, 2, 3, 0 … 4, 1, 2, 0, 0, 3, 4, 2, 3, 1], trianglelist=Int32[18 13 … 12 15; 12 24 … 3 24; 5 7 … 23 14], segmentlist=Int32[2 3 … 23 24; 16 22 … 3 1], segmentmarkerlist=Int32[1, 2, 3, 4, 4, 1, 2, 3, 4, 1, 2, 3, 4, 2, 3, 1], )
xxxxxxxxxxtriin,triout=make_grid(maxarea=4.0/desired_number_of_triangles)Plotting the grid
In the triout data structure, we indeed see a pointlist, a trianglelist and a segmentlist.
We use the plot_in_out function from Triangulate.jl to plot the grid.
Plot grid:
Number of points: 24, number of triangles: 30.
Desired number of triangles
From the desired number of triangles, we can claculate 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)*cospi(y)g (generic function with 1 method)xxxxxxxxxx g(x,y)=0δ:
solve_example (generic function with 1 method)xxxxxxxxxxfunction solve_example(triout) n=size(triout.pointlist,2) matrix=spzeros(n,n) rhs=zeros(n) assemble!(matrix,rhs,δ,f,α,g,triout.pointlist,triout.trianglelist, triout.segmentlist) sol=matrix\rhsend 0.0207156
-0.0121475
-0.010301
0.0245238
0.0162066
-0.0152359
0.00557976
0.0377689
0.0104351
0.0121834
0.0184025
0.0169708
0.00189377
-0.0563245
-0.00840472
-0.0464402
0.00999763
0.0832824
0.0155037
0.0643528
0.00921857
0.0122819
-0.0431849
0.0705515
xxxxxxxxxxsolution=solve_example(triout)xxxxxxxxxxclf(); plot(solution,triout.pointlist, triout.trianglelist);gcf().set_size_inches(4,4);gcf()3D Plot ?
xxxxxxxxxxif do_3d_plot clf(); plot3d(solution,triout.pointlist, triout.trianglelist);gcf().set_size_inches(4,4);gcf()end