Skip to content

Commit 750cb04

Browse files
committed
Merge branch 'main' into paper-2025-joss
2 parents 65f6040 + afbdc41 commit 750cb04

8 files changed

Lines changed: 255 additions & 177 deletions

File tree

.github/workflows/SpellCheck.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ jobs:
1010
- name: Checkout Actions Repository
1111
uses: actions/checkout@v4
1212
- name: Check spelling
13-
uses: crate-ci/typos@v1.31.1
13+
uses: crate-ci/typos@v1.31.2

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "PositiveIntegrators"
22
uuid = "d1b20bf0-b083-4985-a874-dc5121669aa5"
33
authors = ["Stefan Kopecz, Hendrik Ranocha, and contributors"]
4-
version = "0.2.12-DEV"
4+
version = "0.2.13-DEV"
55

66
[deps]
77
FastBroadcast = "7034ab61-46d4-4ed7-9d0f-46aef9175898"

docs/Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e"
44
DiffEqCallbacks = "459566f4-90b8-5000-8ac3-15dfb0a30def"
55
DiffEqDevTools = "f3b72e0c-5b89-59e1-b016-84e28bfd966d"
66
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
7+
DoubleFloats = "497a8b3b-efae-58df-a0af-a86822472b78"
78
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
89
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
910
LinearSolve = "7ed4a6bd-45f5-4d41-b270-4a48e9bafcae"
@@ -25,6 +26,7 @@ DiffEqBase = "6.160"
2526
DiffEqCallbacks = "4"
2627
DiffEqDevTools = "2.45.1"
2728
Documenter = "1"
29+
DoubleFloats = "1.4"
2830
InteractiveUtils = "1"
2931
LinearAlgebra = "1"
3032
LinearSolve = "3.7.1"

docs/src/convergence.md

Lines changed: 112 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# [Experimental convergence order of MPRK schemes](@id convergence_mprk)
22

3-
In this tutorial, we check that the implemented MPRK schemes have the expected order of convergence.
3+
In this tutorial, we check that all implemented MPRK schemes can achieve their expected order of convergence.
4+
We also address the issue that some methods suffer from order reduction when the solution gets too close to zero.
45

56
## Conservative production-destruction systems
67

@@ -14,7 +15,7 @@ u_2' & = \sin(2\pi t)^2 u_1 - \cos(\pi t)^2 u_2, & u_2(0)&=0.1,
1415
```
1516
for ``0≤ t≤ 1``.
1617
The PDS is conservative since the sum of the right-hand side terms equals zero.
17-
An implementation of the problem is given next.
18+
An implementation of this problem is given next.
1819

1920

2021
```@example eoc
@@ -29,94 +30,102 @@ nothing # hide
2930

