%reset -f
Populations isolées
Le modèle de Malthus
Nous considérons le modèle proposé par Malthus (1798) : \dot x = (n-m)x, \tag{1} avec n le taux de natalité, et m le taux de mortalité.
Pour simuler ce modèle, c’est à dire intégrer numériquement les solutions au problème de Cauchy correspondant à l’Equation 1 et x(0)=x_0\geq0, nous allons utiliser les modules numpy
et matplotlib.pyplot
, et la méthode odeint
de scipy.integrate
.
Préliminaires
On commence par nettoyer l’espace de travail, dans Jupyter :
Et on importe les modules cités précédemment :
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint
Système dynamique
Définissons le modèle: une fonction de l’état x, du temps t (nécessaire pour odeint
), de paramètres, qui renvoit la dérivée \dot x.
def model_malthus(etat, t, params):
= etat # unpack l'etat
x = params # unpack params -> params locaux
n, m = (n-m)*x # calcule la derivee de l'etat
xdot return xdot # renvoit la derivée
On fera attention a bien désencapsuler les états/paramètres/etc. dans le même ordre que l’encapsulation faite dans le corps du programme (cf. ci-dessous).
Problème de Cauchy (initial value problem)
Définissons les conditions initiales, et encapsulons le dans un np.array
:
= 0.1
x0
= np.array([x0]) etat0_malthus
Définissons les paramètres et encapsulons les :
= 3.0 # taux de natalité
n = 2.0 # taux de mortalité
m
= np.array([n, m]) params_malthus
Définissons le temps d’intégration tspan
(vecteur entre le temps initial et le temps final):
= 0.0 # temps initial
t_0 = 10.0 # temps final
t_fin = 0.01 # pas de temps
pas_t
= np.arange(start=t_0, stop=t_fin, step=pas_t) tspan
Intégration
Il s’agit d’utiliser la fonction odeint
de scipy
:
= odeint(
int_malthus # système dynamique
model_malthus, # condition initiale
etat0_malthus, # temps d'intégration
tspan, =(params_malthus,), # paramètres du syst. dyn. ici un tuple
args## à un élément (cf. virgule)
=pas_t) # pas d'intégration max. sur temps hmax
L’appel à odeint
est très précis et doit respecter les règles et l’ordre indiqués ci-dessus.
L’intégration est faite :
5] int_malthus[:
array([[0.1 ],
[0.10100502],
[0.10202014],
[0.10304547],
[0.10408109]])
Il reste à représenter graphiquement la solution calculée.
Représentation graphique
Créons une figure et deux systèmes d’axes pour représenter deux sous-figures, puis traçons l’évolution de x(t) calculée numériquement en fonction du temps (gauche) ou la solution mathématique x(t)=e^{(n-m)t} x_0 (droite). La représentation est visible dans la Figure 1.
Découvrons aussi différentes options des méthodes subplots(), plot(), legend()
, l’utilisation de LaTeX dans les chaînes de caractères, ou les fstrings
de Python 3 pour compléter les chaines de caractères avec des valeurs via "".format()
.
## figure et systèmes d'axes
= plt.subplots(1, 2)
fig1, (ax1, ax2)
## titre de la figure
'Simulation du modèle de Malthus\n $n = {}, m = {}$'.format(n, m),
fig1.suptitle(='top', fontsize='14')
va
## premier subplot
ax1.plot(tspan, int_malthus, ='C0',
color='$x(t)$ (numérique)')
label
## modification des bornes
=None, top=None)
ax1.set_ylim(bottom
## axes / légendes / grille
='10')
ax1.legend(fontsize'Temps $t$', fontsize='12')
ax1.set_xlabel('Densité de population $x$', fontsize='12')
ax1.set_ylabel(
ax1.grid()
## second subplot
-m)*tspan)*x0,
ax2.plot(tspan, np.exp((n='C2',
color='$x(t)$ (analytique)')
label
## axes / légendes / grille
='10')
ax2.legend(fontsize'Temps $t$', fontsize='12')
ax2.set_xlabel( ax2.grid()
Malgré une bonne précision d’intégration, une simulation/intégration numérique reste une approximation des solutions mathématiques, et par construction induit donc des erreurs, comme l’illustre la Figure 2.
Code
= plt.subplots(1, 1)
fig2, ax3
ax3.plot(tspan, 0]-np.exp((n-m)*tspan)*x0,
int_malthus[:,='C1',
color='$x(t)$ (numérique)')
label
'Erreur d\'intégration pour le modèle de Malthus',
fig2.suptitle(='top', fontsize='14')
va'Temps $t$', fontsize='12')
ax3.set_xlabel('Erreur d\'intégration', fontsize='12')
ax3.set_ylabel( ax3.grid()
Le modèle logistique
Nous considérons ici le modèle “logistique” proposé par Verhulst (1938) :
\dot x = r x \left(1-\frac{x}{K}\right), \tag{2} avec r le taux de croissance intrinsèque de la population et K la capacité de charge de l’environnement.
Il n’y a pas de difficulté particulière par rapport aux simulations précedentes. On commence par nettoyer l’espace de travail, puis ré-importer les modules nécessaires :
%reset -f
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint
La définition du modèle, des conditions initiales et paramètres, l’intégration et la représentation graphique suivent les étapes vues précédemment.
Simulation
- Définition du système dynamique :
Code
def model_logistic(etat, t, params):
= etat
x = params
r, K = r*x*(1-x/K)
xdot return xdot
- Condition initiale, paramètres,
tspan
:
Code
## condition initiale
= 0.1
x0 = np.array([x0])
etat0_logistic
## paramètres
= 1.0 # taux de croissance intrinsèque
r = 10.0 # capacité de charge
K = np.array([r, K])
params_logistic
## tspan
= 0.0
t_0 = 10.0
t_fin = 0.01
pas_t = np.arange(t_0, t_fin, pas_t) tspan
- Intégration :
Code
= odeint(
int_logistic
model_logistic,
etat0_logistic,
tspan, =(params_logistic,),
args=pas_t) hmax
Représentation graphique
Code
## figure et systèmes d'axes
= plt.subplots(1, 1)
fig, ax
## titre de la figure
'Simulation du modèle logistique ; $r = {}, K = {}$'.format(r, K),
fig.suptitle(='top', fontsize='14')
va
## simulation
ax.plot(tspan, int_logistic, ='C0',
color='$x(t)$') # solution
label
## équilibres
ax.plot(tspan, np.zeros_like(tspan),= 'C3',
color = 'dashed',
linestyle = "équilibre instable")
label *K,
ax.plot(tspan, np.ones_like(tspan)= 'C2',
color = 'dashed',
linestyle = "équilibre stable")
label
## modification des bornes
=None, top=None)
ax.set_ylim(bottom
## axes / légendes / grille
='10')
ax.legend(fontsize'Temps $t$', fontsize='12')
ax.set_xlabel('Densité de population $x$', fontsize='12')
ax.set_ylabel( ax.grid()
Effets Allee
On s’intéresse à un modèle de dynamique de population avec “effets Allee forts”, souvent attribué à Gruntfest, Arditi, and Dombrovsky (1997)1 :
1 mais de nombreuses variations de cette forme polynomiale existent dans la littérature depuis Bazykin (1985)
\dot x = r x \left(\frac{x}{\epsilon}-1\right)\left(1-\frac{x}{K}\right), \tag{3} avec r le taux de croissance intrinsèque de la population (par analogie avec la logistique), K la capcité de charge de l’environnement et \epsilon le seuil en dessous duquel la population n’est pas viable (‘seuil de Allee’). On procède comme ci-dessus.
Simulation : préliminaires
- nettoyage de l’espace de travail et chargement des modules :
Code
%reset -f
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint
- Définition du système dynamique :
Code
def model_allee(etat, t, params):
= etat
x = params
r, K, epsilon = r*x*(x/epsilon-1)*(1-x/K)
xdot return xdot
- Paramètres,
tspan
:
Code
## paramètres
= 1.0 # taux de croissance intrinsèque
r = 2.0 # seuil de Allee
epsilon = 10.0 # capacité de charge
K = np.array([r, K, epsilon])
params_allee
## tspan
= 0.0
t_0 = 3.0
t_fin = 0.01
pas_t = np.arange(t_0, t_fin, pas_t) tspan
Simulation : conditions initiales multiples
Pour mettre en valeur la bi-stabilité du système dynamique défini par l’Equation 3, nous créons une fonction qui intègre le problème de Cauchy depuis une condition initiale donnée.
def int_allee(x0, tspan=tspan, params=params_allee):
= odeint(
sim_allee
model_allee, # argument de la fonction
x0, # argument de la fonction
tspan, =(params,), # argument de la fonction
args=pas_t)
hmaxreturn sim_allee
Nous remarquons l’utilisation de kwargs
(keyword arguments tspan
et params
), des arguments qui prendront leur valeur par défaut si ils ne sont pas re-spécifiés dans l’appel de la fonction int_allee()
.
Vérifions que la fonction réalise bien ce qui est attendu :
0.1)[:5,0] int_allee(
array([0.1 , 0.09906363, 0.09813549, 0.09721551, 0.09630365])
Représentation graphique
L’idée est de représenter plusieurs trajectoires issues de plusieurs conditions initiales. Créons un array
de conditions initiales :
= 1.35
x0_step = np.arange(x0_step, K, x0_step) x0_arr
Puis nous faisons la représentation graphique via une boucle exploitant la fonction int_allee()
:
= plt.subplots(1, 1)
fig, ax 'Simulation du modèle avec effets Allee'\
fig.suptitle('; $r={}, K={}, \epsilon={}$'.format(r, K, epsilon),
='top', fontsize='14')
va
## redéfinition du cycle des couleurs pour un dégradé de bleu
= plt.cm.Blues(np.linspace(.8, .3, x0_arr.shape[0]))
colorAllee = colorAllee)
ax.set_prop_cycle(color
## simulations
0]),
ax.plot(tspan, int_allee(x0_arr[= "$x(t)$")
label for x0 in x0_arr[1:]: # x0 parcour x0_arr
ax.plot(tspan, int_allee(x0))
## équilibres
ax.plot(tspan, np.zeros_like(tspan),= 'C2',
color = 'dashed',
linestyle = "équilibre stable")
label *K,
ax.plot(tspan, np.ones_like(tspan)= 'C2',
color = 'dashed')
linestyle *epsilon,
ax.plot(tspan, np.ones_like(tspan)= 'C3',
color = 'dashed',
linestyle = "équilibre instable")
label
## axes / légendes / grille
='10')
ax.legend(fontsize'Temps $t$', fontsize='12')
ax.set_xlabel('Densité de population $x$', fontsize='12')
ax.set_ylabel( ax.grid()
Nous utilisons une boucle avec x0
parcourant les conditions initiales x0_arr
pour tracer les différentes solutions.
Le dégradé de bleu des simulations est obtenu en créant une colormap
à partir de plt.cm.Blues()
ayant pour argument un array
à valeurs dans [0,1], de la même taille que le nombre de courbes à tracer. Cette colormap
redéfini ensuite le cycle de couleurs du système d’axes via ax.set_prop_cycle(color = foo)
.
La suite sur les populations exploitées par-ici.