using DifferentialEquations
using Plots
using DataFrames
Populations exploitées
Prélèvements et effets Allee
Nous reprenons le modèle précédent sur l’effet Allee mais en prenant en compte des prélèvements externes avec un effort (taux) de prélèvement E : \dot x = r x \left(\frac{x}{\epsilon}-1\right)\left(1-\frac{x}{K}\right)-Ex. \tag{1}
La simulation de ce modèle pour différentes valeurs de E (par exemple E=0.2 ou E=0.85) ne présente aucune difficulté supplémentaire.
Nous allons maintenant nous intéresser à une situation où l’effort de prélèvement E varie au cours du temps entre une valeur soutenable E_s (par exemple 0.2), et une valeur excessive E_x (par exemple 0.85).
L’attendu théorique est que si les prélèvements sont maintenus à une valeur excessive E_x trop longtemps, la population disparait irrémédiablement même si les prélèvements sont par la suite ramenés à une valeur initialement soutenable E_s.
Prélèvements variables dans le temps
Définissons une fonction effort()
dépendant du temps, qui renvoit :
- E_s~ si ~t<T_s
- E_x~ si ~t\in [T_s+T_x[
- à nouveau E_s~ si ~t\geq T_s+T_x
# paramètres
= 0.2
E_s = 0.85
E_x = 10.0
T_s = 9.0
T_x
= [E_s, E_x, T_s, T_x]
p_effort
function effort(t, p)
= p
E_s, E_x, T_s, T_x
if t < T_s || t >= T_s + T_x
return E_s
elseif t >= T_s && t < T_s + T_x
return E_x
end
end
Remarquez les opérateurs booléens “ou” ||
et “et” &&
, ainsi que la structure du test if
.
La fonction correspond bien à nos hypothèses :
Code
= 0: .1: 30
t2plot
plot(
# abscisses
t2plot, effort(t, p_effort) for t in t2plot]; # ordonnées
[= :tab10,
palette = 2,
linewidth = "\$E(t)\$",
label = "temps",
xlabel = "\$E(t)\$",
ylabel = "Effort de pêche \$E(t)\$",
title = .5Plots.cm,
margin = 1Plots.cm,
topmargin )
Remarquez la construction des ordonnées correspondant à la fonction effort
via une compréhension de liste : [effort(t, p_effort) for t in t2plot]
.
Simulations en fonction du temps
Nous définissons les paramètres du modèle, la condition initiale et le temps d’intégration :
Code
= 10.0
x0
= (0.0, 30.0)
tspan = 0.1
tstep
= 1.0
r = 10.0
K = 2.0
epsilon = [r, K, epsilon] p_allee
Nous définissons le système dynamique comme précédemment, à la différence que nous prévoyons de surcharger l’argument p
dans le problème d’intégration sous forme d’un vecteur comprennant :
- le vecteur de paramètres
p_allee
en première position - le vecteur de paramètres
p_effort
en seconde position - et la fonction
effort
en troisième position1
1 En Julia tout est passable en argument, des variables aux fonctions et plus.
= [p_allee, p_effort, effort] p_evar
function allee_evar(u, p, t)
= p[1] # unpacking model parameters
r, K, epsilon = p[2] # unpacking fishing effort parameters
p_effort = p[3] # unpacking fishing effort function
E = u # use x notation
x
return dx = r*x*(x/epsilon - 1)*(1 - x/K) - E(t, p_effort)*x
end
L’intégration en elle-même suit le shéma vu précédemment, si ce n’est que l’argument de paramètres doit bien refléter ce qui est attendu par la fonction allee_evar()
. La simulation en elle-même est effectuée avec une modification de la précision relative de l’intégration reltol = 1e-6
, la précision par défaut n’étant pas suffisante ici.
= ODEProblem(
prob_allee_evar
allee_evar,
x0,
tspan,
p_evar;= tstep,
saveat
)
= solve(prob_allee_evar; reltol = 1e-6)
sol_allee_evar
= DataFrame(sol_allee_evar)
sol_allee_evar rename!(sol_allee_evar, :timestamp => :time, :value => :x)
Ici nous avons imposé une tolérance plus précise au solver via le keyword argument reltol = 1e-6
.
Finalement, nous pouvons représenter graphiquement la solution contre le temps. Ici, malgré la perturbation violente induite par la période de surexploitation, le pêcherie retrouve une situation soutenable après un retour à E=E_s.
Code
plot(
sol_allee_evar.time,
sol_allee_evar.x;= "\$x(t)\$",
label = 2,
linewidth = "temps",
xlabel = "densité de population \$x(t)\$",
ylabel = "Effort de pêche variant dans le temps",
title = :tab10,
palette = :left,
legend = .5Plots.cm,
margin = 1Plots.cm,
topmargin )
Lorsque la période de surexploitation de la population est trop longue (e.g. ici T_x=9.2), la population ne parvient pas à récupérer malgré le retour à un effort de prélèvement soutenable.
Code
# change parameter
= 9.2
T_x2
= [E_s, E_x, T_s, T_x2]
p_effort2 = [p_allee, p_effort2, effort]
p_evar2
# define new problem and integrate
= ODEProblem(
prob_allee_evar2
allee_evar,
x0,
tspan,
p_evar2,= tstep
saveat
)
= solve(prob_allee_evar2, reltol = 1e-6)
sol_allee_evar2
= DataFrame(sol_allee_evar2)
sol_allee_evar2 rename!(sol_allee_evar2, :timestamp => :time, :value => :x)
# plot
plot(
sol_allee_evar2.time, sol_allee_evar2.x,= "\$x(t)\$";
label = 2,
linewidth = "temps",
xlabel = "densité de population \$x(t)\$",
ylabel = "Effort de pêche variant dans le temps",
title = :tab10,
palette = :left,
legend = .5Plots.cm,
margin = 1Plots.cm,
topmargin )
Simulations dans l’espace (E, x)
Il s’agit ici de représenter dans l’espace (E, x) l’évolution conjointe de l’effort de pêche et de la densité de la population au cours du temps, afin de mieux comprendre le phénomène d’extinction. Dans cet objectif, on tracera aussi le lieu des équilibres de la population x^*(E) en fonction d’une valeur de E constante, i.e. : x^*(E) = 0, ce qui est équivalent à : E = r\left(\frac{x^*(E)}{\epsilon}-1\right)\left(1-\frac{x^*(E)}{K}\right).
Nous allons tracer les deux situations vues à la section précédente dans deux sous figures. Commençons par les lieux des équilibres, qui sont les mêmes dans les deux situations.
Définissons le lieu des équilibres positifs :
# parabole pour les equilibres positifs
function eeqpos(x, par = p_allee)
= par
r, K, epsilon return r*(x/epsilon -1)*(1-x/K)
end
Puis traçons ce lieu dans le plan (E,x) (il y a deux branches une stable et une instable pour les équilibres positifs qui se rejoignent en x^* = (K+\epsilon)/2, nous les traçons séparément)
# vecteurs pour le tracé
= 0:.1:1
e2plot = epsilon:.02:(K+epsilon)/2
x2plot1 = (K+epsilon)/2:.02:K
x2plot2
# on définit des couleurs spécifiques depuis la palette :pal10
= palette(:tab10)[3]
mygreen = palette(:tab10)[2]
myorange = palette(:tab10)[1]
myblue = palette(:tab10)[4]
myred
# plot
= plot(
fig1
e2plot,zeros(length(e2plot));
= mygreen,
color = 2,
linewidth = "équilibres stables",
label = "densité de population \$x\$",
ylabel = "effort de pêche \$E\$",
xlabel = :left,
legend
)
plot!(
fig1,eeqpos.(x2plot1),
x2plot1;= myorange,
color= 2,
linewidth = "équilibre instable",
label
)
plot!(
fig1,eeqpos.(x2plot2),
x2plot2;= mygreen,
color= 2,
linewidth ="",
label
)
display(fig1)
Remarquez le broadcast (calcul terme à terme) sur les ordonnées des équilibres positifs en utilisant eeqpos.
. On aurait pu procéder par compréhension de liste: [eeqpos(x) for x in x2plot2]
.
On prépare un graphique fig2
avec les mêmes éléments que dans fig1
:
= deepcopy(fig1) # on fait une copie complète de fig1 fig2
Finalement, on complète fig1
et fig2
avec quelques annotations et les trajectoires calculées plus haut en fonction de l’effort de pêche variable au cours du temps, et on trace les résultats en deux sous-figures :
# annotations
annotate!(fig1, .35, 8.5, Plots.text("branche stable", 10, rotation=-28))
annotate!(fig1, .4, 2.7, Plots.text("branche instable", 10, rotation=28))
annotate!(fig2, .4, 2.7, Plots.text("branche instable", 10, rotation=28))
annotate!(fig2, .35, 8.5, Plots.text("branche stable", 10, rotation=-28))
# peche durable sur fig1
plot!(
fig1,effort(t, p_effort) for t in sol_allee_evar.time],
[
sol_allee_evar.x;= myblue,
color = 2,
linewidth = "trajectoire",
label
)
# surpeche sur fig2
plot!(
fig2,effort(t, p_effort2) for t in sol_allee_evar2.time], sol_allee_evar2.x;
[= myblue,
color = 2,
linewidth = "trajectoire",
label
)
# figure reprenant les deux sous figures fig1 et fig2
plot(
fig1,
fig2;= "Effets Allee et prélèvements",
suptitle = .5Plots.cm,
margin = 1Plots.cm,
topmargin )
La tordeuse du bourgeon de l’épinette
Modèle
Nous considérons le modèle de dynamique de populations suivant, inspiré de Ludwig, Jones, and Holling (1978) :
\dot x =rx\left(1-\frac{x}{K}\right) - \frac{\alpha x^2}{h^2+x^2}\ y, \tag{2}
avec x la densité de tordeuses et y la densité d’oiseaux. La croissance des tordeuses suit une loi logistique et la prédation des oiseaux une réponse fonctionnelle de type Holling III.
Simulations
On procède classiquement, en ajustant les valeurs de paramètres pour mettre en évidence les phénomènes dynamiques attendus :
# paramètres
= 5.0 # natalité
r = 10.0 # mortalité
K = 1.0 # taux max de prédation, \alpha + TAB
α = 0.5 # constante de demi-saturation
h = 7.0 # densité de prédateurs
yc
= [r, K, α, h, yc]
par_tordeuse
# temps d'intégration
= (0.0, 3.0)
tspan = 0.02 tstep
Puis on définit le modèle :
function tordeuse(u, p, t)
= p
r, K, α, h, yc = u
x return dx = r*x*(1 - x/K) - α*x^2/(h^2 + x^2)*yc
end
Comme pour le modèle avec effets Allee, on définit une fonction qui redéfinit le problème d’intégration à chaque fois, et simule et renvoit la solution pour pouvoir illustrer la bi-stabilité2 :
2 des méthodes alternative utilisant l’interface integrator de DifferentialEquations.jl
ou la redéfinition du problème d’intégration via remake
sont proposées en annexe
function int_tordeuse(x0, tspan = tspan, param = par_tordeuse)
= ODEProblem(
prob_tordeuse # modèle
tordeuse, # condition initiale
x0, # tspan
tspan, # paramètres
param; = tstep,
saveat
)
= solve(prob_tordeuse)
sol_tordeuse = DataFrame(sol_tordeuse)
sol_tordeuse rename!(sol_tordeuse, :timestamp => :time, :value => :x)
end
Nous simulons le modèle depuis différentes conditions intiales, et traçons les résultats via une boucle for
.
# conditions initiales
= 1.35
x0step = x0step:x0step:K
x0vec
# custom color palette
= palette([:steelblue, :lightblue], length(x0vec))
init_cgrad
# initialisation du graphique, équilibre nul
= plot(; # seulement des kwargs: on initialise le graphique
fig3 = init_cgrad,
palette = :right,
legend ="équilibres instables",
label = "Tordeuse du bourgeon de l\'épinette",
title = "densité de population \$x(t)\$",
ylabel = "temps \$t\$",
xlabel = .5Plots.cm,
margin = 1Plots.cm,
topmargin
)
# boucle de plot avec intégration pour differentes conditions initiales
for x0 in x0vec
plot!(
fig3,int_tordeuse(x0).time,
int_tordeuse(x0).x,
= 2,
linewidth = "",
label
)end
display(fig3) # actually shows the plot fig3
Equilibres
Les valeurs des équilibres positifs du modèle Equation 2 n’ont pas d’écriture mathématique simple. Nous allons calculer numériquement les racines du polynôme dont ils sont solution : r\left(1-\frac{x^*}{K}\right)\left(h^2+x^{*2}\right)-\alpha x^* y = 0 \tag{3}
Pour cela, on utilise le package Polynomials.jl
:
using Polynomials
# définition du monôme X
= Polynomial([0, 1])
X
# définition du polynôme
= r*(1-X/K)*(h^2 + X^2)-α*X*yc
pol
# calcul des racines, réelles, positives et plus petites que K
= roots(pol) # calcul des racines
eq_pos = real.(eq_pos[isreal.(eq_pos)]) # filtrage des racines réelles
eq_pos = eq_pos[(eq_pos .> 0) .& (eq_pos .<= K)] # filtrage des racines >0 et <K eq_pos
3-element Vector{Float64}:
0.20406511760131657
1.4717313636879934
8.324203518710693
A partir de la définition du monôme de degré 1, on écrit assez naturellement un polynôme et on en extrait les racines via roots
.
La partie filtrage des racines réelles prend la partie réelle des racines qui sont rendues sous forme d’un type complexe (même si la partie imaginaire est nulle).
Remarquez les broadcasting divers, notamment .>
et .&
qui testent les conditions terme à terme sur le vecteur eq_pos
.
Et on trace les différents équilibres :
= collect(tspan)
t2plot
# initialisation du graphique, équilibre nul
plot!(
fig3,
t2plot,zeros(length(t2plot));
= 2,
linewidth = :dash,
linestyle = myorange,
color = init_cgrad,
palette = :right,
legend ="équilibres instables",
label = "densité de population \$x(t)\$",
ylabel = "temps \$t\$",
xlabel = .5Plots.cm,
margin = 1Plots.cm,
topmargin
)
# équilibres positifs, différents cas
if length(eq_pos) == 1
plot!(
fig3,ones(length(t2plot)).*eq_pos;
t2plot, = mygreen,
color ="équilibre stable",
label
)elseif length(eq_pos) == 3
plot!(
fig3,
t2plot,ones(length(t2plot)).*eq_pos[1];
=2,
lw= :dash,
linestyle = mygreen,
color ="équilibres stables",
label
)plot!(
fig3,
t2plot,ones(length(t2plot)).*eq_pos[2];
=2,
lw= :dash,
linestyle = myorange,
color = "",
label
)plot!(
fig3,
t2plot,ones(length(t2plot)).*eq_pos[3];
=2,
lw= :dash,
linestyle = mygreen,
color = "",
label
)else
println("Cette situation est non générique (2 équilibres positifs à la bifurcation)")
end
Diagramme de bifurcations
Le début de cette partie est assez technique et a pour objectif principal de montrer ce qu’il est possible de faire en manipulant des expressions symboliques.
Finalement nous traçons dans le plan (y, x) le lieu des points d’équilibres en fonction de la taille de la population d’oiseaux:
y = \frac{r}{\alpha x^*}\left(1-\frac{x^*}{K}\right)(h^2+x^{*2}) \tag{4}
Comme nous l’avons vu en cours cette fonction est non monotone avec une branche décroissante, une branche croissante puis à nouveau une branche décroissante, le sens de variation de la branche déterminant la stabilité de l’équilibre correspondant.
Pour déterminer ces différentes branches et les représenter de différentes couleurs de façon à illustrer leur stabilité, nous allons dériver l’Equation 4 par rapport à x^* en utilisant le package Symbolics.jl
et chercher les racines de la dérivée3.
3 si l’Equation 4 avait été un polynôme nous aurions utilisé les outils pour les polynômes, mais il s’agit d’une fraction rationnelle
using Symbolics
@variables X
= Differential(X)
D
# on définit le lieu des équilibres selon l'équation ci-dessus
= r/(α*X)*(1-X/K)*(h^2+X^2) Y
\begin{equation} \frac{5 \left( 1 - 0.1 X \right) \left( 0.25 + X^{2} \right)}{X} \end{equation}
Les racines de la dérivée, sont les racines du numérateur de la dérivée, donc on dérive:
# Dérivée de Y, distribuée et simplifiée
= simplify(expand_derivatives(D(Y))) derY
\begin{equation} \frac{-1.25 + 5 X^{2} - X^{3}}{X^{2}} \end{equation}
et on récupère ce numérateur:
= Symbolics.arguments(Symbolics.value(derY))[1] dnumerator
\begin{equation} -1.25 + 5.0 X^{2} - X^{3} \end{equation}
Comme ce numérateur est un polynôme, on peut utiliser le polynôme symbolique de Symbolics.jl
pour regénérer un polynôme et utiliser la méthode roots()
de Polynomials.jl
4
4 il faut l’admettre, ce serait plus simple d’avoir directement une façon de trouver les racines d’un polynôme symbolique, mais ça ne semble pas implémenté pour l’instant (fin 2023)
# on récupère les coefficients X^k du polynôme
= Symbolics.value(dnumerator).dict
coefs_dict
# on crée un dictionnaire rassemblant les coefficients du polynôme
= Dict(Symbolics.degree(first(kv)) => kv[2] for kv in coefs_dict)
poldict
# on rajoute dans le dictionnaire le coefficient constant de dnumerator
0] = substitute(dnumerator, Dict( X=> 0))
poldict[
# on définit le polynôme à partir du dictionnaire
= SparsePolynomial(poldict, :X) dnumpoly
Finalement, on calcule les solutions (racines de la dérivée de y) en prenant soin de filtrer celles entre 0 et K.
# on calcule les solutions en filtrant les racines entre 0 et K via une fonction anonyme s-> K > s > 0
= filter(s -> K > s > 0, roots(dnumpoly)) dyroots
Ici nous utilisons une fonction anonyme s -> K > s > 0
qui renvoit true
ou false
selon que la condition est vérifiée par l’argument, et qui appliquée à roots(dnumpoly)
, “filtre” les valeurs vérifiant la condition.
On aurait pu faire un filtrage comme vu plus haut.
On calcule les branches du lieu des équilibres correspondantes, et on peut les tracer dans le plan (y,x) pour illustrer le diagramme de bifurcations.
# on calcule chacune des branches
= 0.08:.01:dyroots[1]
xplot1 = dyroots[1]:.01:dyroots[2]
xplot2 = dyroots[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
# branche stable 1 et initialisation
= 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
)
# branche instable
plot!(
figbif,
yeq2,
xplot2;= 2,
linewidth = myred,
color = "équilibres instables",
label
)
# branche stable 2
plot!(
figbif,
yeq3,
xplot3;= 2,
linewidth = mygreen,
color = "",
label
)
# équilibre nul
plot!(
figbif,0, maximum(yeq1)],
[0, 0];
[= myred,
color = 2,
lw = "",
label )
Nous verrons sur la page des populations en interactions le cas où la population d’oiseaux varie lentement au cours du temps.