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)
xxxxxxxxxx
function 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)
end
ExtendableGrids.ExtendableGrid{Float64, Int32};
dim: 2 nodes: 631 cells: 1159 bfaces: 101
xxxxxxxxxx
grid=make_grid()
Let us define the following reaction - diffusion system in
with boundary conditions
The source
xxxxxxxxxx
function storage(f,u,node)
f.=u
end;
xxxxxxxxxx
function 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;
xxxxxxxxxx
r(u1,u2)= u1-0.1*u2;
xxxxxxxxxx
function reaction(f,u,node)
f[1]= r(u[1],u[2])
f[2]=-r(u[1],u[2])
end;
xxxxxxxxxx
function source(f,node)
f[1]=1.0
end;
... 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)
xxxxxxxxxx
physics=VoronoiFVM.Physics(num_species=2,
flux=flux,
storage=storage,
reaction=reaction,
source=source)
xxxxxxxxxx
begin
system=VoronoiFVM.System(grid,physics)
enable_species!(system,1,[1])
enable_species!(system,2,[1])
end
xxxxxxxxxx
boundary_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.0
xxxxxxxxxx
inival=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.642655
xxxxxxxxxx
u=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_species
num_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.35857367795902967
xxxxxxxxxx
U=integrate(system,storage,u)
Amount of species created by source term per unit time:
2×1 Matrix{Float64}:
0.7499999999999993
0.0
xxxxxxxxxx
F=integrate(system,(f,u,node)->source(f,node),u)
Amount of reaction per unit time:
2×1 Matrix{Float64}:
0.7499999999999992
-0.7499999999999992
xxxxxxxxxx
R=integrate(system,reaction,u)
Reaction == creation ?
Let us check our first identity: creation rate = reaction rate:
true
F[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)
end
Write
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
xxxxxxxxxx
I=integrate(system,T,u)
Check that none of
true
xxxxxxxxxx
isapprox(I[1],0.0,atol=1.0e-16)
Check that creation of
true
R[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.
xxxxxxxxxx
t0=0.0; tend=10;
xxxxxxxxxx
control=VoronoiFVM.NewtonControl();
xxxxxxxxxx
control.Δu_opt=0.025;
xxxxxxxxxx
control.Δt_min=1.0e-4;
xxxxxxxxxx
control.Δ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]
xxxxxxxxxx
tsol=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 timestepi
tsol[ispec,:,it]
contains the solution for componentispec
at timestepi
tsol(t)
returns a (linearly) interpolated solution value fort
.tsol.t[it]
is the corresponding timetsol[ispec,ix,it]
refers to solution of componentispec
at nodeix
at 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
xxxxxxxxxx
begin
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])
end
outflow_rate
end
For 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.35637165496992
begin
I_all=0.0
for i=1:length(tsol)-1
I_all-=outflow_rate[i]*(tsol.t[i+1]-tsol.t[i])
end
I_all
end
The 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.35820090915664426
xxxxxxxxxx
Uend=integrate(system,storage,tsol[end])
true
F[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