API updates
Here we describe some updates for the API of VoronoiFVM.jl
. These have been implemented mostly on top of the existing API, whose functionality is not affected.
TableOfContents(; aside = false, depth = 5)
begin
import Pkg as _Pkg
haskey(ENV, "PLUTO_PROJECT") && _Pkg.activate(ENV["PLUTO_PROJECT"])
using Revise
using VoronoiFVM
using ExtendableGrids
using ExtendableSparse
using Test
using PlutoUI
using GridVisualize
using LinearSolve
using ILUZero
using LinearAlgebra
using CairoMakie
CairoMakie.activate!(; type = "png", visible = false)
GridVisualize.default_plotter!(CairoMakie)
end;
v0.19
This is a breaking release. Implementations using default solver settings should continue to work (albeit possibly with deprecation and allocation warnings). Really breaking is control of iterative linear solvers and allocation checks.
Solve now a method of CommonSolve.solve
As a consequence, all VoronoiFVM.solve methods with signatures others than solve(system; kwargs...)
are now deprecated
n = 100
100
begin
h = 1.0 / convert(Float64, n)
const eps = 1.0e-2
function reaction(f, u, node, data)
f[1] = u[1]^2
end
function flux(f, u, edge, data)
f[1] = eps * (u[1, 1]^2 - u[1, 2]^2)
end
function source(f, node, data)
x1 = node[1] - 0.5
x2 = node[2] - 0.5
f[1] = exp(-20.0 * (x1^2 + x2^2))
end
function storage(f, u, node, data)
f[1] = u[1]
end
function bcondition(f, u, node, data)
boundary_dirichlet!(f,
u,
node;
species = 1,
region = 2,
value = ramp(node.time; dt = (0, 0.1), du = (0, 1)))
boundary_dirichlet!(f,
u,
node;
species = 1,
region = 4,
value = ramp(node.time; dt = (0, 0.1), du = (0, 1)))
end
sys0 = VoronoiFVM.System(0.0:h:1.0,
0.0:h:1.0;
reaction,
flux,
source,
storage,
bcondition,
species = [1],)
end
VoronoiFVM.System{Float64, Float64, Int32, Int64, Matrix{Int32}}( grid = ExtendableGrids.ExtendableGrid{Float64, Int32}(dim=2, nnodes=10201, ncells=20000, nbfaces=400), physics = Physics(flux=flux, storage=storage, reaction=reaction, source=source, breaction=bcondition, ), num_species = 1)
Deprecated call:
begin inival = unknowns(sys0; inival = 0.1) sol00 = unknowns(sys0) solve!(sol00, inival, sys0) end
Replace this by:
sol0 = solve(sys0; inival = 0.1)
1×10201 VoronoiFVM.DenseSolutionArray{Float64, 2}: 1.02241e-33 0.0319717 0.045243 0.0554731 … 0.045243 0.0319717 1.02241e-33
Docstring of solve
solve(system; kwargs...)
Built-in solution method for VoronoiFVM.System
.
Keyword arguments:
General for all solvers
inival
(default: 0) : Array created viaunknowns
or number giving the initial value.control
(default: nothing): Pass instance ofSolverControl
All elements of
SolverControl
can be used as kwargs. Eventually overwrites values given viacontrol
params
: Parameters (Parameter handling is experimental and may change)
Stationary solver: Invoked if neither
times
norembed
, nortstep
are given as keyword argument.time
(default:0.0
): Set time value.
Returns a
DenseSolutionArray
orSparseSolutionArray
Embedding (homotopy) solver: Invoked if
embed
kwarg is given. Use homotopy embedding + damped Newton's method to solve stationary problem or to solve series of parameter dependent problems. Parameter step control is performed according to solver control data. kwargs and default values are:embed
(default:nothing
): vector of parameter values to be reached exactly
In addition, all kwargs of the implicit Euler solver (besides
times
) are handled. Returns a transient solution objectsol
containing the stored solution(s), seeTransientSolution
.Implicit Euler transient solver: Invoked if
times
kwarg is given. Use implicit Euler method + damped Newton's method to solve time dependent problem. Time step control is performed according to solver control data. kwargs and default values are:times
(default:nothing
): vector of time values to be reached exactlypre
(default:(sol,t)->nothing
): callback invoked before each time steppost
(default:(sol,oldsol, t, Δt)->nothing
): callback invoked after each time stepsample
(default:(sol,t)->nothing
): callback invoked after timestep for all times intimes[2:end]
.delta
(default:(system, u,v,t, Δt)->norm(sys,u-v,Inf)
): Value used to control the time step sizeΔu
If
control.handle_error
is true, if time step solution throws an error, stepsize is lowered, and step solution is called again with a smaller time value. Ifcontrol.Δt<control.Δt_min
, solution is aborted with error. Returns a transient solution objectsol
containing the stored solution, seeTransientSolution
.Implicit Euler timestep solver. Invoked if
tstep
kwarg is given. Solve one time step of the implicit Euler method.time
(default:0
): Set time value.tstep
: time step
Returns a
DenseSolutionArray
orSparseSolutionArray
Docstring of SolverControl
SolverControl
SolverControl(;kwargs...)
Solver control parameter for time stepping, embedding, Newton method and linear solver control. All field names can be used as keyword arguments for solve(system::VoronoiFVM.AbstractSystem; kwargs...)
Newton's method solves \(F(u)=0\) by the iterative procedure \(u_{i+1}=u_{i} - d_i F'(u_i)^{-1}F(u_i)\) starting with some initial value \(u_0\), where \(d_i\) is a damping parameter.
verbose::Union{Bool, String}
: Verbosity control. A collection of output categories is given in a string composed of the following letters:a: allocation warnings
d: deprecation warnings
e: time/parameter evolution log
n: newton solver log
l: linear solver log
Alternatively, a Bool value can be given, resulting in
true: "neda"
false: "da"
Switch off all output including deprecation warnings via
verbose=""
. In the output, corresponding messages are marked e.g. via '[n]',[a]
etc. (besides of '[l]')
abstol::Float64
: Tolerance (in terms of norm of Newton update): terminate if \(\Delta u_i=||u_{i+1}-u_i||_\infty <\)abstol
.
reltol::Float64
: Tolerance (relative to the size of the first update): terminate if \(\Delta u_i/\Delta u_1<\)reltol
.
maxiters::Int64
: Maximum number of newton iterations.
tol_round::Float64
: Tolerance for roundoff error detection: terminate if \(|\;||u_{i+1}||_1 - ||u_{i}||_1\;|/ ||u_{i}||_1<\)tol_round
occurredmax_round
times in a row.
tol_mono::Float64
: Tolerance for monotonicity test: terminate with error if \(\Delta u_i/\Delta u_{i-1}>\)1/tol_mono
.
damp_initial::Float64
: Initial damping parameter \(d_0\). To handle convergence problems, set this to a value less than 1.
damp_growth::Float64
: Damping parameter growth factor: \(d_{i+1}=\min(d_i\cdot\)max_growth
\(,1)\). It should be larger than 1.
max_round::Int64
: Maximum number of consecutive iterations within roundoff error tolerance The default effectively disables this criterion.
unorm::Function
: Calculation of Newton update norm
rnorm::Function
: Functional for roundoff error calculation
method_linear::Union{Nothing, LinearSolve.SciMLLinearSolveAlgorithm}
: Solver method for linear systems (see LinearSolve.jl). If givennothing
, as default are chosen:UMFPACKFactorization()
for sparse matrices with Float64SparspakFactorization()
for sparse matrices with general number types.Defaults from LinearSolve.jl for tridiagonal and banded matrices
Users should experiment with what works best for their problem.
For an overeview on possible alternatives, see
VoronoiFVM.LinearSolverStrategy
.
reltol_linear::Float64
: Relative tolerance of iterative linear solver.
abstol_linear::Float64
: Absolute tolerance of iterative linear solver.
maxiters_linear::Int64
: Maximum number of iterations of linear solver
precon_linear::Union{Nothing, Function, ExtendableSparse.AbstractFactorization, LinearSolve.SciMLLinearSolveAlgorithm, Type}
: Constructor for preconditioner for linear systems. This should work as a functionprecon_linear(A)
which takes an AbstractSparseMatrixCSC (e.g. an ExtendableSparseMatrix) and returns a preconditioner object in the sense ofLinearSolve.jl
, i.e. which has anldiv!(u,A,v)
method. Useful examples:ExtendableSparse.ILUZero
ExtendableSparse.Jacobi
For easy access to this functionality, see see also
VoronoiFVM.LinearSolverStrategy
.
keepcurrent_linear::Bool
: Update preconditioner in each Newton step ? Translates toreuse_precs=!keepcurrent_linear
for LinearSolve.
Δp::Float64
: Initial parameter step for embedding.
Δp_max::Float64
: Maximal parameter step size.
Δp_min::Float64
: Minimal parameter step size.
Δp_grow::Float64
: Maximal parameter step size growth.
Δp_decrease::Float64
: Parameter step decrease factor upon rejection
Δt::Float64
: Initial time step size.
Δt_max::Float64
: Maximal time step size.
Δt_min::Float64
: Minimal time step size.
Δt_grow::Float64
: Maximal time step size growth.
Δt_decrease::Float64
: Time step decrease factor upon rejection
Δu_opt::Float64
: Optimal size of update for time stepping and embedding. The algorithm tries to keep the difference in norm between "old" and "new" solutions approximately at this value.
Δu_max_factor::Float64
: Control maximum sice of updateΔu
for time stepping and embeding relative toΔu_opt
. Time steps withΔu > Δu_max_factor*Δu_opt
will be rejected.
force_first_step::Bool
: Force first timestep.
num_final_steps::Int64
: Number of final steps to adjust at end of time interval in order to prevent breakdown of step size.
handle_exceptions::Bool
: Handle exceptions during transient solver and parameter embedding. Iftrue
, exceptions in Newton solves are caught, the embedding resp. time step is lowered, and solution is retried. Moreover, if embedding or time stepping fails (e.g. due to reaching minimal step size), a warning is issued, and a solution is returned with all steps calculated so far.Otherwise (by default) errors are thrown.
store_all::Bool
: Store all steps of transient/embedding problem:
in_memory::Bool
: Store transient/embedding solution in memory
log::Any
: Record history
edge_cutoff::Float64
: Edge parameter cutoff for rectangular triangles.
pre::Function
: Functionpre(sol,t)
called before time/embedding step
post::Function
: Functionpost(sol,oldsol,t,Δt)
called after successful time/embedding step
sample::Function
: Functionsample(sol,t)
to be called for eacht in times[2:end]
delta::Function
: Time step error estimator. A functionΔu=delta(system,u,uold,t,Δt)
to calculateΔu
.
tol_absolute::Union{Nothing, Float64}
tol_relative::Union{Nothing, Float64}
damp::Union{Nothing, Float64}
damp_grow::Union{Nothing, Float64}
max_iterations::Union{Nothing, Int64}
tol_linear::Union{Nothing, Float64}
max_lureuse::Union{Nothing, Int64}
mynorm::Union{Nothing, Function}
myrnorm::Union{Nothing, Function}
Rely on LinearSolve.jl for linear system solution
This provides easy access to a large variety of linear solvers:
LU factorization from UMFPACK
umf_sol = solve(sys0; inival = 0.1, method_linear = UMFPACKFactorization(), verbose = true)
1×10201 VoronoiFVM.DenseSolutionArray{Float64, 2}: 1.02241e-33 0.0319717 0.045243 0.0554731 … 0.045243 0.0319717 1.02241e-33
@test isapprox(umf_sol, sol0, atol = 1.0e-7)
Test Passed
LU factorization from Sparspak.jl
sppk_sol = solve(sys0; inival = 0.1, method_linear = SparspakFactorization(), verbose = true)
1×10201 VoronoiFVM.DenseSolutionArray{Float64, 2}: 1.02241e-33 0.0319717 0.045243 0.0554731 … 0.045243 0.0319717 1.02241e-33
@test isapprox(sppk_sol, sol0, atol = 1.0e-7)
Test Passed
Iterative solvers
BICGstab from Krylov.jl with diagonal (Jacobi) preconditioner
The Jacobi preconditioner is defined in ExtendableSparse.jl.
krydiag_sol = solve(sys0;
inival = 0.1,
method_linear = KrylovJL_BICGSTAB(),
precon_linear = JacobiPreconditioner,
verbose = true,)
1×10201 VoronoiFVM.DenseSolutionArray{Float64, 2}: 1.02242e-33 0.0319717 0.045243 0.0554731 … 0.045243 0.0319717 1.02242e-33
@test isapprox(krydiag_sol, sol0, atol = 1.0e-5)
Test Passed
BICGstab from Krylov.jl with delayed factorization preconditioner
krydel_sol = solve(sys0;
inival = 0.1,
method_linear = KrylovJL_BICGSTAB(),
precon_linear = SparspakFactorization(),
verbose = "nlad",)
1×10201 VoronoiFVM.DenseSolutionArray{Float64, 2}: 1.02241e-33 0.0319717 0.045243 0.0554731 … 0.045243 0.0319717 1.02241e-33
@test isapprox(krydel_sol, sol0, atol = 1.0e-5)
Test Passed
BICGstab from Krylov.jl with ilu0 preconditioner
ILUZeroPreconditioner
is exported from ExtendableSparse and wraps the predonditioner defined in ILUZero.jl .
kryilu0_sol = solve(sys0;
inival = 0.5,
method_linear = KrylovJL_BICGSTAB(),
precon_linear = ILUZeroPreconditioner,
verbose = true,)
1×10201 VoronoiFVM.DenseSolutionArray{Float64, 2}: 1.05391e-33 0.0319717 0.045243 0.0554731 … 0.045243 0.0319717 1.05573e-33
@test isapprox(kryilu0_sol, sol0, atol = 1.0e-5)
Test Passed
New verbosity handling
verbose
can now be a Bool or a String of flag characters, allowing for control of different output categories. I would love to do this via logging, but there is still a long way to go IMHOAllocation check is active by default with warnings which can be muted by passing a
verbose
string without 'a'. This is now the only control in this respect. Allcheck_allocs
methods/kwargs, control via environment variables have been removed.Deprecation warnings can be switched off by passing a
verbose
string without 'd'.Improve iteration logging etc., allow for logging of linear iterations ('l' flag character)
The following example gives some information in this respect:
D = 0.1
0.1
function xflux(f, u, edge, data)
f[1] = D * (u[1, 1]^2 - u[1, 2]^2)
end
xflux (generic function with 1 method)
xsys = VoronoiFVM.System(0:0.001:1; flux = xflux, species = [1])
VoronoiFVM.System{Float64, Float64, Int32, Int64, Matrix{Int32}}( grid = ExtendableGrids.ExtendableGrid{Float64, Int32}(dim=1, nnodes=1001, ncells=1000, nbfaces=2), physics = Physics(flux=xflux, storage=default_storage, ), num_species = 1)
solve(xsys; inival = 0.1, times = [0, 1]);
If we find these warnings annoying, we can switch them off:
solve(xsys; inival = 0.1, times = [0, 1], verbose = "");
Or we get some more logging:
solve(xsys; inival = 0.1, times = [0, 1], verbose = "en");
But we can also look for the reasons of the allocations. Here, global values should be declared as constants.
const D1 = 0.1
0.1
function xflux1(f, u, edge, data)
f[1] = D1 * (u[1, 1]^2 - u[1, 2]^2)
end
xflux1 (generic function with 1 method)
xsys1 = VoronoiFVM.System(0:0.001:1; flux = xflux1, species = [1])
VoronoiFVM.System{Float64, Float64, Int32, Int64, Matrix{Int32}}( grid = ExtendableGrids.ExtendableGrid{Float64, Int32}(dim=1, nnodes=1001, ncells=1000, nbfaces=2), physics = Physics(flux=xflux1, storage=default_storage, ), num_species = 1)
solve(xsys1; inival = 0.1, times = [0, 1]);
v0.14
VoronoiFVM.System
constructor
Implicit creation of physics
The VoronoiFVM.Physics
struct almost never was used outside of the constructor of VoronoiFVM.System
. Now it is possible to specify the flux functions directly in the system constructor. By default, it is also possible to set a list of species which are attached to all interior and boundary regions of the grid.
grid1 = simplexgrid(0:0.1:1);
function multispecies_flux(y, u, edge, data)
for i = 1:(edge.nspec)
y[i] = u[i, 1] - u[i, 2]
end
end
multispecies_flux (generic function with 1 method)
function test_reaction(y, u, node, data)
y[1] = u[1]
y[2] = -u[1]
end
test_reaction (generic function with 1 method)
begin
system1 = VoronoiFVM.System(grid1;
flux = multispecies_flux,
reaction = test_reaction,
species = [1, 2])
boundary_dirichlet!(system1; species = 1, region = 1, value = 1)
boundary_dirichlet!(system1; species = 2, region = 2, value = 0)
end;
sol1 = solve(system1);
@test isapprox(sum(sol1), 11.323894375033476, rtol = 1.0e-14)
Test Passed
Boundary conditions as part of physics
This makes the API more consistent and opens an easy possibility to have space and time dependent boundary conditions. One can specify them either in breaction
or the synonymous bcondition
.
function bcond2(y, u, bnode, data)
boundary_neumann!(y, u, bnode; species = 1, region = 1, value = sin(bnode.time))
boundary_dirichlet!(y, u, bnode; species = 2, region = 2, value = 0)
end;
system2 = VoronoiFVM.System(grid1;
flux = multispecies_flux,
reaction = test_reaction,
species = [1, 2],
bcondition = bcond2,
check_allocs = false);
sol2 = solve(system2; times = (0, 10), Δt_max = 0.01);
GridVisualizer(Plotter=CairoMakie)
time:
@test isapprox(sum(sol2) / length(sol2), 2.4921650158811794, rtol = 1.0e-14)
Test Passed
Implicit creation of grid
By passing data for grid creation (one to three abstract vectors) instead a grid, a tensor product grid is implicitly created. This example also demonstrates position dependent boundary values.
function bcond3(y, u, bnode, data)
boundary_dirichlet!(y, u, bnode; region = 4, value = bnode[2])
boundary_dirichlet!(y, u, bnode; region = 2, value = -bnode[2])
end;
system3 = VoronoiFVM.System(-1:0.1:1,
-1:0.1:1;
flux = multispecies_flux,
bcondition = bcond3,
species = 1);
sol3 = solve(system3);
@test isapprox(sum(sol3), 0.0, atol = 1.0e-14)
Test Passed
GridVisualize API extended to System
Instead of a grid, a system can be passed to gridplot
and scalarplot
.
scalarplot(system3, sol3; resolution = (300, 300), levels = 10, colormap = :hot)
Parameters of solve
The solve
API has been simplified and made more Julian. All entries of VoronoiFVM.NewtonControl
can be now passed as keyword arguments to solve
.
Another new keyword argument is inival
which allows to pass an initial value which by default is initialized to zero. Therefore we now can write solve(system)
as we already have seen above.
reaction4(y, u, bnode, data) = y[1] = -bnode[1]^2 + u[1]^4;
bc4(f, u, node, data) = boundary_dirichlet!(f, u, node; value = 0);
system4 = VoronoiFVM.System(-10:0.1:10;
species = [1],
reaction = reaction4,
flux = multispecies_flux,
bcondition = bc4);
sol4 = solve(system4; log = true, damp_initial = 0.001, damp_growth = 3);
@test isapprox(sum(sol4), 418.58515700568535, rtol = 1.0e-14)
Test Passed
Built with Julia 1.11.1 and
CairoMakie 0.12.16ExtendableGrids 1.9.2
ExtendableSparse 1.5.3
GridVisualize 1.7.0
ILUZero 0.2.0
LinearAlgebra 1.11.0
LinearSolve 2.34.0
Pkg 1.11.0
PlutoUI 0.7.60
Revise 3.5.18
Test 1.11.0
VoronoiFVM 1.25.1