Visualizing the Lotka-Volterra Model#

The Lotka-Volterra equations are a set of coupled ordinary differential equations(ODEs) that can be used to model predator prey relationships.

They have 4 parameters that can each be tuned individually which will affect how the flucuations in population behave. In order to explore this 4D parameter space we can use mpl_interactionsplot function to plot the results of the integrated ODE and have the plot update automatically as we update the parameters.

Define the function#

%matplotlib ipympl
import matplotlib.pyplot as plt
import numpy as np

from mpl_interactions import ipyplot as iplt
# this cell is based on https://scipy-cookbook.readthedocs.io/items/LoktaVolterraTutorial.html
from scipy import integrate

t = np.linspace(0, 15, 1000)  # time
X0 = np.array([10, 5])  # initials conditions: 10 rabbits and 5 foxes

# use `c_` instead of `c` because `c` is an argument to plt.scatter


def f(a, b, c_, d):
    def dX_dt(X, t=0):
        """Return the growth rate of fox and rabbit populations."""
        rabbits, foxes = X
        dRabbit_dt = a * rabbits - b * foxes * rabbits
        dFox_dt = -c_ * foxes + d * b * rabbits * foxes
        return [dRabbit_dt, dFox_dt]

    X, _ = integrate.odeint(dX_dt, X0, t, full_output=True)
    return X  # expects shape (N, 2)

Make the plots#

Here we make two plots. On the left is a parametric plot that shows all the possible combinations of rabbits and foxes that we can have. The plot on the right has time on the X axis and shows how the fox and rabbit populations evolve in time.

fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(10, 4.8))
controls = iplt.plot(f, ax=ax1, a=(0.5, 2), b=(0.1, 3), c_=(1, 3), d=(0.1, 2), parametric=True)
ax1.set_xlabel("rabbits")
ax1.set_ylabel("foxes")

iplt.plot(f, ax=ax2, controls=controls, label=["rabbits", "foxes"])
ax2.set_xlabel("time")
ax2.set_ylabel("population")
_ = ax2.legend()

You may have noticed that it looks as though we will end up calling our function f twice every time we update the parameters. This would be a bummer because then our computer would be doing twice as much work as it needs to. Fortunately the control object implements a cache and will avoid call a function more than necessary when we move the sliders.

If for some reason you want to disable this you can disable it by setting the use_cache attribute to False:

controls.use_cache = False