Fishery Simulation
A miniature world of systems-thinking with stocks, flows, and feedback loops modeling fish population dynamics and ecosystem management.
Level:Beginner
Feedback Loops
Understand the balancing and reinforcing feedback loops that drive system behavior and create complex dynamics in systems thinking.
Explore Feedback LoopsManaging 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"],
}
initial_pop: 30
carrying_capacity: 100
growth_rate: 0.4
quota_fraction: 0.22
perception_delay: 7
sim_time: 300