The Julia library PositiveIntegrators.jl provides several time integration methods developed to preserve the positivity of numerical solutions.
PositiveIntegrators.jl is a registered Julia package. Thus, you can install it from the Julia REPL via
julia> using Pkg; Pkg.add("PositiveIntegrators")If you want to update PositiveIntegrators.jl, you can use
julia> using Pkg; Pkg.update("PositiveIntegrators")As usual, if you want to update PositiveIntegrators.jl and all other packages in your current project, you can execute
julia> using Pkg; Pkg.update()Modified Patankar-Runge-Kutta (MPRK) schemes are unconditionally positive and conservative time integration schemes for the solution of positive and conservative ODE systems. The application of these methods is based on the representation of the ODE system as a so-called production-destruction system (PDS).
The application of MPRK schemes requires the ODE system to be represented as a production-destruction system (PDS). A PDS takes the general form
where \boldsymbol u=(u_1,\dots,u_n)^T is the vector of unknowns and
both production terms p_{ij}(t,\boldsymbol u) and destruction terms
d_{ij}(t,\boldsymbol u) must be nonnegative for all i,j=1,\dots,N.
The meaning behind p_{ij} and d_{ij} is as follows:
p_{ij}withi\ne jrepresents the sum of all nonnegative terms which appear in equationiwith a positive sign and in equationjwith a negative sign.d_{ij}withi\ne jrepresents the sum of all nonnegative terms which appear in equationiwith a negative sign and in equationjwith a positive sign.p_{ii}represents the sum of all nonnegative terms which appear in equationiand don't have a negative counterpart in one of the other equations.d_{ii}represents the sum of all negative terms which appear in equationiand don't have a positive counterpart in one of the other equations.
This naming convention leads to p_{ij} = d_{ji} for i≠ j and therefore
a PDS is completely defined by the production matrix \mathbf{P}=(p_{ij})_{i,j=1,\dots,N}
and the destruction vector \mathbf{d}=(d_{ii})_{i=1,\dots,N}.
As an example we consider the Lotka-Volterra model
which always has positive solutions if positive initial values are supplied.
Assuming u_1,u_2>0, the above naming scheme results in
where all remaining production and destruction terms are zero.
Consequently the production matrix \mathbf P and destruction vector \mathbf d are
import Pkg; Pkg.add("OrdinaryDiffEq"); Pkg.add("Plots")
To solve this PDS together with initial values u_1(0)=u_2(0)=2
on the time domain (0,10), we first need to create a PDSProblem.
using PositiveIntegrators # load PDSProblem
function Pd(u, p, t)
P = [2*u[1] 0; u[1]*u[2] 0] # Production matrix
d = [0; u[2]] # Destruction vector
return P, d
end
u0 = [2.0; 2.0] # initial values
tspan = (0.0, 10.0) # time span
# Create PDS
prob = PDSProblem(Pd, u0, tspan)
nothing #hide
Now that the problem has been created, we can solve it with any method
of PositiveIntegrators.jl.
In the following, we use the method MPRK22(1.0). In addition, we could
also use any method provided by OrdinaryDiffEq.jl,
but these might possibly generate negative approximations.
sol = solve(prob, MPRK22(1.0))
nothing # hide
Finally, we can use Plots.jl to visualize the solution.
using Plots
plot(sol)
A PDS with the additional property
for i=1,\dots,N is called conservative. In this case we have
p_{ij}=d_{ji} for all i,j=1,\dots,N, which leads to
This shows that the sum of the state variables of a conservative PDS remains constant over time, i.e.
for all times t>0.
One specific example of a conservative PDS is the SIR model
with N=S+I+R and \beta,\gamma>0. Assuming S,I,R>0 the production and destruction terms are given by
where the remaining production and destruction terms are zero.
The corresponding production matrix \mathbf P is
The following example shows how to implement the above SIR model with \beta=0.4, \gamma=0.04, initial conditions S(0)=997, I(0)=3, R(0)=0 and time domain (0, 100) using ConservativePDSProblem from PositiveIntegrators.jl.
import Pkg; Pkg.add("OrdinaryDiffEq");
using PositiveIntegrators
# Out-of-place implementation of the P matrix for the SIR model
function P(u, p, t)
S, I, R = u
β = 0.4
γ = 0.04
N = 1000.0
P = zeros(3,3)
P[2,1] = β*S*I/N
P[3,2] = γ*I
return P
end
u0 = [997.0; 3.0; 0.0]; # initial values
tspan = (0.0, 100.0); # time span
# Create SIR problem
prob = ConservativePDSProblem(P, u0, tspan)
nothing # hide
Since the SIR model is not only conservative but also positive, we can use
any scheme from PositiveIntegrators.jl
to solve it. Here we use MPRK22(1.0).
Please note that any method from OrdinaryDiffEq.jl
can be used as well, but might possibly generate negative approximations.
sol = solve(prob, MPRK22(1.0))
nothing # hide
Finally, we can use Plots.jl to visualize the solution.
using Plots
plot(sol, label = ["S" "I" "R"], legend=:right)
plot!(sol.t, sum.(sol.u), label = "S+I+R") # Plot S+I+R over time.
We see that there is always a nonnegative number of people in each compartment, while the population S+I+R remains constant over time.
If you use PositiveIntegrators.jl for your research, please cite it using the bibtex entry
@misc{PositiveIntegrators.jl,
title={{PositiveIntegrators.jl}: {A} {J}ulia library of positivity-preserving
time integration methods},
author={Kopecz, Stefan and Ranocha, Hendrik and contributors},
year={2023},
doi={10.5281/zenodo.10868393},
url={https://github.com/SKopecz/PositiveIntegrators.jl}
}This project is licensed under the MIT license (see License). Since it is an open-source project, we are very happy to accept contributions from the community. Please refer to the section Contributing for more details.