using DifferentialEquations
using Plots
using DataFrames
# paramètres
= 5.0 # natalité
r = 10.0 # mortalité
K = 1.0 # taux max de prédation
α = 0.5 # constante de demi-saturation
h
= 0.01 # timescale, \epsilon + TAB
ϵ = 5.0 # gain à la prédation
n = 3.0 # mortalité
m
= [r, K, α, h, ϵ, n, m]
par_tord_ois
# temps d'intégration
= (0.0, 400.0)
tspan = 0.02
tstep
# conditions initiales
= 1.0 # tordeuses
x0 = 2.5 # oiseaux
y0 = [x0, y0] etat0
Populations en interaction (1)
La tordeuse du bourgeon de l’épinette (suite)
Pour débuter cette partie sur les populations en interactions, nous reprenons le modèle de tordeuse de Ludwig, Jones, and Holling (1978) en supposant que la population d’oiseaux réagit (lentement) à la démographie des tordeuses, via la prédation.
Le changement principal ici est la dimension du modèle (dimension 2) : les tailles de populations de tordeuses x et d’oiseaux y varient toutes deux au cours du temps en s’influençant l’une l’autre, avec une population d’oiseaux qui varie lentement (d’où le paramètre \varepsilon supposé petit) par rapport à celle des tordeuses.
Le modèle prend la forme : \left\{ \begin{array}{l} \displaystyle \dot x = rx\left(1-\frac{x}{K}\right) - \frac{\alpha x^2}{h^2+x^2}\ y \\[.3cm] \displaystyle \dot y = \varepsilon \left(\frac{n \alpha x^2}{h^2+x^2}\ y -m y\right) \end{array} \right. \tag{1}
Il faut ajuster un peu la manière de coder pour prendre en compte ces deux dimensions. Commençons par les paramètres divers.
Puis le modèle :
function tord_ois(u, p, t)
= p
r, K, α, h, ϵ, n, m = u[1]
x = u[2]
y
= r*x*(1 - x/K) - α*x^2/(h^2 + x^2)*y
dx = ϵ*(n*α*x^2/(h^2 + x^2)*y - m*y)
dy
return [dx, dy]
end
Problème d’intégration et simulation :
= ODEProblem(
prob_tord_ois
tord_ois,
etat0,
tspan,
par_tord_ois;= tstep,
saveat
)
= solve(prob_tord_ois, reltol = 1e-6)
sol_tord_ois
= DataFrame(sol_tord_ois)
sol_tord_ois rename!(sol_tord_ois, :timestamp => :time, :value1 => :x, :value2 => :y)
Représentation graphique contre le temps
# color definitions
= palette(:tab10)[3]
mygreen = palette(:tab10)[2]
myorange = palette(:tab10)[1]
myblue = palette(:tab10)[4]
myred
# initialisation graphique et solution simulée
= plot(
fig1
sol_tord_ois.time,
sol_tord_ois.x;= myblue,
color = 2,
linewidth = "tordeuses \$x\$",
label = "temps",
xlabel = "densités de populations",
ylabel = "Dynamiques des tordeuses avec\n population d'oiseaux variable",
title
)
plot!(
fig1,
sol_tord_ois.time,./ 2, # scale by 2 for aesthetics
sol_tord_ois.y = myorange,
color = 2,
linewidth = "oiseaux \$y/2\$",
label )
On observe ici des bifurcations dynamiques avec le passage de la population de tordeuse d’une branche d’équilibre stable à l’autre, qui créé un comportement de type cycle d’hysteresis.
La situation se comprend bien sur le diagramme de bifurcations (y, x). On retrace le diagramme de bifurcations (cf. la page sur les populations exploitées).
Code
using Symbolics
using Polynomials
@variables X
= Differential(X)
D
# lieu des équilibres positifs
= r/(α*X)*(1-X/K)*(h^2+X^2)
Y
# dénominateur de la dérivée
= Symbolics.arguments(Symbolics.value(simplify(expand_derivatives(D(Y)))))[1]
dnumerator
# on récupère les coefficients X^k du polynôme
= Symbolics.value(dnumerator).dict
coefs_dict = Dict(Symbolics.degree(first(kv)) => kv[2] for kv ∈ coefs_dict)
dd # on rajoute dans le dictionnaire le coefficient constant
0] = substitute(dnumerator, Dict(X=>0))
dd[
# on définit le polynôme à partir du dictionnaire
= SparsePolynomial(dd, :X)
dnumpoly
# on calcule les racines en filtrant les racines entre 0 et K via une fonction anonyme s-> K > s > 0
= filter(s -> K > s > 0, roots(dnumpoly))
droots
# vecteur pour le tracé du diagramme de bifurcation
= 0.08:.01:droots[1]
xplot1 = droots[1]:.01:droots[2]
xplot2 = droots[2]:.01:K
xplot3 = [r*(1 - x/K)/(α*x)*(h^2 + x^2) for x in xplot1]
yeq1 = [r*(1 - x/K)/(α*x)*(h^2 + x^2) for x in xplot2]
yeq2 = [r*(1 - x/K)/(α*x)*(h^2 + x^2) for x in xplot3]
yeq3
# diagramme de bifurcations
= plot(
figbif
yeq1,
xplot1;= 2,
linewidth = mygreen,
color = "équilibres stables",
label = :left,
legend = "population d'oiseaux \$y\$",
xlabel = "population de tordeuses \$x\$",
ylabel = "Diagramme de bifurcations pour le modèle de tordeuses",
title = .5Plots.cm,
margin = 1Plots.cm,
topmargin
)
plot!(
figbif,
yeq2,
xplot2;= 2,
linewidth = myred,
color = "équilibres instables",
label
)
plot!(
figbif,
yeq3,
xplot3;= 2,
linewidth = mygreen,
color = "",
label
)
plot!(
figbif,0, maximum(yeq1)],
[0, 0];
[= myred,
color = 2,
lw = "",
label )
On trace sur ce diagramme la trajectoire simulée plus haut :
plot!(
figbif,
sol_tord_ois.y,
sol_tord_ois.x;= myblue,
color = 2,
linewidth = 0.5,
linealpha = "trajectoire",
label )
Le modèle proie-prédateur de Lotka et Volterra
Nous considérons le modèle de dynamique de populations de Lotka (1925) et Volterra (1926) :
\left\{\begin{array}{l} \dot x = rx - c xy,\\ \dot y = bxy - m y. \end{array}\right. \tag{2}
Avec x la population de proies et y la population de prédateurs.
Dynamiques
Il n’y a pas de difficulté particulière à la simulation par rapport au modèle de la tordeuse du bourgeon de l’épinette avec population d’oiseaux variables.
Code
# conditions initiales
= 1.0
x0 = 1.95
y0 = [x0, y0]
etat0
# paramètres
= 1.0
r = 1.0
c = 1.0
b = 1.0
m = [r, c, b, m]
par_lovo
# temps d'integration
= (0.0, 30.0)
tspan = .01
tstep
# définition du modèle
function lovo(u, par, t)
= par
r, c, b, m = u[1]
x = u[2]
y = r*x - c*x*y
dx = b*x*y - m*y
dy return [dx, dy]
end
# problème
= ODEProblem(lovo, etat0, tspan, par_lovo, saveat = tstep)
prob_lovo
# intégration
= solve(prob_lovo, reltol = 1e-6)
sol_lovo
# dataframe
= DataFrame(sol_lovo)
sol_lovo rename!(sol_lovo, :timestamp => :time, :value1 => :x, :value2 => :y)
= plot(
figlv
sol_lovo.time,
sol_lovo.x;= 2,
linewidth = myblue,
color = "proies",
label = "temps",
xlabel = "densité de populations",
ylabel = "Modèle de Lotka Volterra",
title = .5Plots.cm,
margin = 1Plots.cm,
topmargin
)
plot!(
figlv,
sol_lovo.time,
sol_lovo.y;= 2,
linewidth = myorange,
color = "prédateurs",
label )
Espace d’état
Commençons par tracer les isoclines nulles ainsi que l’orientation du champs de vecteur dans l’espace d’état. Ce dernier utilise la fonction quiver()
.
Ce qui suit fonctionne avec Plots.jl
, néanmoins la façon de tracer le champs de vecteurs semble vraiment peu naturelle. La librairie graphique Makie
que nous verrons un peu plus loin permet de faire ces représentations bien plus facilement, comme nous le voyons ici.
# fonctions qui renvoient les composantes de la dérivée mises à l'échelle
= 10
scale der_x(x, y) = lovo([x y], par_lovo, 0)[1]/scale
der_y(x, y) = lovo([x y], par_lovo, 0)[2]/scale
# valeur de x et y formant une grille sur laquelle évaluer le champs de vecteurs
= range(0., 2, length=11)' # note the quote : broadcast pour créer une grille via der_x.() et der_y.()
xrange = range(0. ,2, length=11)
yrange
# champs de vecteurs
= quiver(
figplan
xrange,
yrange,= (der_x.(xrange, yrange), der_y.(xrange, yrange));
quiver = (-0.05, 2),
ylim = (-0.05, 2),
xlim = :lightgray,
color = false,
grid = "Modèle de Lotka Volterra",
title = "proies",
xlabel = "prédateurs",
ylabel = .5Plots.cm,
margin = 1Plots.cm,
topmargin )
On rajoute les isoclines nulles et les équilibres :
Code
# isoclines nulles
= 0:2
xplot = 0:2
yplot
# dot x = 0
plot!(
figplan,
xplot,ones(length(xplot)).*r./c;
= 2,
linewidth = mygreen,
color = "\$\\dot x = 0\$",
label
)plot!(
figplan,zeros(length(yplot)),
yplot;= 2,
linewidth = mygreen,
color = "",
label
)
# dot y = 0
plot!(
figplan,ones(length(yplot)).*m./b,
yplot;= 2,
linewidth = myred,
color = "\$\\dot y = 0\$",
label
)plot!(
figplan,
xplot,zeros(length(xplot));
= 2,
linewidth = myred,
color = "",
label
)
# équilibres
plot!(
figplan,0, 0);
(= :circle,
markershape = myred,
color= "",
label
)plot!(
figplan,/b, r/c);
(m= :circle,
markershape = myorange,
color= "",
label )
Et enfin la trajectoire :
Code
# trajectoire
plot!(
figplan,
sol_lovo.x,
sol_lovo.y;= myblue,
color = 2,
linewidth = "trajectoire",
label
)
display(figplan)
Intégrale première
Nous illustrons en 3D l’intégrale première du modèle et superposons la trajectoire simulée plus haut sur ce graphique. L’intégrale première s’écrit : H(x,y)= -r\log(y)+c y-m\log(x) + bx
# l'intégrale première
function int_prem(x, y, par = par_lovo)
= par
r, c, b, m return -r*log(y) + c*y - m*log(x) + b*x
end
Pour tracer le graphique en 3D, nous utilisons le backend plotly
.
plotly()
# x, y
= 0.2:0.1:3.0
xsurf = 0.2:0.1:3.0
ysurf
# calcul de la surface via une compréhension de liste
= [int_prem(x, y, par_lovo) for x in xsurf, y in ysurf]
zsurf # et H(x0, y0)
= [int_prem(x0, y0, par_lovo) for x in xsurf, y in ysurf]
zplane
# on trace H(x0, y0)
= plot(
figsurf
xsurf,
ysurf,# array de taille length(xsurf) x length(ysurf)
zplane; = :surface,
st = myorange,
color =.5,
alpha = "",
label
)
# l'intégrale première H(x,y)
plot!(
figsurf,
xsurf,
ysurf,# array de taille length(xsurf) x length(ysurf)
zsurf; =:surface,
st= .6,
alpha = (30, 10),
camera = :viridis, # colormap
color = "proies",
xlabel = "prédateurs",
ylabel = "Intégrale première du modèle de Lotka Volterra",
title
)
# la trajectoire
# un simple plot avec 3 coordonnees: x et y de la solution, et z = H(x,y)
plot!(
figsurf,
sol_lovo.x,
sol_lovo.y,ones(length(sol_lovo.x)).*int_prem(x0, y0, par_lovo);
= myred,
color =4,
lw= "",
label )
Allons plus loin en étudiant le modèle de Rosenzweig MacArthur, par ici.