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```
1516for `` 0≤ t≤ 1 `` .
1617The 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
3031To 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.
3132Since 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
3534using OrdinaryDiffEqVerner
3635using DiffEqDevTools: analyticless_test_convergence
3736
3837# solver and tolerances to compute reference solution
3938test_setup = Dict(:alg => Vern9(), :reltol => 1e-14, :abstol => 1e-14)
40-
41- # choose step sizes
42- dts = 0.5 .^ (5:10)
43-
4439nothing # 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])
6567end
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
8395The 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
128137for `` 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.
130139Hence, 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]]
136145prob = PDSProblem(P, D, [0.9; 0.1], (0.0, 1.0))
137146
138147nothing # 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