510: Mixture
Test mixture diffusion flux. The problem is here that in the flux function we need to solve a linear system of equations which calculates the fluxes from the gradients.
$u_i$ are the species partial pressures, $\vec N_i$ are the species fluxes. $D_i^K$ are the Knudsen diffusion coefficients, and $D^B_{ij}$ are the binary diffusion coefficients.
\[ -\nabla \cdot \vec N_i =0 \quad (i=1\dots n)\\ \frac{\vec N_i}{D^K_i} + \sum_{j\neq i}\frac{u_j \vec N_i - u_i \vec N_j}{D^B_{ij}} = -\vec \nabla u_i \quad (i=1\dots n)\]
From this representation, we can derive the matrix $M=(m_{ij})$ with
\[m_{ii}= \frac{1}{D^K_i} + \sum_{j\neq i} \frac{u_j}{D_ij}\\ m_{ij}= -\sum_{j\neq i} \frac{u_i}{D_ij}\]
such that
\[ M\begin{pmatrix} \vec N_1\\ \vdots\\ \vec N_n \end{pmatrix} = \begin{pmatrix} \vec \nabla u_1\\ \vdots\\ \vec \nabla u_n \end{pmatrix}\]
In the two point flux finite volume discretization, this results into a corresponding linear system which calculates the discrete edge fluxes from the discrete gradients. Here we demonstrate how to implement this in a fast, (heap) allocation free way.
For this purpose, intermediate arrays have to be used. They need to have the same element type as the unknowns passed to the flux function (which could be Float64 or some dual number).
To do so without (heap) allocations can be achieved at least in three ways tested in this example:
- Stack allocation within the flux function using
StrideArrays
.StrideArray
, with the need to have static (compile time) information about the size of the local arrays to be allocated via e.g. a global constant, or, as demonstrated here, a type parameter. As documented in StrideArrays.jl, use@gc_preserve
when passing aStrideArray
as a function parameter. See also this Discourse thread. - Stack allocation within the flux function using
StaticArrays
.MArray
, with the need to have static (compile time) information about the size of the local arrays to be allocated via e.g. a global constant, or, as demonstrated here, a type parameter. However, this may run into this issue, requiring@inbounds
e.g. with reverse order loops. - Preallocation using
PreallocationTools
.DiffCache
. While this avoids the need to pass the size via a compile time constant, one has to ensure that each running thread has its own cache. Here this is achieved by providing a cache for each partition.
module Example510_Mixture
using Printf
using VoronoiFVM
using ExtendableGrids
using GridVisualize
using LinearAlgebra
using AMGCLWrap
using Random
using StrideArraysCore: @gc_preserve, StrideArray, StaticInt, PtrArray
using LinearSolve, ExtendableSparse
using ExtendableSparse: ILUZeroPreconBuilder
using StaticArrays
using ExtendableSparse
using PreallocationTools
using Metis
# Userdata structure for passing number of species as parameter known at compile time.
# Buffers are stack allocated
Base.@kwdef struct MyDataStaticSizeInfo{NSpec}
DBinary::Symmetric{Float64, Matrix{Float64}} = Symmetric(fill(0.1, NSpec, NSpec))
DKnudsen::Vector{Float64} = ones(NSpec)
diribc::Vector{Int} = [1, 2]
end
nspec(::MyDataStaticSizeInfo{NSpec}) where {NSpec} = NSpec
MyDataStaticSizeInfo(nspec; kwargs...) = MyDataStaticSizeInfo{nspec}(; kwargs...)
# Flux with stack allocated buffers using StrideArray
function flux_strided(f, u, edge, data)
T = eltype(u)
M = StrideArray{T}(undef, StaticInt(nspec(data)), StaticInt(nspec(data)))
au = StrideArray{T}(undef, StaticInt(nspec(data)))
du = StrideArray{T}(undef, StaticInt(nspec(data)))
ipiv = StrideArray{Int}(undef, StaticInt(nspec(data)))
for ispec in 1:nspec(data)
M[ispec, ispec] = 1.0 / data.DKnudsen[ispec]
du[ispec] = u[ispec, 1] - u[ispec, 2]
au[ispec] = 0.5 * (u[ispec, 1] + u[ispec, 2])
end
for ispec in 1:nspec(data)
for jspec in 1:nspec(data)
if ispec != jspec
M[ispec, ispec] += au[jspec] / data.DBinary[ispec, jspec]
M[ispec, jspec] = -au[ispec] / data.DBinary[ispec, jspec]
end
end
end
# Pivoting linear system solution via RecursiveFactorizations.jl (see vfvm_functions.jl)
inplace_linsolve!(M, du, ipiv)
for ispec in 1:nspec(data)
f[ispec] = du[ispec]
end
return
end
# Flux with stack allocated buffers using MArray
function flux_marray(f, u, edge, data)
T = eltype(u)
n = nspec(data)
M = MMatrix{nspec(data), nspec(data), T}(undef)
au = MVector{nspec(data), T}(undef)
du = MVector{nspec(data), T}(undef)
ipiv = MVector{nspec(data), Int}(undef)
for ispec in 1:nspec(data)
M[ispec, ispec] = 1.0 / data.DKnudsen[ispec]
du[ispec] = u[ispec, 1] - u[ispec, 2]
au[ispec] = 0.5 * (u[ispec, 1] + u[ispec, 2])
end
for ispec in 1:nspec(data)
for jspec in 1:nspec(data)
if ispec != jspec
M[ispec, ispec] += au[jspec] / data.DBinary[ispec, jspec]
M[ispec, jspec] = -au[ispec] / data.DBinary[ispec, jspec]
end
end
end
# Pivoting linear system solution via RecursiveFactorizations.jl (see vfvm_functions.jl)
inplace_linsolve!(M, du, ipiv)
for ispec in 1:nspec(data)
f[ispec] = du[ispec]
end
return nothing
end
# Userdata structure for passing number of species as a field in the structure, with
# multithreading-aware pre-allocated buffers
Base.@kwdef struct MyDataPrealloc
nspec::Int = 5
npart::Int = 1
DBinary::Symmetric{Float64, Matrix{Float64}} = Symmetric(fill(0.1, nspec, nspec))
DKnudsen::Vector{Float64} = ones(nspec)
diribc::Vector{Int} = [1, 2]
M::Vector{DiffCache{Matrix{Float64}, Vector{Float64}}} = [DiffCache(ones(nspec, nspec), 2 * nspec) for i in 1:npart]
au::Vector{DiffCache{Vector{Float64}, Vector{Float64}}} = [DiffCache(ones(nspec), 2 * nspec) for i in 1:npart]
du::Vector{DiffCache{Vector{Float64}, Vector{Float64}}} = [DiffCache(ones(nspec), 2 * nspec) for i in 1:npart]
ipiv::Vector{Vector{Int}} = [zeros(Int, nspec) for i in 1:npart]
end
nspec(data::MyDataPrealloc) = data.nspec
# Flux using pre-allocated buffers
function flux_diffcache(f, u, edge, data)
T = eltype(u)
n = data.nspec
ipart = partition(edge)
M = get_tmp(data.M[ipart], u)
au = get_tmp(data.au[ipart], u)
du = get_tmp(data.du[ipart], M)
ipiv = data.ipiv[ipart]
for ispec in 1:nspec(data)
M[ispec, ispec] = 1.0 / data.DKnudsen[ispec]
du[ispec] = u[ispec, 1] - u[ispec, 2]
au[ispec] = 0.5 * (u[ispec, 1] + u[ispec, 2])
end
for ispec in 1:nspec(data)
for jspec in 1:nspec(data)
if ispec != jspec
M[ispec, ispec] += au[jspec] / data.DBinary[ispec, jspec]
M[ispec, jspec] = -au[ispec] / data.DBinary[ispec, jspec]
end
end
end
# Pivoting linear system solution via RecursiveFactorizations.jl (see vfvm_functions.jl)
inplace_linsolve!(M, du, ipiv)
for ispec in 1:nspec(data)
f[ispec] = du[ispec]
end
return nothing
end
function bcondition(f, u, node, data)
for species in 1:nspec(data)
boundary_dirichlet!(
f, u, node; species, region = data.diribc[1],
value = species % 2
)
boundary_dirichlet!(
f, u, node; species, region = data.diribc[2],
value = 1 - species % 2
)
end
return nothing
end
function main(;
n = 11, nspec = 5,
dim = 2,
Plotter = nothing,
verbose = "",
unknown_storage = :dense,
flux = :flux_strided,
strategy = nothing,
assembly = :cellwise,
npart = 1
)
h = 1.0 / convert(Float64, n - 1)
X = collect(0.0:h:1.0)
DBinary = Symmetric(fill(0.1, nspec, nspec))
for ispec in 1:nspec
DBinary[ispec, ispec] = 0
end
DKnudsen = fill(1.0, nspec)
if dim == 1
grid = simplexgrid(X)
diribc = [1, 2]
elseif dim == 2
grid = simplexgrid(X, X)
diribc = [4, 2]
else
grid = simplexgrid(X, X, X)
diribc = [4, 2]
end
if npart > 1
grid = partition(grid, PlainMetisPartitioning(; npart), nodes = true, edges = true)
end
function storage(f, u, node, data)
f .= u
return nothing
end
if flux == :flux_strided
_flux = flux_strided
data = MyDataStaticSizeInfo(nspec; DBinary, DKnudsen, diribc)
elseif flux == :flux_diffcache
_flux = flux_diffcache
data = MyDataPrealloc(; nspec, npart = num_partitions(grid), DBinary, DKnudsen, diribc)
else
_flux = flux_marray
data = MyDataStaticSizeInfo(nspec; DBinary, DKnudsen, diribc)
end
sys = VoronoiFVM.System(grid; flux = _flux, storage, bcondition, species = 1:nspec, data, assembly, unknown_storage)
if !isnothing(strategy) && hasproperty(strategy, :precs)
if isa(strategy.precs, BlockPreconBuilder)
strategy.precs.partitioning = A -> partitioning(sys, Equationwise())
end
if isa(strategy.precs, ILUZeroPreconBuilder) && strategy.precs.blocksize != 1
strategy.precs.blocksize = nspec
end
end
control = SolverControl(method_linear = strategy)
control.maxiters = 500
u = solve(sys; verbose, control, log = true)
return norm(u)
end
using Test
function runtests()
if Sys.isapple()
return nothing
# MacOS14 currently crashes here:
# OMP: Error #13: Assertion failure at kmp_runtime.cpp(8114).
# MacOS13 may crash here:
# OMP: Error #13: Assertion failure at kmp_csupport.cpp(607).
end
strategies = [
(method = UMFPACKFactorization(), dims = (1, 2, 3)),
(method = KrylovJL_GMRES(precs = LinearSolvePreconBuilder(UMFPACKFactorization())), dims = (1, 2, 3)),
(method = KrylovJL_GMRES(precs = BlockPreconBuilder(precs = LinearSolvePreconBuilder(UMFPACKFactorization()))), dims = (1, 2, 3)),
(method = KrylovJL_GMRES(precs = BlockPreconBuilder(precs = AMGPreconBuilder())), dims = (2, 3)),
(method = KrylovJL_BICGSTAB(precs = BlockPreconBuilder(precs = AMGPreconBuilder())), dims = (2,)),
(method = KrylovJL_GMRES(precs = BlockPreconBuilder(precs = ILUZeroPreconBuilder())), dims = (2, 3)),
(method = KrylovJL_GMRES(precs = ILUZeroPreconBuilder(blocksize = 5)), dims = (1, 2)),
]
dimtestvals = [4.788926530387466, 15.883072449873742, 52.67819183426213]
for dim in [1, 2, 3]
for assembly in [:edgewise, :cellwise]
for flux in [:flux_marray, :flux_strided, :flux_diffcache]
for strategy in strategies
if dim in strategy.dims
result = main(; dim, assembly, flux, strategy = strategy.method) ≈ dimtestvals[dim]
if !result
@show dim, assembly, flux, strategy
end
@test result
end
end
end
end
end
for dim in [2]
for assembly in [:edgewise, :cellwise]
for flux in [:flux_marray, :flux_strided, :flux_diffcache]
result = main(; dim, n = 100, assembly, flux, npart = 20)
@test result ≈ 141.54097792523987
end
end
end
return nothing
end
end
This page was generated using Literate.jl.