Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Some benchmarks for the documentation #123

Draft
wants to merge 69 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
235f684
Added structure for benchmarks
SKopecz Sep 6, 2024
968c8da
bugfix and first benchmark
SKopecz Sep 6, 2024
67b3460
typo
SKopecz Sep 6, 2024
c16d9a9
extended npzd benchmark
SKopecz Sep 6, 2024
5dbfde5
bugfix
SKopecz Sep 6, 2024
7d94271
additional npzd benchmark
SKopecz Sep 6, 2024
2a05883
Added Robertson benchmark
SKopecz Sep 6, 2024
b2db32f
bugfix
SKopecz Sep 6, 2024
15caf2c
Stratospheric reaction benchmark
SKopecz Sep 8, 2024
4140152
Added benchmark convergence order
SKopecz Sep 8, 2024
181890f
added PrettyTables to toml file and bugfix
SKopecz Sep 8, 2024
93af536
???
SKopecz Sep 8, 2024
eb59b61
???
SKopecz Sep 8, 2024
0c4f9b8
???
SKopecz Sep 8, 2024
9495976
debugging
SKopecz Sep 8, 2024
e18f22b
last try ...
SKopecz Sep 8, 2024
12682d0
cont. debugging
SKopecz Sep 8, 2024
322d705
more debugging
SKopecz Sep 8, 2024
a4c80f2
more debugging
SKopecz Sep 8, 2024
d9552b4
last try
SKopecz Sep 8, 2024
37e2341
bugfix
SKopecz Sep 8, 2024
22b16fa
fingers crossed
SKopecz Sep 8, 2024
2eea5e2
Update docs/src/npzd_model_benchmark.md
SKopecz Sep 9, 2024
20c638f
some test
SKopecz Sep 9, 2024
0ab9edb
added workprecision functions
SKopecz Sep 10, 2024
d3bbcbe
skip some parts of docs
SKopecz Sep 10, 2024
4a63ce7
revised npzd benchmarks
SKopecz Sep 10, 2024
b20f73e
add benchmarks with fixed time step size
SKopecz Sep 10, 2024
7d527ee
bugfix
SKopecz Sep 10, 2024
f6f145b
bugfix
SKopecz Sep 10, 2024
cf38f90
revised npzd benchmark
SKopecz Sep 10, 2024
afc847f
typo
SKopecz Sep 10, 2024
256663e
revised robertson benchmark
SKopecz Sep 10, 2024
d566a4e
revised stratospheric reaction benchmark
SKopecz Sep 10, 2024
e5919ed
revision of benchmarks
SKopecz Sep 11, 2024
46fdb62
revised functions for work-precision diagrams and revised nzpd benchmark
SKopecz Sep 20, 2024
5792829
robertson_benchmark is running (but needs revision)
SKopecz Sep 20, 2024
ca4d84a
stratospheric reaction benchmark is running (but needs revision)
SKopecz Sep 20, 2024
4180a94
typo
SKopecz Sep 20, 2024
dbe2c8f
typos and additional explanations in npzd benchmark
SKopecz Sep 23, 2024
ea91519
revised converenge tutorial
SKopecz Sep 23, 2024
7e3fa1d
use non-autonomous tests in convergence benchmark
SKopecz Sep 23, 2024
8311a3e
revised convergence benchmark
SKopecz Sep 24, 2024
0d822d1
revised robertson benchmark
SKopecz Sep 24, 2024
2330132
revised robertson benchmark
SKopecz Sep 25, 2024
85bdf09
typo
SKopecz Sep 25, 2024
370314c
revised benchmarks
SKopecz Sep 27, 2024
49d7d58
format
SKopecz Sep 27, 2024
cbce69a
bugfix
SKopecz Sep 27, 2024
6ff35b6
benchmarks finished
SKopecz Oct 2, 2024
ace698d
bugfix
SKopecz Oct 2, 2024
693dc9b
revised benchmark npzd
SKopecz Oct 9, 2024
867af11
revised benchmarks once more
SKopecz Oct 10, 2024
59ff4cb
bugfix
SKopecz Oct 10, 2024
58912bd
added MPRK 0.5 to comparison
SKopecz Oct 11, 2024
e3fbe32
benchmark robertson mprk comparison changed axis
SKopecz Nov 4, 2024
bc183ca
Merge branch 'main' into sk/benchmarks
ranocha Nov 5, 2024
09da0af
fix implicit imports test
ranocha Nov 5, 2024
e559644
add compat entry for statistics
ranocha Nov 5, 2024
8b58819
hopefully fix Aqua CI error
ranocha Nov 5, 2024
0e68827
fix typo
ranocha Nov 5, 2024
ccc7177
robertson bechnmark comparison mprk xticks
SKopecz Nov 5, 2024
da63ff0
robertson benchmark use MPRK22(0.5) in all comparisons
SKopecz Nov 8, 2024
f296c69
added tests for utility functions
SKopecz Nov 8, 2024
5c793ad
revised robertson benchmark with respect to MPRK22(0.5)
SKopecz Nov 8, 2024
8243fe1
using median in runtests.jl
SKopecz Nov 8, 2024
96161ac
additional output in tests
SKopecz Nov 8, 2024
36bddfa
set n=1000 in work_precision_XXX
SKopecz Nov 12, 2024
da96e89
set n=10000 in work_precision_XXX
SKopecz Nov 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
[deps]
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
DiffEqDevTools = "f3b72e0c-5b89-59e1-b016-84e28bfd966d"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
LinearSolve = "7ed4a6bd-45f5-4d41-b270-4a48e9bafcae"
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"

