Skip to content

Commit 1285cf6

Browse files
SKopeczranocha
andauthored
Tutorial for scalar PDS (#144)
* added tutorial for scalar PDS * fix referencing * corrections * package versions and corrections * Apply suggestions from code review Co-authored-by: Hendrik Ranocha <[email protected]> * Apply outdated suggestions from code review * Apply outdated suggestion from code review * typo * Apply outdated suggestion from code review --------- Co-authored-by: Hendrik Ranocha <[email protected]>
1 parent a272aa1 commit 1285cf6

File tree

2 files changed

+150
-1
lines changed

2 files changed

+150
-1
lines changed

docs/make.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ makedocs(modules = [PositiveIntegrators],
7979
"Stratospheric reaction problem" => "stratospheric_reaction.md",
8080
"Linear Advection" => "linear_advection.md",
8181
"Heat Equation, Neumann BCs" => "heat_equation_neumann.md",
82-
"Heat Equation, Dirichlet BCs" => "heat_equation_dirichlet.md"
82+
"Heat Equation, Dirichlet BCs" => "heat_equation_dirichlet.md",
83+
"Scalar equation" => "scalar_pds.md"
8384
],
8485
"Troubleshooting, FAQ" => "faq.md",
8586
"API reference" => "api_reference.md",

docs/src/scalar_pds.md

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# [Tutorial: Solving a scalar production-destruction equation with MPRK schemes](@id tutorial-scalar-pds)
2+
3+
Originally, modified Patankar-Runge-Kutta (MPRK) schemes were designed to solve positive and conservative systems of ordinary differential equations.
4+
The conservation property requires that the system consists of at least two scalar differential equations.
5+
Nevertheless, we can also apply the idea of the Patankar trick to a scalar production-destruction system (PDS)
6+
7+
```math
8+
u'(t)=p(u(t))-d(u(t)),\quad u(0)=u_0>0
9+
```
10+
11+
with nonnegative functions ``p`` and ``d``.
12+
Since conservation is not an issue here, we can apply the Patankar trick to the destruction term ``d`` to ensure positivity and leave the production term ``p`` unweighted.
13+
A first-order scheme of this type, based on the forward Euler method, reads
14+
15+
```math
16+
u^{n+1}= u^n + Δ t p(u^n) - Δ t d(u^n)\frac{u^{n+1}}{u^n}
17+
```
18+
and this idea can easily be generalized to higher-order explicit Runge-Kutta schemes.
19+
20+
By closer inspection we realize that this is exactly the approach the MPRK schemes of [PositiveIntegrators.jl](https://github.com/SKopecz/PositiveIntegrators.jl) use to solve non-conservative PDS for which the production matrix is diagonal.
21+
Hence, we can use the existing schemes to solve a scalar PDS by regarding the production term as a ``1×1``-matrix and the destruction term as a ``1``-vector.
22+
23+
# [Example 1](@id scalar-example-1)
24+
25+
We want to solve
26+
27+
```math
28+
u' = u^2 - u,\quad u(0) = 0.95
29+
```
30+
31+
for ``0≤ t≤ 10``.
32+
Here, we can choose ``p(u)=u^2`` as production term and ``d(u)=u`` as destruction term.
33+
The exact solution of this problem is
34+
35+
```math
36+
u(t) = \frac{19}{19+e^{t}}.
37+
```
38+
39+
Next, we show how to solve this scalar PDS in the way discussed above.
40+
Please note that we must use [`PDSProblem`](@ref) to create the problem.
41+
Furthermore, we use static matrices and vectors from [StaticArrays.jl](https://juliaarrays.github.io/StaticArrays.jl/stable/) instead of standard arrays for efficiency.
42+
43+
44+
```@example scalar_example_1
45+
using PositiveIntegrators, StaticArrays, Plots
46+
47+
u0 = @SVector [0.95] # 1-vector
48+
tspan = (0.0, 10.0)
49+
50+
# Attention: Input u is a 1-vector
51+
prod(u, p, t) = @SMatrix [u[1]^2] # create static 1x1-matrix
52+
dest(u, p, t) = @SVector [u[1]] # create static 1-vector
53+
prob = PDSProblem(prod, dest, u0, tspan)
54+
55+
sol = solve(prob, MPRK22(1.0))
56+
57+
# plot
58+
tt = 0:0.1:10
59+
f(t) = 19.0 / (19.0 + exp(t)) # exact solution
60+
plot(tt, f.(tt), label="exact")
61+
plot!(sol, label="u")
62+
```
63+
64+
# [Example 2] (@id scalar-example-2)
65+
66+
Next, we want to compute positive solutions of a more challenging scalar PDS.
67+
In [Example 1](@ref scalar-example-1), we could have also used standard schemes from [OrdinaryDiffEq.jl](https://docs.sciml.ai/OrdinaryDiffEq/stable/) and use the solver option `isoutofdomain` to ensure positivity.
68+
But this is not always the case as the following example will show.
69+
70+
We want to compute the nonnegative solution of
71+
72+
```math
73+
u'(t) = -\sqrt{\lvert u(t)\rvert },\quad u(0)=1.
74+
```
75+
76+
for ``t≥ 0``.
77+
Please note that this initial value problem has infinitely many solutions
78+
79+
```math
80+
u(t) = \begin{cases} \frac{1}{4}(t-2)^2, & 0≤ t< 2,\\ 0, & 2≤ t < t^*,\\ -\frac{1}{4}(t-2)^2, & t^*≤ t, \end{cases}
81+
```
82+
83+
where ``t^*≥ 2`` is arbitrary.
84+
But among these, the only nonnegative solution is
85+
86+
```math
87+
u(t) = \begin{cases} \frac{1}{4}(t-2)^2, & 0≤ t< 2,\\ 0, & 2≤ t. \end{cases}
88+
```
89+
90+
This is the solution we want to compute.
91+
92+
First, we try this using a standard solver from [OrdinaryDiffEq.jl](https://docs.sciml.ai/OrdinaryDiffEq/stable/).
93+
We try to enforce positivity with the solver option `isoutofdomain` by specifying that negative solution components are not acceptable.
94+
95+
```@example
96+
using OrdinaryDiffEqRosenbrock
97+
98+
tspan = (0.0, 3.0)
99+
u0 = 1.0
100+
101+
f(u, p, t) = -sqrt(abs(u))
102+
prob = ODEProblem(f, u0, tspan)
103+
104+
sol = solve(prob, Rosenbrock23(); isoutofdomain = (u, p, t) -> any(<(0), u))
105+
```
106+
107+
We see that `isoutofdomain` cannot be used to ensure nonnegative solutions in this case, as the computation stops at about ``t≈ 2`` before the desired final time is reached.
108+
For at least first- and second-order explicit Runge-Kutta schemes, this can also be shown analytically. A brief computation reveals that to ensure nonnegative solutions, the time step size must tend to zero if the numerical solution tends to zero.
109+
110+
Next, we want to use an MPRK scheme.
111+
We can choose ``p(u)=0`` as the production term and ``d(u)=\sqrt{\lvert u\rvert }`` as the destruction term.
112+
Furthermore, we create the [`PDSProblem`](@ref) in the same way as in [Example 1](@ref scalar-example-1).
113+
114+
```@example
115+
using PositiveIntegrators, StaticArrays, Plots
116+
117+
tspan = (0.0, 3.0)
118+
u0 = @SVector [1.0]
119+
120+
prod(u, p, t) = @SMatrix zeros(1,1)
121+
dest(u, p, t) = @SVector [sqrt(abs(first(u)))]
122+
prob = PDSProblem(prod, dest, u0, tspan)
123+
124+
sol = solve(prob, MPRK22(1.0))
125+
126+
# plot
127+
tt = 0:0.03:3
128+
f(t) = 0.25 * (t - 2)^2 * (t <= 2) # exact solution
129+
plot(tt, f.(tt), label="exact")
130+
plot!(sol, label="u")
131+
```
132+
133+
We can see that the MPRK scheme used is well suited to solve the problem.
134+
135+
136+
## Package versions
137+
138+
These results were obtained using the following versions.
139+
```@example scalar_example_1
140+
using InteractiveUtils
141+
versioninfo()
142+
println()
143+
144+
using Pkg
145+
Pkg.status(["PositiveIntegrators", "StaticArrays", "OrdinaryDiffEqRosenbrock"],
146+
mode = PKGMODE_MANIFEST)
147+
nothing # hide
148+
```

0 commit comments

Comments
 (0)