Extracting integral data from Finite Volume solutions
After calculating solutions based on the finite volume method, it may be interesting to obtain information about the solution besides of the graphical representation.
Here, we focus on the following data:
integrals of the solution
flux through parts of the boundary
Sample problem
Here, we define a sample problem for discussing these issues, which could be formulated in a more general way as well.
make_grid (generic function with 1 method)xxxxxxxxxxfunction make_grid(;maxvolume=0.001) builder=SimplexGridBuilder(Generator=Triangulate) p00=point!(builder, 0,0) p10=point!(builder, 1,0.25) p11=point!(builder, 1,0.75) p01=point!(builder, 0,1) facetregion!(builder,1) facet!(builder, p00,p10) facetregion!(builder,2) facet!(builder, p10,p11) facetregion!(builder,3) facet!(builder, p11,p01) facetregion!(builder,4) facet!(builder,p00,p01) simplexgrid(builder,maxvolume=maxvolume)endExtendableGrids.ExtendableGrid{Float64, Int32};
dim: 2 nodes: 631 cells: 1159 bfaces: 101
xxxxxxxxxxgrid=make_grid()Let us define the following reaction - diffusion system in
with boundary conditions
The source
xxxxxxxxxxfunction storage(f,u,node) f.=uend;xxxxxxxxxxfunction flux(f,_u,edge) u=unknowns(edge,_u) f[1]=u[1,1]-u[1,2] f[2]=u[2,1]-u[2,2]end;xxxxxxxxxxr(u1,u2)= u1-0.1*u2;xxxxxxxxxxfunction reaction(f,u,node) f[1]= r(u[1],u[2]) f[2]=-r(u[1],u[2])end;xxxxxxxxxxfunction source(f,node) f[1]=1.0end;... be careful with the sign: reaction is on the left hand side, source on the right hand side.
Create the system, enable species, set boundary condition, solve, create initial value:
VoronoiFVM.Physics(num_species=2, flux=flux, storage=storage, reaction=reaction, source=source, breaction=nofunc2, bstorage=nofunc2, generic_operator=nofunc_generic, generic_operator_sparsity=nofunc_generic_sparsity)
xxxxxxxxxxphysics=VoronoiFVM.Physics(num_species=2, flux=flux, storage=storage, reaction=reaction, source=source)xxxxxxxxxxbegin system=VoronoiFVM.System(grid,physics) enable_species!(system,1,[1]) enable_species!(system,2,[1])endxxxxxxxxxxboundary_dirichlet!(system,2,2,0.0);2×631 Matrix{Float64}:
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 … 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0xxxxxxxxxxinival=unknowns(system,inival=0.0)Stationary case
For this problem, we have the following flux balances derived from the equations and from Gauss' theorem:
The volume integrals can be approximated based on the finite volume subdivision
2×631 Matrix{Float64}:
1.04961 1.04503 1.04503 … 1.0496 1.04956 1.04573 1.04954
0.647343 3.67642e-32 6.23316e-32 0.646598 0.644086 0.26481 0.642655xxxxxxxxxxu=solve(inival,system)The integrate method of VoronoiFVM provides a possibility to calculate the volume integral of a function of a solution as described above. It returns a num_speciesnum_regions matrix of the integrals of the function of the unknowns over the different subdomains (here, we have only one):
Amount of
and in the domain aka integral over identity storage function:
2×1 Matrix{Float64}:
0.7858573677959029
0.35857367795902967xxxxxxxxxxU=integrate(system,storage,u)Amount of species created by source term per unit time:
2×1 Matrix{Float64}:
0.7499999999999993
0.0xxxxxxxxxxF=integrate(system,(f,u,node)->source(f,node),u)Amount of reaction per unit time:
2×1 Matrix{Float64}:
0.7499999999999992
-0.7499999999999992xxxxxxxxxxR=integrate(system,reaction,u)Reaction == creation ?
Let us check our first identity: creation rate = reaction rate:
trueF[1] ≈ R[1]Outflow == reaction ?
The test function trick
This trick goes back to work of H. Gajewski in the field of semiconductor device simulation.
But what about the boundary integral ? Here, we use a trick to cast the surface integral to a volume integral with the help of a test function:
Let
VoronoiFVM.jl provides a special API for obtaining such a test function:
1.30582e-32
1.0
1.0
5.43952e-33
0.400827
1.24937e-32
0.372123
0.613776
1.0
0.659505
0.514265
0.692285
0.607893
0.811032
0.523974
0.597665
0.639823
0.645443
0.608459
0.615978
0.0305674
0.0511571
0.0945545
0.0756602
8.21007e-33
0.0139336
0.0185675
0.0503246
0.72905
0.0606202
begin tf=VoronoiFVM.TestFunctionFactory(system) Γ_where_T_equal_1=[2] Γ_where_T_equal_0=[4] T=testfunction(tf,Γ_where_T_equal_0,Γ_where_T_equal_1)endWrite
and we approximate
where the sum runs over pairs of neigboring control volumes.
The integrate method with a test function parameter returns a value for each species, the sign convention assumes that species leaving the domain lead to negative values.
-2.7333e-17
-0.75
xxxxxxxxxxI=integrate(system,T,u)Check that none of
truexxxxxxxxxxisapprox(I[1],0.0,atol=1.0e-16)Check that creation of
trueR[2] ≈ I[2]So we indeed can confirm the requirement for the right balance of source, reaction and outflow.
Transient problem
For the transient case, in addition, we need to consider the time derivative part along with reaction and source. In the derivation of the test function procedure, under the assumption of the implicit Euler time discretization method, this can be achieved by handling the finite difference in time along with source and reaction.
xxxxxxxxxxt0=0.0; tend=10;xxxxxxxxxxcontrol=VoronoiFVM.NewtonControl();xxxxxxxxxxcontrol.Δu_opt=0.025;xxxxxxxxxxcontrol.Δt_min=1.0e-4;xxxxxxxxxxcontrol.Δt=0.1;control.Δt_max=1.0;t: 52-element Vector{Float64}:
0.0
0.05
0.07624404074558075
0.10317181117166707
0.1308175835099931
0.15921827342132694
0.18841372976572393
⋮
5.559040374037019
6.3928478027881015
7.3928478027881015
8.3928478027881
9.3928478027881
10.0
u: 52-element Vector{Matrix{Float64}}:
[0.0 0.0 … 0.0 0.0; 0.0 0.0 … 0.0 0.0]
[0.04762986051262805 0.04762515338868721 … 0.047626533643388674 0.04762983009855026; 0.002333433999389573 2.7293310792136733e-34 … 0.0015109610896880744 0.0023298340140827903]
[0.07199507932790919 0.07198603534863536 … 0.07198859299349966 0.071995017790104; 0.004173630571335018 4.6191767690938325e-34 … 0.0026298884586591536 0.0041664244297611]
[0.09634579206667453 0.09632984681193643 … 0.09633412101648231 0.09634567399379049; 0.006676211888525428 6.934873923757612e-34 … 0.004060917377954477 0.006662711969808952]
[0.1206812680926302 0.12065526905151462 … 0.1206618529370015 0.12068105702750248; 0.009853691310599 9.64290547275173e-34 … 0.005784363860491114 0.009830330601194781]
[0.1450007114816121 0.14496097925801335 … 0.14497051166089883 0.14500035994329577; 0.013714929631473617 1.2723773752982496e-33 … 0.007788007080055941 0.013677406839114099]
[0.1693032610197695 0.16924565599166444 … 0.16925881538494889 0.16930271135296024; 0.01826577647204192 1.6165262499841943e-33 … 0.010063941948642876 0.018209229564488182]
⋮
[1.0385752156887353 1.0340921165535901 … 1.03477911038836 1.0385058294133163; 0.6343307920012656 3.6074790509346257e-32 … 0.2597250519725562 0.6297451771316646]
[1.0433476555389323 1.0388205387820237 … 1.039514018332502 1.04327756408024; 0.6399421547157282 3.637214644036565e-32 … 0.26191803839607647 0.6353124236987555]
[1.0463385855795477 1.0417837846404365 … 1.0424813426603208 1.046268050548031; 0.6434698397041693 3.655903536946323e-32 … 0.26329645679280594 0.6388123656651705]
[1.047901640125766 1.0433323388819464 … 1.044032032900378 1.0478308727419585; 0.645317060822444 3.6656880700021267e-32 … 0.2640181652105382 0.6406450566967268]
[1.0487185330763111 1.0441416427459433 … 1.0448424546229953 1.0486476440787889; 0.6462836845052583 3.670807629433297e-32 … 0.26439579792203355 0.6416040760501582]
[1.049037582750251 1.0444577261691323 … 1.045158974955603 1.0489666462177778; 0.646661462958075 3.6728083583632383e-32 … 0.26454337989061 0.6419788823648036]xxxxxxxxxxtsol=solve(inival,system,[t0,tend],control=control)The call to solve corresponds to a new API for transient solutions. It returns a solution object which is compatible to that from DifferentialEquations.jl.
In particular, a TransientSolution tsol can be accessed as follows:
tsol[it]contains the solution for timestepitsol[ispec,:,it]contains the solution for componentispecat timestepitsol(t)returns a (linearly) interpolated solution value fort.tsol.t[it]is the corresponding timetsol[ispec,ix,it]refers to solution of componentispecat nodeixat momentit
Time:
From the solution we now can calculate the normal flux via our test function "trick", once again through the API provided by VoronoiFVM:
-0.00586532
-0.00987319
-0.0147437
-0.0204071
-0.0268231
-0.0339665
-0.041821
-0.0503754
-0.0596217
-0.0695539
-0.080167
-0.0914568
-0.103419
-0.11605
-0.129346
-0.143301
-0.157911
-0.17317
-0.189073
-0.205612
-0.682065
-0.700085
-0.715222
-0.727183
-0.736008
-0.742043
-0.745836
-0.747822
-0.748861
-0.749267
xxxxxxxxxxbegin outflow_rate=Float64[] for i=2:length(tsol) ofr=integrate(system,T,tsol[i],tsol[i-1],tsol.t[i]-tsol.t[i-1]) push!(outflow_rate,ofr[2]) endoutflow_rateendFor increasing time, the outflow rate should approach the value we calculated from the stationary solution:
The overall amount of species which left the domain is can be calculated integrating the discrete outflow rate over time
6.35637165496992begin I_all=0.0 for i=1:length(tsol)-1 I_all-=outflow_rate[i]*(tsol.t[i+1]-tsol.t[i]) end I_allendThe amount of species created via the source term (measured in F) integrated over time should be equal to the sum of the amount of species left in the domain at the very end of the evolution and the amount of species which left the domain:
2×1 Matrix{Float64}:
0.7854274358734347
0.35820090915664426xxxxxxxxxxUend=integrate(system,storage,tsol[end])trueF[1]*(tend-t0) ≈ ( Uend[1] + Uend[2] + I_all )Status `/tmp/jl_9t6uW1/Project.toml` [cfc395e8] ExtendableGrids v0.7.4 [7f904dfe] PlutoUI v0.7.4 [d330b81b] PyPlot v2.9.0 [57bfcd06] SimplexGridFactory v0.5.1 [f7e6ffb2] Triangulate v1.0.1 [82b139dc] VoronoiFVM v0.10.8