[compat]
BenchmarkTools = "1"
DiffEqDevTools = "2.44"
Documenter = "1"
InteractiveUtils = "1"
LinearAlgebra = "1.7"
LinearSolve = "2.21"
OrdinaryDiffEq = "6.59"
Pkg = "1"
Plots = "1"
PrettyTables = "2.3"
SparseArrays = "1.7"
StaticArrays = "1.5"
6 changes: 6 additions & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ makedocs(modules = [PositiveIntegrators],
"Heat Equation, Neumann BCs" => "heat_equation_neumann.md",
"Heat Equation, Dirichlet BCs" => "heat_equation_dirichlet.md",
],
"Benchmarks" => [
"Experimental order of convergence" => "convergence.md",
"NPZD model" => "npzd_model_benchmark.md",
"Robertson problem" => "robertson_benchmark.md",
"Stratospheric reaction problem" => "stratospheric_reaction_benchmark.md",
],
"Troubleshooting, FAQ" => "faq.md",
"API reference" => "api_reference.md",
"Contributing" => "contributing.md",
Expand Down
119 changes: 119 additions & 0 deletions docs/src/convergence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# [Experimental convergence order of MPRK schemes](@id convergence_mprk)

## Second order MPRK schemes

```@example eoc
using PositiveIntegrators

# choose schemes
algs = [MPRK22(0.5)
MPRK22(2.0 / 3.0)
MPRK22(1.0)
SSPMPRK22(0.5, 1.0)]

names = ["MPRK22(0.5)"
"MPRK22(2.0/3.0)"
"MPRK22(1.0)"
"SSPMPRK22(0.5, 1.0)"]

prob = prob_pds_linmod

#nothing #hide
#```
#```@example eoc
using DiffEqDevTools

dts = 0.5 .^ (5:10)
err = Vector{Vector{Float64}}(undef, length(algs))
eoc = Vector{Vector{Float64}}(undef, length(algs))

#compute errors and experimental order of convergence
for i in eachindex(algs)
sim = test_convergence(dts, prob, algs[i])
err[i] = sim.errors[:l∞]
eoc[i] = -log2.(err[i][2:end] ./ err[i][1:(end - 1)])
end
#```
#
#```@example eoc
using PrettyTables

# collect data and create headers
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coding this almost drove me crazy!

Originally I used something like data = dts outside the loop and data = [data err[i] [NaN; eoc[i]]] within the loop. This was perfectly fine on my local machine, but caused an error like "data not defined" in the build process.

@ranocha: Any idea what the problem was/is?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you built the docs locally or copy and paste the code to the REPL?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh. I didn't build the docs locally, I copied & pasted everything from my vscode repl to the markdown files. But I ensured to use the docs environment.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then it might be an issue of soft global scope, see https://docs.julialang.org/en/v1/manual/variables-and-scoping/#on-soft-scope

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may need to add a keyword global

