# paramètres
= 1.0
m = 2.0
μ = [m, μ]
param
= 10.0
etat0
# time horizon
= 10.0
tmax = (0.0, tmax)
tspan = 0.1 tstep
Simuler un modèle semi-discret
Modèles semi-discrets
Les modèles semi-discrets (ou impulsionnels) ont des applications larges en sciences de la vie (Mailleret and Lemesle (2009)). Ils décrivent des variables à dynamiques continues la plupart du temps, perturbées par des évènements discrets à certains moments \tau_k:
\left\{\begin{array}{l} \dot x = f(x),\\ x(\tau_k^+) = g(\tau_k, x(\tau_k)). \end{array}\right.
Une manière classique de les simuler est de faire une boucle intégrant le système continu entre deux instants \tau_k, de combiner les résultats avec la partie discrète, et d’incrémenter.
DifferentialEquations.jl
offre la possibilité de gérer ce type de simulation directement via le solver, sans boucle, par l’intermédiaire d’un DiscreteCallback.
Immigration et dynamique puits
Considérons une population de densité x suivant une dynamique de type puits (mortalité m), avec des immigrations régulières d’individus (tous les T unités de temps), à un taux d’immigration moyen \mu. On obtient le modèle suivant (e.g Mailleret and Grognard (2009)):
\left\{\begin{array}{l} \dot x = -m x,\\ x(kT^+) = x(kT) + \mu T,~\forall k \in\mathbb{N}^*. \end{array}\right.
On définit le système dynamique, la condition initiale, les paramètres:
Ainsi que les instants auxquels ont lieu les évènements discrets:
# inter-pulse period
= 3.5
T
# timing of pulses, first pulse T time units after initial time
= Vector(tspan[1]+T:T:tspan[2]) pulsetimes
Le modèle pour sa partie continue:
# model (continuous dynamics)
function sink(u, param, t)
= param[1]
m = u[1]
x return dx = -m*x
end
implémentation des parties discrètes
On définit le problème d’intégration classiquement:
using DifferentialEquations
# define ODE
= ODEProblem(
tosim
sink,
etat0,
tspan,
param,= tstep,
saveat )
Pour les parties discrètes, on définit la fonction discrète qui s’applique aux instants d’impulsion. Ici on modifie la composante état de l’intégrateur (integrator.u
) en lui rajoutant \mu T (on utilise ici le \mu du namespace global, c’est mal ! mais voir Section 3 pour des choses propres):
# define pulse function
discrete!(integrator) = integrator.u += μ*T
Puis le callback en lui-même; plusieurs solutions sont possibles (e.g. sur des conditions sur l’état, ou le temps), ici il s’agit d’un PresetTimeCallBack
:
# define pulses as callbacks
= PresetTimeCallback(pulsetimes, discrete!) callback
Et on intègre:
# integrate
= solve(tosim, Tsit5(), callback = callback) sol
La représentation graphique montre la dynamique source-puits semi-discrete:
# plot
using CairoMakie
= Figure()
fig
= Axis(
ax 1, 1],
fig[= "time",
xlabel = "population density",
ylabel = "Sink dynamics with pulsed migration",
title
)
lines!(
ax,
sol.t,
sol.u,
)
fig
Lutte biologique et densité dépendance
Le modèle
En second exemple, pour illustrer les dimensions supérieures, nous choisissons le modèle de lutte biologique par augmentation de Nundloll, Mailleret, and Grognard (2010), avec des prédateurs à dynamiques denisté dépendantes négatives (interfèrences de type squabbling).
Le modèle est construit sur la base d’un modèle proies (ravageurs) - prédateurs (ennemis naturels), les proies suivant une croissance logistique et la prédation étant décrite par une réponse de type Holling II. Le modèle s’écrit :
\left\{\begin{array}{l} \dot x = \displaystyle rx\left(1-\frac{x}{K}\right)-\frac{a x}{c + x} y,\\[.25cm] \dot y = \displaystyle \frac{\gamma a x}{c + x} - (m + q y) y,\\[.25cm] y(nT^+) = y(nT) + \mu T. \end{array}\right. avec :
- x la densité population de proies et y celle des prédateurs
- r et K les taux de croissance intrinsèque et la capacité de charge des proies
- a le taux d’attaque maximum, c la constante de demi-saturation et \gamma le facteur de conversion
- m et q les taux de mortalité et de squabbling
Définitions de struct
Pour faire les choses de manière plus structurée que dans l’exemple précédent, nous définissons différents struct
:
ParamSquab
pour les paramètres du modèle d’interactionsParamIntro
pour les paramètres liés aux introductions de prédateursParamTime
pour ce qui concerne l’horizon temporel de simulation
# model parameters struct
@kwdef struct ParamSquab
= 1.0
r = 10.0
K = 1.0
a = 2.0
c = 1.0
γ = 1.0
m = 0.5
q end
# release parameters
@kwdef struct ParamIntro
= 10.0 # release rate
μ = .5 # release period
T end
# time horizon
@kwdef struct ParamTime
= 0.0
tmin = 30.0
tmax = (tmin, tmax)
tspan = 0.1
tstep end
Définition du modèle
Nous définissons les fonctions de croissance et de prédation :
logistic()
qui prend pour arguments la densité de proies et les paramètresholling2()
qui prend pour arguments la densité de proies et les paramètres
# growth function
function logistic(x::Real, param::ParamSquab)
= param # unpack parameters
(; r, K) return r*x*(1 - x/K)
end
# response/predation function
function holling2(x::Real, param::ParamSquab)
= param
(; a, c) return a*x/(c + x)
end
Sur cette base, nous définissons le modèle d’interactions (dynamiques continues) modelsquab()
, qui renvoie les dérivées de l’état en fonction des arguments:
- état (
u
) - paramètres (
param
de typeParamSquab
) - temps (
t
)
# model (continuous dynamics)
function modelsquab(u, param::ParamSquab, t::Real)
= param
(; γ, m, q)
= u[1]
x = u[2]
y
= logistic(x, param) - holling2(x, param)*y
dx = γ*holling2(x, param)*y - (m+q*y)*y
dy
return [dx, dy]
end
fonction d’intégration numérique
Nous définissons la fonction simsquab()
qui renvoie le résultat de la simulation, en fonction des arguments:
- condition initiale (
etat0
) - paramètres du modèle continu (
parsquab
de type `ParamSquab”) - paramètres liés à la partie d’introductions discrètes (
parintro
de typeParIntro
) - paramètres liés à l’horizon temporel de simulation (
partime
de typeParamTime
)
La fonction simsquab()
définit les instants d’introduction (variable interne introtimes
) depuis parintro
et partime
, et crée le callback correspondant pour intégrer les introductions discrètes dans les simulations.
using DifferentialEquations
function simsquab(etat0, parsquab::ParamSquab, parintro::ParamIntro, partime::ParamTime)
# unpack parameters
= parintro
(; μ, T) = partime
(; tspan, tstep)
# define the ODE
= ODEProblem(
tosimsquab
modelsquab,
etat0,
tspan,
parsquab,= tstep,
saveat
)
# timings of pulses, first pulse T times after initial time
= Vector(tspan[1]+T:T:tspan[2])
introtimes
# define pulses for the integrator
intro!(integrator) = integrator.u[2] += μ*T
= PresetTimeCallback(introtimes, intro!)
cbSquab
# solve the semi-discrete model
= solve(tosimsquab, Tsit5(), callback = cbSquab);
solsquab
return solsquab
end
représentation graphique
Finalement, nous exploitons la fonction simsquab()
dans une fonction plotsquab()
pour la représentation graphique. Elle renvoie la figure correspondante sur la base des mêmes arguments:
- condition initiale (
etat0
) - paramètres du modèle continu (
parsquab
de type `ParamSquab”) - paramètres liés à la partie d’introductions discrètes (
parintro
de typeParIntro
) - paramètres liés à l’horizon temporel de simulation (
partime
de typeParamTime
)
using CairoMakie
function plotsquab(etat0, parsquab::ParamSquab, parintro::ParamIntro, partime::ParamTime)
# unpack useful introduction parameters
= parintro
(; μ, T)
# do the simulation
= simsquab(etat0, parsquab, parintro, partime)
solsquab
# do the plot
= Figure()
figsquab
= Axis(
ax 1,1],
figsquab[= "temps",
xlabel = "densités de population",
ylabel = "Modèle de Nundloll et al. 2010 (μ = $μ, T=$T) ",
title
)
lines!(
ax,
solsquab.t,1,:];
solsquab[= 2,
lw = "proies",
label
)
lines!(
ax,
solsquab.t,2,:];
solsquab[= 2,
lw = "prédateurs",
label
)
axislegend(ax; position = :rt)
return figsquab
end
résultats
Finalement, nous définissons des conditions initiales et construisons des paramètres sur la base des valeurs par défaut, pour générer la représentation graphique.
# initial condition
= [10.0, 0.0]
etat0
# construct parameters
= ParamSquab()
parsquab = ParamIntro()
parintro = ParamTime()
partime
plotsquab(etat0, parsquab, parintro, partime)
Dans ces conditions d’introduction (\mu = 10 et T=0.5), l’éradication des proies est GAS.
Nundloll, Mailleret, and Grognard (2010) ont montré que la condition de stabilité de l’éradication est plus difficile à remplir lorsque pour un même taux d’introduction \mu, la période T est plus grande. C’est un résultat que nous illustrons ci-après pour le même taux d’introduction \mu = 10, et une période plus grande T=1.
# construct a new intro strategy with larger T than default (T = 0.5)
= ParamIntro(T = 1.0)
parintro2
# plot the simulation
plotsquab(etat0, parsquab, parintro2, partime)
Nota : pulses dépendant de l’état
Dans les exemple ci-dessus, les composantes discrètes (pulses) des modèles ne sont pas dépendantes de l’état (ou du temps). Si c’est le cas, il faudra modifier la fonction qui définit ce qu’il se passe au callback.
Ainsi pour un modèle avec prélèvements impulsionnels du type (E étant l’effort de prélèvement):
x(nT^+) = e^{-ET} x(nT)
On définira un callback ainsi:
# define times of pulses, first pulse T time units after initial time
= Vector(tspan[1]+T:T:tspan[2])
prelevtimes
# define pulses for the integrator, assume x is the first coordinate
prelev!(integrator) = integrator.u[1] = exp(-E*T) integrator.u[1]
= PresetTimeCallback(prelevtimes, prelev!) cbprelev
Le reste du code restant peu ou prou le même.