3031
To use `analyticless_test_convergence` from [DiffEqDevTools.jl](https://github.com/SciML/DiffEqDevTools.jl), we need to pick a solver to compute the reference solution and specify tolerances.
3132
Since the problem is not stiff, we use the high-order explicit solver `Vern9()` from [OrdinaryDiffEqVerner.jl](https://docs.sciml.ai/OrdinaryDiffEq/stable/).
32-
Moreover, we choose time step sizes to investigate the convergence behavior.
33-
3433
```@example eoc
3534
using OrdinaryDiffEqVerner
3635
using DiffEqDevTools: analyticless_test_convergence
3736
3837
# solver and tolerances to compute reference solution
3938
test_setup = Dict(:alg => Vern9(), :reltol => 1e-14, :abstol => 1e-14)
40-
41-
# choose step sizes
42-
dts = 0.5 .^ (5:10)
43-
4439
nothing # hide
4540
```
4641

47-
### Second-order MPRK schemes
48-
49-
First, we test several second-order MPRK schemes.
42+
To keep the code short, we also define an auxiliary function that outputs a convergence table, which lists the errors obtained with the respective time step size ``Δ t`` as well as the estimated order of convergence in parentheses.
5043

5144
```@example eoc
52-
# select schemes
53-
algs2 = [MPRK22(0.5); MPRK22(2.0 / 3.0); MPRK22(1.0); SSPMPRK22(0.5, 1.0)]
54-
labels2 = ["MPRK22(0.5)"; "MPRK22(2.0/3.0)"; "MPRK22(1.0)"; "SSPMPRK22(0.5, 1.0)"]
45+
using Printf: @sprintf
46+
using PrettyTables: pretty_table
5547
56-
# compute errors and experimental order of convergence
57-
err_eoc = []
58-
for i in eachindex(algs2)
59-
sim = analyticless_test_convergence(dts, prob, algs2[i], test_setup)
48+
# auxiliary function
49+
function convergence_table(dts, prob, algs, labels, test_setup)
50+
# compute errors and estimated convergence orders
51+
err_eoc = []
52+
for i in eachindex(algs)
53+
sim = analyticless_test_convergence(dts, prob, algs[i], test_setup)
54+
55+
err = sim.errors[:l∞]
56+
eoc = [NaN; -log2.(err[2:end] ./ err[1:(end - 1)])]
57+
58+
push!(err_eoc, tuple.(err, eoc))
59+
end
6060
61-
err = sim.errors[:l∞]
62-
eoc = [NaN; -log2.(err[2:end] ./ err[1:(end - 1)])]
61+
# gather data for table
62+
data = hcat(dts, reduce(hcat,err_eoc))
6363
64-
push!(err_eoc, tuple.(err, eoc))
64+
# print table
65+
formatter = (v, i, j) -> (j>1) ? (@sprintf "%5.2e (%4.2f) " v[1] v[2]) : (@sprintf "%5.2e " v)
66+
pretty_table(data, formatters = formatter, header = ["Δt"; labels])
6567
end
68+
69+
nothing # hide
6670
```
6771

68-
Next, we print a table with the computed data.
69-
The table lists the errors obtained with the respective time step size ``Δ t`` as well as the estimated order of convergence in parentheses.
72+
### Second-order and third-order MPRK schemes
73+
74+
First, we test several second-order and third-order MPRK schemes.
7075

7176
```@example eoc
72-
using Printf: @sprintf
73-
using PrettyTables: pretty_table
77+
# choose step sizes
78+
dts = 0.5 .^ (5:10)
79+
80+
# select 2nd order schemes
81+
algs2 = [MPRK22(0.5); MPRK22(2.0 / 3.0); MPRK22(1.0); SSPMPRK22(0.5, 1.0); MPDeC(2)]
82+
labels2 = ["MPRK22(0.5)"; "MPRK22(2.0/3.0)"; "MPRK22(1.0)"; "SSPMPRK22(0.5, 1.0)"; "MPDeC(2)"]
83+
84+
# select 3rd order schemes
85+
algs3 = [MPRK43I(1.0, 0.5); MPRK43I(0.5, 0.75); MPRK43II(0.5); MPRK43II(2.0 / 3.0);
86+
SSPMPRK43(); MPDeC(3)]
87+
labels3 = ["MPRK43I(1.0,0.5)"; "MPRK43I(0.5, 0.75)"; "MPRK43II(0.5)"; "MPRK43II(2.0/3.0)";
88+
"SSPMPRK43()"; "MPDeC(3)"]
7489
75-
# gather data for table
76-
data = hcat(dts, reduce(hcat,err_eoc))
90+
convergence_table(dts, prob, algs2, labels2, test_setup)
7791
78-
# print table
79-
formatter = (v, i, j) -> (j>1) ? (@sprintf "%5.2e (%4.2f) " v[1] v[2]) : (@sprintf "%5.2e " v)
80-
pretty_table(data, formatters = formatter, header = ["Δt"; labels2])
92+
convergence_table(dts, prob, algs3, labels3, test_setup)
8193
```
8294

8395
The table shows that all schemes converge as expected.
8496

85-
### Third-order MPRK schemes
97+
### Higher-order MPRK schemes
8698

87-
In this section, we proceed as above, but consider third-order MPRK schemes instead.
99+
To actually see the order of higher-order methods we need to use more accurate floating-point numbers. Here, we use [`DoubleFloats`](https://github.com/JuliaMath/DoubleFloats.jl).
88100

89101
```@example eoc
90-
# select 3rd order schemes
91-
algs3 = [MPRK43I(1.0, 0.5); MPRK43I(0.5, 0.75); MPRK43II(0.5); MPRK43II(2.0 / 3.0);
92-
SSPMPRK43()]
93-
labels3 = ["MPRK43I(1.0,0.5)"; "MPRK43I(0.5, 0.75)"; "MPRK43II(0.5)"; "MPRK43II(2.0/3.0)";
94-
"SSPMPRK43()"]
102+
using DoubleFloats
95103
96-
# compute errors and experimental order of convergence
97-
err_eoc = []
98-
for i in eachindex(algs3)
99-
sim = analyticless_test_convergence(dts, prob, algs3[i], test_setup)
104+
# define problem using Double64
105+
P(u, p, t) = [0 cospi(t)^2 * u[2]; sinpi(2 * t)^2 * u[1] 0]
106+
u0 = [Double64(9) / 10; Double64(1) / 10]
107+
tspan = (Double64(0), Double64(1))
108+
prob_d64 = ConservativePDSProblem(P, u0, tspan)
100109
101-
err = sim.errors[:l∞]
102-
eoc = [NaN; -log2.(err[2:end] ./ err[1:(end - 1)])]
110+
# choose step sizes
111+
dts_d64 = Double64(1/2) .^ (5:9)
103112
104-
push!(err_eoc, tuple.(err, eoc))
105-
end
113+
# select higher-order schemes
114+
algs4 = [MPDeC(4); MPDeC(5); MPDeC(6); MPDeC(7); MPDeC(8); MPDeC(9); MPDeC(10)]
115+
labels4 = ["MPDeC(4)"; "MPDeC(5)"; "MPDeC(6)"; "MPDeC(7)"; "MPDeC(8)"; "MPDeC(9)"; "MPDeC(10)"]
106116
107-
# gather data for table
108-
data = hcat(dts, reduce(hcat,err_eoc))
117+
# solver and tolerances to compute reference solution
118+
test_setup_d64 = Dict(:alg => Vern9(), :reltol => 1e-30, :abstol => 1e-30)
109119
110-
# print table
111-
formatter = (v, i, j) -> (j>1) ? (@sprintf "%5.2e (%4.2f) " v[1] v[2]) : (@sprintf "%5.2e " v)
112-
pretty_table(data, formatters = formatter, header = ["Δt"; labels3])
120+
# compute errors and experimental order of convergence
121+
convergence_table(dts_d64, prob_d64, algs4, labels4, test_setup_d64)
113122
```
114123

115-
As above, the table shows that all schemes converge as expected.
124+
Again, all schemes show the expected converge order.
116125

117126
## Non-conservative PDS
118127

119-
In this section we consider the non-autonomous but non-conservative test problem
128+
Next, we consider the non-autonomous but also non-conservative test problem
120129

121130
```math
122131
\begin{aligned}
@@ -126,60 +135,77 @@ u_2' & = \sin(2\pi t)^2 u_1 - \cos(\pi t)^2 u_2 - \sin(\pi t)^2 u_2, & u_2(0)&=0
126135
```
127136

128137
for ``0≤ t≤ 1``.
129-
Since the sum of the right-hand side terms does not cancel, the PDS is indeed non-conservative.
138+
Since the sum of the right-hand side terms does not vanish, the PDS is indeed non-conservative.
130139
Hence, we need to use [`PDSProblem`](@ref) for its implementation.
131140

132141
```@example eoc
133-
# choose problem
134-
P(u, p, t) = [0.0 cos.(π * t) .^ 2 * u[2]; sin.(2 * π * t) .^ 2 * u[1] 0.0]
135-
D(u, p, t) = [cos.(2 * π * t) .^ 2 * u[1]; sin.(π * t) .^ 2 * u[2]]
142+
# PDS
143+
P(u, p, t) = [0.0 cospi(t)^2 * u[2]; sinpi(2 * t)^2 * u[1] 0.0]
144+
D(u, p, t) = [cospi(2 * t)^2 * u[1]; sinpi(t)^2 * u[2]]
136145
prob = PDSProblem(P, D, [0.9; 0.1], (0.0, 1.0))
137146
138147
nothing # hide
139148
```
140149

141-
The following sections will show that the selected MPRK schemes show the expected convergence order also for this non-conservative PDS.
150+
The following tables demonstrate that the chosen MPRK schemes converge as expected also for this non-conservative PDS.
142151

143-
### Second-order MPRK schemes
152+
### Second-order and third-order MPRK schemes
144153

145154
```@example eoc
146-
# compute errors and experimental order of convergence
147-
err_eoc = []
148-
for i in eachindex(algs2)
149-
sim = analyticless_test_convergence(dts, prob, algs2[i], test_setup)
155+
convergence_table(dts, prob, algs2, labels2, test_setup)
150156
151-
err = sim.errors[:l∞]
152-
eoc = [NaN; -log2.(err[2:end] ./ err[1:(end - 1)])]
157+
convergence_table(dts, prob, algs3, labels3, test_setup)
158+
```
153159

154-
push!(err_eoc, tuple.(err, eoc))
155-
end
160+
### Higher-order MPRK schemes
156161

157-
# gather data for table
158-
data = hcat(dts, reduce(hcat,err_eoc))
162+
```@example eoc
163+
# problem implementation using DoubleFloats
164+
P(u, p, t) = [0 cospi(t)^2 * u[2]; sinpi(2 * t)^2 * u[1] 0]
165+
D(u, p, t) = [cospi(2 * t)^2 * u[1]; sinpi(t)^2 * u[2]]
166+
prob_d64 = PDSProblem(P, D, [Double64(9)/10; Double64(1)/10], (Double64(0), Double64(1)))
159167
160-
# print table
161-
formatter = (v, i, j) -> (j>1) ? (@sprintf "%5.2e (%4.2f) " v[1] v[2]) : (@sprintf "%5.2e " v)
162-
pretty_table(data, formatters = formatter, header = ["Δt"; labels2])
168+
convergence_table(dts_d64, prob_d64, algs4, labels4, test_setup_d64)
163169
```
164170

165-
### Third-order MPRK schemes
171+
## Order reduction
172+
173+
It was shown in [Torlo, Öffner, Ranocha: Issues with positivity-preserving Patankar-type schemes with positivity-preserving Patankar-type schemes](https://doi.org/10.1016/j.apnum.2022.07.014) that some MPRK methods
174+
suffer from order reduction if the solution of the PDS is too close to zero.
175+
We demonstrate this by solving a problem where one component of the initial condition is equal to zero.
176+
177+
The problem is
178+
179+
```math
180+
\begin{aligned}
181+
u_1' &= -u_1, & u_1(0)&=1, \\
182+
u_2' & = u_1, & u_2(0)&=0,
183+
\end{aligned}
184+
```
185+
186+
for ``0≤ t≤ 1`` and can be implemented as follows.
187+
166188

167189
```@example eoc
168-
# compute errors and experimental order of convergence
169-
err_eoc = []
170-
for i in eachindex(algs3)
171-
sim = analyticless_test_convergence(dts, prob, algs3[i], test_setup)
190+
# PDS
191+
P(u, p, t) = [0 0; u[1] 0]
192+
prob = ConservativePDSProblem(P, [1.0; 0.0], (0.0, 1.0))
193+
nothing # hide
194+
```
172195

173-
err = sim.errors[:l∞]
174-
eoc = [NaN; -log2.(err[2:end] ./ err[1:(end - 1)])]
196+
Next, we generate the corresponding convergence tables as in the sections above.
175197

176-
push!(err_eoc, tuple.(err, eoc))
177-
end
198+
```@example eoc
199+
test_setup = Dict(:alg => Vern9(), :reltol => 1e-14, :abstol => 1e-14)
200+
201+
dts = 0.5 .^ (6:12)
178202
179-
# gather data for table
180-
data = hcat(dts, reduce(hcat,err_eoc))
203+
convergence_table(dts, prob, algs2, labels2, test_setup)
204+
convergence_table(dts, prob, algs3, labels3, test_setup)
205+
convergence_table(dts, prob, algs4, labels4, test_setup)
206+
207+
nothing # hide
208+
```
181209

182-
# print table
183-
formatter = (v, i, j) -> (j>1) ? (@sprintf "%5.2e (%4.2f) " v[1] v[2]) : (@sprintf "%5.2e " v)
184-
pretty_table(data, formatters = formatter, header = ["Δt"; labels3])
185-
```
210+
We find that all methods apart from MPDeC(``K``) methods with ``K ≥ 3`` converge as expected.
211+
The MPDeC(``K``) methods with ``K ≥ 3`` suffer from order reduction and show convergence order 2 instead of ``K``.

0 commit comments

Comments
 (0)