N = 1 + 2*length(algs)
data = Matrix{Float64}(undef, length(dts), N)
data[:,1] = dts
header = Matrix{String}(undef, 1, N)
header[1] = "Δt"
subheader = Matrix{String}(undef, 1, N)
subheader[1] = ""
for i in eachindex(algs)
#data = [data err[i] [NaN; eoc[i]]]
data[:, 2*i] = err[i]
data[:, 2*i+1] = [NaN; eoc[i]]
#header = [header names[i] names[i]]
header[1, 2*i] = names[i]
header[1, 2*i+1] = names[i]
#subheader = [subheader "Error" "EOC"]
subheader[1, 2*i] = "Error"
subheader[1, 2*i+1] = "EOC"
end

# print table
pretty_table(data; header = (header, subheader),
formatters = (ft_printf("%5.4e", [1, 2, 4, 6, 8]),
ft_printf("%5.4f", [3, 5, 7, 9])))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we don't need all of the digits - this would also keep the tables smaller so that we don't have to scroll as much. And maybe we can just show "EOC" right next to the value so that the headers in the first row are not duplicated.


```

## Third order MPRK schemes


```@example eoc
algs = [MPRK43I(1.0, 0.5)
MPRK43I(0.5, 0.75)
MPRK43II(0.5)
MPRK43II(2.0 / 3.0)
SSPMPRK43()]

names = ["MPRK43I(1.0,0.5)"
"MPRK43I(0.5, 0.75)"
"MPRK43II(0.5)"
"MPRK43II(2.0/3.0)"
"SSPMPRK43()"]


dts = 0.5 .^ (5:10)
err = Vector{Vector{Float64}}(undef, length(algs))
eoc = Vector{Vector{Float64}}(undef, length(algs))

#compute errors and experimental order of convergence
for i in eachindex(algs)
sim = test_convergence(dts, prob, algs[i])
err[i] = sim.errors[:l∞]
eoc[i] = -log2.(err[i][2:end] ./ err[i][1:(end - 1)])
end

# collect data and create headers
N = 1 + 2*length(algs)
data = Matrix{Float64}(undef, length(dts), N)
data[:,1] = dts
header = Matrix{String}(undef, 1, N)
header[1] = "Δt"
subheader = Matrix{String}(undef, 1, N)
subheader[1] = ""
for i in eachindex(algs)
#data = [data err[i] [NaN; eoc[i]]]
data[:, 2*i] = err[i]
data[:, 2*i+1] = [NaN; eoc[i]]
#header = [header names[i] names[i]]
header[1, 2*i] = names[i]
header[1, 2*i+1] = names[i]
#subheader = [subheader "Error" "EOC"]
subheader[1, 2*i] = "Error"
subheader[1, 2*i+1] = "EOC"
end

pretty_table(data, header = (header, subheader),
formatters = (ft_printf("%5.4e", [1, 2, 4, 6, 8, 10]),
ft_printf("%5.4f", [3, 5, 7, 9, 11])))
```
208 changes: 208 additions & 0 deletions docs/src/npzd_model_benchmark.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# [Benchmark: Solution of an NPZD model](@id benchmark-npzd)

We use the NPZD model [`prob_pds_npzd`](@ref) to assess the efficiency of different solvers.

Standard methods have difficulties to solve this problem accurately, at least for low tolerances.

```@example NPZD
using OrdinaryDiffEq, PositiveIntegrators
using Plots

# select problem
prob = prob_pds_npzd

# compute reference solution (standard tolerances are too low)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean not strict enough? The question is if you consider 1e-5 to be lower (smaller) than 1e-13

ref_sol = solve(prob, Vern7(); abstol = 1e-14, reltol = 1e-13)

# compute solutions with low tolerances
SKopecz marked this conversation as resolved.
Show resolved Hide resolved
abstol = 1e-2
reltol = 1e-1
sol_Ros23 = solve(prob, Rosenbrock23(); abstol, reltol)
sol_MPRK = solve(prob, MPRK22(1.0); abstol, reltol)

# plot solutions
p1 = plot(ref_sol, linestyle = :dash, label = "", legend = :right)
plot!(p1, sol_Ros23; denseplot = false, markers = :circle, ylims = (-1.0, 10.0),
title = "Rosenbrock23", label = ["N" "P" "Z" "D"])
p2 = plot(ref_sol, linestyle = :dash, label = "", legend = :right)
plot!(p2, sol_MPRK; denseplot = false, markers = true, ylims = (-1.0, 10.0),
title = "MPRK22(1.0)", label = ["N" "P" "Z" "D"])
plot(p1, p2)
```

Nevertheless, [OrdinaryDiffEq.jl](https://docs.sciml.ai/OrdinaryDiffEq/stable/) provides the solver option `isoutofdomain`, which can be used to guarantee nonnegative solutions.

```@example NPZD
sol_Ros23 = solve(prob, Rosenbrock23(); abstol, reltol,
isoutofdomain = isnegative) #reject negative solutions

