Predator-Prey (Lotka–Volterra, SimPy)

Discrete-time SimPy approximation of the predator–prey model.

Level:Intermediate

populationecosystemnonlinear-dynamics

  • Stocks:prey, predator
  • Flows:prey_births, predations, predator_deaths
  • Feedback Loops:predation cycle
  • Probes:prey, predator
FAQ
Why avoid SciPy in this implementation?
The populations are stepped forward with Euler integration inside SimPy so it can run even when SciPy is unavailable.
How does the time_step affect accuracy?
A larger dt leads to bigger numerical errors since Euler integration is only first order.
Where is env.timeout used?
Each discrete step yields env.timeout(dt) to advance the SimPy clock.
simulation.py

Predator and prey via SimPy

This example approximates the Lotka--Volterra equations in discrete time using SimPy's event loop. It intentionally avoids SciPy's ODE solver so it can run in environments where SciPy is unavailable.

Limitations: because we update the populations at a fixed time step, results depend heavily on the chosen time_step and may drift from the true continuous solution. A stiff system or a large step size can cause the simulation to diverge or behave unrealistically.


from tys import probe, progress


def simulate(cfg: dict):
    """Simulate predator and prey populations with SimPy."""

    import simpy
    env = simpy.Environment()

Parameters

    prey = float(cfg["initial_prey"])
    predator = float(cfg["initial_predator"])
    alpha = cfg["alpha"]
    beta = cfg["beta"]
    delta = cfg["delta"]
    gamma = cfg["gamma"]
    sim_time = float(cfg["sim_time"])
    dt = float(cfg.get("time_step", 0.01))

    assert all(p > 0 for p in (alpha, beta, delta, gamma)), "Rates must be positive"
    assert dt > 0 and sim_time > 0, "Time parameters must be positive"

    steps = int(sim_time / dt)
    done = env.event()

Discretely integrate the differential equations using Euler's method. This simple integrator is convenient but inaccurate for larger dt.

    def dynamics():
        nonlocal prey, predator
        for i in range(steps):
            prey_births = alpha * prey
            predations = beta * prey * predator
            predator_births = delta * prey * predator
            predator_deaths = gamma * predator

            prey += (prey_births - predations) * dt
            predator += (predator_births - predator_deaths) * dt

            prey = max(prey, 0.0)
            predator = max(predator, 0.0)

            probe("prey", env.now, prey)
            probe("predator", env.now, predator)
            progress(int(100 * (i + 1) / steps))
            yield env.timeout(dt)
        done.succeed({
            "final_prey": prey,
            "final_predator": predator,
        })

    env.process(dynamics())
    env.run(until=done)
    return done.value


def requirements():
    return {
        "builtin": ["micropip", "pyyaml"],
        "external": ["simpy==4.1.1"],
    }
Default.yaml
initial_prey: 10
initial_predator: 5
alpha: 1.5
beta: 1.0
delta: 1.0
gamma: 3.0
sim_time: 20
time_step: 0.01
Charts (Default)

prey

prey chart
Samples2000 @ 0.00–19.99
Valuesmin 0.05, mean 3.12, median 0.89, max 18.58, σ 4.37

predator

predator chart
Samples2000 @ 0.00–19.99
Valuesmin 0.00, mean 1.63, median 0.08, max 15.63, σ 3.22
Final Results (Default)
MetricValue
final_prey0.24
final_predator10.79