|
| 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