plot(ref_sol, linestyle = :dash, label = "", legend = :right)
plot!(sol_Ros23; denseplot = false, markers = :circle, ylims = (-1.0, 10.0),
title = "Rosenbrock23", label = ["N" "P" "Z" "D"])
```

## Work-Precision diagrams

First we compare different (adaptive) MPRK schemes described in the literature. The chosen `l∞` error computes the maximum of the absolute values of the difference between the numerical solution and the reference solution over all components and all time steps.

```@example NPZD
using DiffEqDevTools #load WorkPrecisionSet

# choose methods to compare
setups = [Dict(:alg => MPRK22(0.5))
Dict(:alg => MPRK22(2.0 / 3.0))
Dict(:alg => MPRK22(1.0))
Dict(:alg => SSPMPRK22(0.5, 1.0))
Dict(:alg => MPRK43I(1.0, 0.5))
Dict(:alg => MPRK43I(0.5, 0.75))
Dict(:alg => MPRK43II(0.5))
Dict(:alg => MPRK43II(2.0 / 3.0))]

labels = ["MPRK22(0.5)"
"MPPRK22(2/3)"
"MPRK22(1.0)"
"SSPMPRK22(0.5,1.0)"
"MPRK43I(1.0,0.5)"
"MPRK43I(0.5,0.75)"
"MPRK43II(0.5)"
"MPRK43II(2.0/3.0)"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern seems to be present several times. Maybe we shall overload show to automate this? But we would likely lose some information so I am not fully sure...


# set tolerances and error
abstols = 1.0 ./ 10.0 .^ (2:0.5:8)
reltols = 1.0 ./ 10.0 .^ (1:0.5:7)
err_est = :l∞
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do other error choices lead to significantly different results?


# create reference solution for `WorkPrecisionSet`
test_sol = TestSolution(ref_sol)

# compute work-precision
wp = WorkPrecisionSet(prob, abstols, reltols, setups;
error_estimate = err_est, appxsol = test_sol,
names = labels, print_names = true,
verbose = false)

#plot
plot(wp, title = "NPZD benchmark", legend = :topright,
color = permutedims([repeat([1],3)...,2,repeat([3],2)...,repeat([4],2)...]),
ylims = (10 ^ -5, 10 ^ -1), yticks = 10.0 .^ (-5:.5:-1), minorticks=10,
xlims = (10 ^ -7, 10 ^ 0), xticks =10.0 .^ (-6:1:0))
```

The second- and third-order methods behave very similarly. For comparisons with other schemes from [OrdinaryDiffEq.jl](https://docs.sciml.ai/OrdinaryDiffEq/stable/) we choose the schemes with the smallest error for the initial tolerances, respectively. These are `SSPMPRK22(0.5, 1.0)` and `MPRK43I(1.0, 0.5)`.

```@example NPZD
sol_SSPMPRK22 = solve(prob, SSPMPRK22(0.5, 1.0); abstol, reltol)
sol_MPRK43 = solve(prob, MPRK43I(1.0, 0.5); abstol, reltol)

p1 = plot(ref_sol, linestyle = :dash, label = "", legend = :right)
plot!(p1, sol_SSPMPRK22; denseplot = false, markers = :circle, ylims = (-1.0, 10.0),
title = "SSPMPRK22(0.5, 1.0)", label = ["N" "P" "Z" "D"])
p2 = plot(ref_sol, linestyle = :dash, label = "", legend = :right)
plot!(p2, sol_MPRK43; denseplot = false, markers = true, ylims = (-1.0, 10.0),
title = "MPRK43I(1.0, 0.5)", label = ["N" "P" "Z" "D"])
plot(p1, p2)
```

Although the SSPMPRK22 solution seems to be more accurate at first glance, the `l∞`-error of the SSPMPRK22 scheme is 0.506359, whereas the `l∞`-error of the MPRK43 scheme is 0.413915. Both errors occurs at approximately $t=2$, where there is a sharp kink in the first component.


Next we compare `SSPMPRK22(0.5, 1.0)` and `MPRK43I(1.0, 0.5)` with some second and third order methods from [OrdinaryDiffEq.jl](https://docs.sciml.ai/OrdinaryDiffEq/stable/). To guarantee positive solutions with these methods, we must select the solver option `isoutofdomain = isnegative`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is isoutofdomain = isnegative to get good work-precision diagrams? How would they look like without it?


```@example NPZD
# select methods
setups = [Dict(:alg => SSPMPRK22(0.5, 1.0)),
Dict(:alg => MPRK43I(1.0, 0.5)),
Dict(:alg => Midpoint(), :isoutofdomain => isnegative),
Dict(:alg => Heun(), :isoutofdomain => isnegative),
Dict(:alg => Ralston(), :isoutofdomain => isnegative),
Dict(:alg => TRBDF2(), :isoutofdomain => isnegative),
Dict(:alg => SDIRK2(), :isoutofdomain => isnegative),
Dict(:alg => Kvaerno3(), :isoutofdomain => isnegative),
Dict(:alg => KenCarp3(), :isoutofdomain => isnegative),
Dict(:alg => Rodas3(), :isoutofdomain => isnegative),
Dict(:alg => ROS2(), :isoutofdomain => isnegative),
Dict(:alg => ROS3(), :isoutofdomain => isnegative),
Dict(:alg => Rosenbrock23(), :isoutofdomain => isnegative)]

labels = ["SSPMPRK22(0.5,1.0)"
"MPRK43I(1.0,0.5)"
"Midpoint"
"Heun"
"Ralston"
"TRBDF2"
"SDIRK2"
"Kvearno3"
"KenCarp3"
"Rodas3"
"ROS2"
"ROS3"
"Rosenbrock23"]

# compute work-precision
wp = WorkPrecisionSet(prob, abstols, reltols, setups;
error_estimate = err_est, appxsol = test_sol,
names = labels, print_names = true,
verbose = false)
plot(wp, title = "NPZD benchmark", legend = :topright,
color = permutedims([2, 3, repeat([4], 3)..., repeat([5], 4)..., repeat([6], 4)...]),
ylims = (10 ^ -5, 10 ^ -1), yticks = 10.0 .^ (-5:.5:-1), minorticks=10,
xlims = (10 ^ -7, 10 ^ 0), xticks =10.0 .^ (-6:1:0))
```

Comparison to recommend solvers.
```@example NPZD
setups = [Dict(:alg => SSPMPRK22(0.5, 1.0)),
Dict(:alg => MPRK43I(1.0, 0.5)),
Dict(:alg => Tsit5(), :isoutofdomain => isnegative),
Dict(:alg => BS3(), :isoutofdomain => isnegative),
Dict(:alg => Vern6(), :isoutofdomain => isnegative),
Dict(:alg => Vern7(), :isoutofdomain => isnegative),
Dict(:alg => Vern8(), :isoutofdomain => isnegative),
Dict(:alg => TRBDF2(), :isoutofdomain => isnegative),
Dict(:alg => Rosenbrock32(), :isoutofdomain => isnegative),
Dict(:alg => Rodas5P(), :isoutofdomain => isnegative),
Dict(:alg => Rodas4P(), :isoutofdomain => isnegative)]

labels = ["SSPMPRK22(0.5,1.0)"
"MPRK43I(1.0,0.5)"
"Tsit6"
"BS3"
"Vern6"
"Vern7"
"Vern8"
"TRBDF2"
"Rosenbrock23"
"Rodas5P"
"Rodas4P"]

# compute work-precision
wp = WorkPrecisionSet(prob, abstols, reltols, setups;
error_estimate = err_est, appxsol = test_sol,
names = labels, print_names = true,
verbose = false)

#plot
plot(wp, title = "NPZD benchmark", legend = :topright,
color = permutedims([2, 3, repeat([4], 5)..., 5, repeat([6], 3)...]),
ylims = (10^-5, 10^-1), yticks = 10.0 .^ (-5:0.5:-1), minorticks = 10,
xlims = (10 ^ -7, 10 ^ 0), xticks =10.0 .^ (-6:1:0))
```

## Literature
- Kopecz, Meister 2nd order
- Kopecz, Meister 3rd order
- Huang, Shu 2nd order

## Package versions

These results were obtained using the following versions.
```@example NPZD
using InteractiveUtils
versioninfo()
println()

using Pkg
Pkg.status(["PositiveIntegrators", "StaticArrays", "LinearSolve", "OrdinaryDiffEq"],
mode=PKGMODE_MANIFEST)
nothing # hide
```
Loading