Équations Lotka-Volterra avec plotly

Contenu

Binder NbViewer

Équations Lotka-Volterra avec plotly#

Ce carnet Jupyter a pour objectif de résoudre un système d’équations différentielles ordinaire (ODE).

Sa fonctionnalité majeure est :

  • Interactivité avec des graphiques par utilisation du module plotly

Modèle#

Également connues sous le nom d’équations prédateur-proie, elles décrivent la variation des populations de deux espèces qui interagissent par le biais de la prédation. Il s’agit d’un modèle classique pour représenter la dynamique de deux populations où \(x\) représente la quantité des proies et \(y\) celles des prédateurs.

\[\begin{split} \left \{ \begin{array}{rcl} \dot{x} &=& \alpha x - \beta x y \\ \dot{y} &=& -\delta y + \gamma x y \end{array} \right . \end{split}\]

Codage#

Importation des modules Python nécessaires.

Hide code cell content
import numpy
import scipy
import plotly.subplots
import plotly.graph_objects as go

Classe qui code le système d’équations différentielles dans la méthode derivative() et qui le résous avec la méthode solutions()

Hide code cell content
class lotka_volterra_model():
    """Model of Lotka-Volterra."""
    
    def __init__(self, alpha=1, beta=1, delta=0.8, gamma=1):
        """Initializes model parameters."""
        self.alpha = alpha
        self.beta = beta
        self.delta = delta
        self.gamma = gamma

    def derivative(self, t, X):
        """Returns derivative values."""
        x, y = X
        x_dot = x * (self.alpha - self.beta * y)
        y_dot = y * (-self.delta + self.gamma * x)
        return (x_dot, y_dot)

    def solution(self, t_init, t_final, x_init, y_init):
        """Returns temporal solution for t in [t_init, t_final]."""
        sol = scipy.integrate.solve_ivp(
            self.derivative, 
            [t_init, t_final], 
            [x_init, y_init],
            rtol = 1.0e-7,
            atol = 1.0e-7,
        )
        return sol.t, sol.y[0], sol.y[1]

Résolution des équations pour \(t \in [t_{init}, t_{final}]\) avec les conditions initiales suivantes \(x=2\) (proie) et \(y=1\) (prédateur), et préparation de la figure pour monter \(x(t)\) et \(y(t)\) avec la bibliothèque Python plotly.

t_init, t_final = 0, 30
prey_init, pred_init = 2, 1

lvm = lotka_volterra_model(delta=0.4)
t, prey, pred = lvm.solution(t_init, t_final, prey_init, pred_init)

fig = go.Figure()
fig.update_layout(
    width=700,
    height=450,
    xaxis_title="temps",
    yaxis_title="population",
)

fig.add_trace(go.Scatter(x=t, y=prey, name="Proies"))
fig.add_trace(go.Scatter(x=t, y=pred, name="Prédateurs"))

print("les deux courbes sont tracées")
Hide code cell output
les deux courbes sont tracées

Affichage de l’évolution des proies \(x\) et des prédateurs \(y\). Les courbes sont interactives à l’aide de la bibliothèque JavaScript plotly.js sous-jacente. Sans code supplémentaire, l’utilisateur peut accéder aux données en promenant simplement le pointeur de la souris le long des courbes.

fig.show()

Construction d’une nouvelle figure composée de deux sous figures, l’évolution temporelle de \(x(t)\) (proie) et \(y(t)\) (prédateur), et le lieu du point \(P(x, y)\) dans le repère \(xOy\) (phase).

t_init, t_final = 0, 30
prey_init, pred_init = 2, 1

prey_max, pred_max = 0, 0

fig = plotly.subplots.make_subplots(
    rows=2,
    cols=1,
    subplot_titles=("Evolution temporelle", "Phase"),
    # vertical_spacing=0.15,
)

lvm = lotka_volterra_model(delta=1.0)
t, prey, pred = lvm.solution(t_init, t_final, prey_init, pred_init)
fig.add_trace(go.Scatter(x=t, y=prey, name="Proies"), row=1, col=1)
fig.add_trace(go.Scatter(x=t, y=pred, name="Prédateurs"), row=1, col=1)
fig.add_trace(go.Scatter(x=prey, y=pred, showlegend=False), row=2, col=1)
fig.update_layout(height=550, width=700)

type(fig)
plotly.graph_objs._figure.Figure

Les courbes d’évolution temporelle de \(x\) (proie), \(y\) (prédateur) et le lieu du point \(P(x,y)\) (phase) sont calculées pour plusieurs valeurs du paramètre \(\delta \in \{0.2, 0.25, 0.3, 0.35 \dots 1.2\}\) et stockées dans la liste steps.

delta = numpy.arange(0.2, 1.25, 0.05)
steps = list()

for d in delta:
    lvm = lotka_volterra_model(delta=d)
    t, prey, pred = lvm.solution(t_init, t_final, prey_init, pred_init)
    prey_max, pred_max = max(prey_max, *prey), max(pred_max, *pred)
    step = dict(
        method="update",
        label = f"{d:.2f}", 
        args=[{"x": [t, t, prey], "y": [prey, pred, pred]}],
    )
    steps.append(step)

popul_max = max(prey_max, pred_max)

La taille des sous-figures est adaptée aux valeurs maximales des données précalculées.

fig.update_xaxes(title_text="temps", row=1, col=1)
fig.update_yaxes(title_text="Population", range=[0, popul_max], row=1, col=1)
fig.update_xaxes(title_text="Proie", range=[0, prey_max], row=2, col=1)
fig.update_yaxes(title_text="Prédateur", range=[0, pred_max], row=2, col=1)

print("Valeurs maximales")
print("  proie x =", prey_max)
print("  prédateur y=", pred_max)
Valeurs maximales
  proie x = 2.0
  prédateur y= 3.6281392945583772

Un curseur est ajouté à la figure pour sélectionner les courbes précalculées à afficher. L’interactivité de la figure avec l’utilisateur est accrue, il n’y a cependant pas d’interaction avec le noyau d’exécution Python, seules des données déjà calculées peuvent s’afficher.

sliders = [dict(currentvalue={'prefix': 'delta = '}, steps=steps)]

fig.update_layout(sliders=sliders)

print("curseur ajouté")
curseur ajouté

L’interactivité de la figure avec l’utilisateur est accrue, il n’y a cependant pas d’interaction avec le noyau d’exécution Python, seules des données déjà calculées peuvent s’afficher.

fig.show()