Fishery Simulation

A miniature world of systems-thinking with stocks, flows, and feedback loops modeling fish population dynamics and ecosystem management.

Level:Beginner

populationresourcesustainabilitymanagementecosystem

  • Stocks:population
  • Flows:births, quota
  • Feedback Loops:reproduction (reinforcing), quota (balancing)
  • Probes:population, quota, gap_to_capacity, extracted_total

Feedback Loops

Understand the balancing and reinforcing feedback loops that drive system behavior and create complex dynamics in systems thinking.

Explore Feedback Loops
simulation.py

Managing a fishery with delayed quotas

Our lake has a healthy fish population that grows logistically. Local managers set the harvest quota based on numbers from last week. If the population drops but the quota lags behind, we risk overfishing.


from tys import probe, progress

def simulate(cfg: dict):

    import simpy
    env = simpy.Environment()

Parameters that shape the scenario.

    population        = cfg["initial_pop"]        # starting fish in the lake
    capacity          = cfg["carrying_capacity"]  # ecological limit of the lake
    growth_rate       = cfg["growth_rate"]        # how quickly fish reproduce
    quota_fraction    = cfg["quota_fraction"]     # fraction of perceived stock harvested
    perception_delay  = cfg["perception_delay"]   # steps our information is behind
    sim_time          = cfg["sim_time"]           # length of the simulation
    history           = [population] * perception_delay  # ring buffer of past counts
    extracted_total   = 0.0  # running total of fish taken

    done = env.event()

Each tick we look at old population data, set a quota, let fish reproduce, and harvest accordingly. The delay means we might be taking too many fish.

    def dynamics():
        nonlocal population, extracted_total
        for t in range(sim_time):
            perceived = history[0]  # what the managers believe
            quota     = quota_fraction * perceived
            birth     = growth_rate * population * (1 - population / capacity)
            population = max(population + birth - quota, 0)  # update stock safely
            extracted_total += quota
            history.append(population)
            history.pop(0)  # advance the perception window

            yield env.timeout(1)

        progress(100)

When all done report the final state of the ecosystem.

        done.succeed({
            "final_population": population,
            "utilisation": population / capacity,
            "survived": population > 0.05 * capacity,
            "extracted_total": extracted_total
        })
    env.process(dynamics())

Record the actual population and the quota computed from our stale perception.

    def recorder():
        while True:
            perceived = history[0]
            quota = quota_fraction * perceived
            probe("population", env.now, population)
            probe("quota", env.now, quota)
            probe("gap_to_capacity", env.now, capacity - population)
            probe("extracted_total", env.now, extracted_total)
            yield env.timeout(0.5)  # half-step for smoother lines
    env.process(recorder())
    
    env.run(until=done)
    return done.value

Install the necessary required libraries for this simulation.

def requirements():
    return {
        "builtin": ["micropip", "pyyaml"],
        "external": ["simpy==4.1.1"],
    }
config.yaml
initial_pop: 30
carrying_capacity: 100
growth_rate: 0.4
quota_fraction: 0.22
perception_delay: 7
sim_time: 300