-
-
Notifications
You must be signed in to change notification settings - Fork 254
Add continuous predator prey #347
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Ishwarpatra
wants to merge
13
commits into
mesa:main
Choose a base branch
from
Ishwarpatra:add-continuous-predator-prey
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
47c5eb5
PREY-PREDATOR
Ishwarpatra e3e62f8
Add Continuous Space Predator-Prey model for dynamic benchmarking
Ishwarpatra fdc814d
Fix ruff formatting and codespell typos
Ishwarpatra 458112f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] f775c7e
fix: resolve circular import and formatting issues
Ishwarpatra 360e6ea
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 07f39f5
Merge branch 'mesa:main' into add-continuous-predator-prey
Ishwarpatra 03f68b0
refactor: upgrade to Mesa 4 standards (remove deprecated scheduler an…
Ishwarpatra 0d185f7
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 2fe868e
feat: restructure folder, add biological constraints, and Solara UI
Ishwarpatra 61f4cc0
refactor: add lifespans and biological constraints to agents
Ishwarpatra f2ec8d5
fix: combine nested if statements in prey step() for ruff SIM102
Ishwarpatra 82d3c19
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
108 changes: 108 additions & 0 deletions
108
examples/continuous_predator_prey/prey_predator/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| # Continuous Space Predator-Prey Model | ||
|
|
||
| ## Summary | ||
| This model simulates a predator-prey ecosystem in Mesa's experimental `ContinuousSpace`. It demonstrates realistic biological behaviors including lifespans, mating requirements, and energy-based reproduction. | ||
|
|
||
| This model has been updated to **Mesa 4.0 standards**, using the native `agents.shuffle_do()` activation and automatic agent ID management. | ||
|
|
||
| ## Agents | ||
|
|
||
| ### Prey | ||
| - **Movement:** Random motion through continuous space | ||
| - **Lifespan:** Maximum age of 40 steps (die of old age) | ||
| - **Mating:** Requires at least 1 mate nearby to reproduce | ||
| - **Carrying Capacity:** Will NOT reproduce if 6+ prey are nearby (simulates overcrowding/limited resources) | ||
| - **Reproduction:** Asexual cloning when conditions are favorable | ||
|
|
||
| ### Predators | ||
| - **Movement:** Random motion with higher speed than prey | ||
| - **Lifespan:** Maximum age of 60 steps (die of old age) | ||
| - **Energy System:** Lose 1 energy per step, gain energy from eating prey | ||
| - **Starvation:** Die immediately if energy reaches 0 | ||
| - **Hunting:** Hunt prey within radius of 4.0 | ||
| - **Reproduction:** Only reproduce when energy > 30 (must be well-fed) | ||
|
|
||
| ## Biological Features | ||
|
|
||
| This model implements realistic Lotka-Volterra population dynamics: | ||
|
|
||
| 1. **Natural Death:** Both species die of old age, preventing immortality | ||
| 2. **Mating Constraints:** Prey need mates nearby, preventing infinite cloning | ||
| 3. **Overcrowding:** Prey reproduction limited by local population density | ||
| 4. **Energy-Based Reproduction:** Predators only reproduce after successful hunting | ||
| 5. **Starting Energy:** Predators spawn with random energy to survive initial hunting | ||
|
|
||
| ## How to Run | ||
|
|
||
| ### Interactive Visualization (SolaraViz) | ||
| To launch the interactive web dashboard with sliders and real-time visualization: | ||
|
|
||
| ```bash | ||
| solara run app.py | ||
| ``` | ||
|
|
||
| This will open a web browser with: | ||
| - **Spatial Map:** Blue circles (Prey) and red triangles (Predators) moving in continuous space | ||
| - **Population Chart:** Real-time line graph showing prey and predator population cycles | ||
| - **Interactive Sliders:** Adjust initial populations and reproduction rates | ||
|
|
||
| ### Headless Mode | ||
| To run a headless benchmark of the model for 50 steps: | ||
|
|
||
| ```bash | ||
| python run.py | ||
| ``` | ||
|
|
||
| ## Installation | ||
|
|
||
| Make sure you have Mesa installed: | ||
|
|
||
| ```bash | ||
| pip install -r requirements.txt | ||
| ``` | ||
|
|
||
| For the interactive visualization, also ensure `solara` is installed: | ||
|
|
||
| ```bash | ||
| pip install solara | ||
| ``` | ||
|
|
||
| ## Model Parameters | ||
|
|
||
| | Parameter | Default | Description | | ||
| |-----------|---------|-------------| | ||
| | `width` | 100 | Width of the continuous space | | ||
| | `height` | 100 | Height of the continuous space | | ||
| | `initial_prey` | 100 | Starting prey population | | ||
| | `initial_predators` | 20 | Starting predator population | | ||
| | `prey_reproduce` | 0.04 | Probability of prey reproduction per step | | ||
| | `predator_reproduce` | 0.05 | Probability of predator reproduction per step | | ||
| | `predator_gain_from_food` | 20 | Energy gained by predator when eating prey | | ||
|
|
||
| ## Expected Behavior | ||
|
|
||
| When running the simulation, you should observe **Lotka-Volterra population cycles**: | ||
|
|
||
| 1. Prey population grows rapidly (blue line rises) | ||
| 2. Predator population follows as food becomes abundant (red line rises) | ||
| 3. Prey population crashes due to predation (blue line falls) | ||
| 4. Predator population crashes due to starvation (red line falls) | ||
| 5. Cycle repeats as prey recover | ||
|
|
||
| The population chart should show the classic "chasing waves" pattern where the red predator line follows the blue prey line with a time delay. | ||
|
|
||
| ## Code Formatting | ||
|
|
||
| Mesa strictly enforces the PEP8 coding standard. Run this command in your terminal to automatically format your files: | ||
|
|
||
| ```bash | ||
| ruff check . --fix | ||
| ruff format . | ||
| ``` | ||
|
|
||
| ## Files | ||
|
|
||
| - `model.py` - Main model class with Mesa 4.0 native activation | ||
| - `agents.py` - Prey and Predator agent classes with biological behaviors | ||
| - `app.py` - SolaraViz interactive visualization | ||
| - `run.py` - Headless simulation runner |
136 changes: 136 additions & 0 deletions
136
examples/continuous_predator_prey/prey_predator/agents.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| import math | ||
|
|
||
| from mesa.experimental.continuous_space import ContinuousSpaceAgent | ||
|
|
||
|
|
||
| class Prey(ContinuousSpaceAgent): | ||
| # a prey agent which has random motion in continuous space | ||
|
|
||
| def __init__(self, space, model, pos, speed=1.0, max_age=40): | ||
| # unique_id is handled automatically by super() | ||
| super().__init__(space, model) | ||
| self.position = pos | ||
| self.speed = speed | ||
|
|
||
| # we need give them random starting age so they don't all die on the exact same step! | ||
| self.age = self.random.randint(0, max_age) | ||
| self.max_age = max_age # it's maximum lifespan of prey agent | ||
|
|
||
| # now we need make it motion random so we need move agent to random position based on it's speed | ||
| def random_move(self): | ||
| # random angle between 0 and 2pi would taken | ||
| angle = self.random.uniform(0, 2 * math.pi) | ||
| # change in x and y by trigonometry | ||
| dx = math.cos(angle) * self.speed | ||
| dy = math.sin(angle) * self.speed | ||
| # NEW POSITION calculated | ||
| new_x = self.position[0] + dx | ||
| new_y = self.position[1] + dy | ||
|
|
||
| new_pos = (new_x, new_y) | ||
|
|
||
| # to make sure it doesn't go off the map; use the new helper | ||
| if self.model.space.torus: | ||
| new_pos = self.model.space.torus_correct(new_pos) | ||
|
|
||
| # update the stored position | ||
| self.position = new_pos | ||
|
|
||
| def step(self): | ||
| # it calls the random move function to move the agent in each step of the simulation | ||
|
|
||
| # 1. DYING OLD: increase age, and die if too old | ||
| self.age += 1 # age increasing each step | ||
| if self.age > self.max_age: | ||
| self.remove() # agent removed when too old | ||
| return | ||
|
|
||
| self.random_move() | ||
|
|
||
| # check surroundings for mating and overcrowding | ||
| neighbors, _ = self.get_neighbors_in_radius( | ||
| radius=2.5 | ||
| ) # it get nearby agents within radius | ||
| prey_neighbors = [ | ||
| n for n in neighbors if isinstance(n, Prey) | ||
| ] # it filter to find only prey | ||
|
|
||
| # 2. MATING & CROWDING: must have at least 1 mate nearby (>0), | ||
| # but won't reproduce if it's too overcrowded (<6) | ||
| # it check not too lonely and not too crowded | ||
| if ( | ||
| 0 < len(prey_neighbors) < 6 | ||
| and self.random.random() < self.model.prey_reproduce | ||
| ): | ||
| # create new prey at the exact same position (asexual reproduction) | ||
| Prey(self.model.space, self.model, self.position, self.speed, self.max_age) | ||
|
|
||
|
|
||
| class Predator(ContinuousSpaceAgent): | ||
| # a predator agent which moves randomly but also hunts nearby prey | ||
|
|
||
| def __init__(self, space, model, pos, speed=1.5, energy=0, max_age=60): | ||
| # unique_id is handled automatically by super() | ||
| super().__init__(space, model) | ||
| self.position = pos | ||
| self.speed = speed | ||
| self.energy = energy # energy level of the predator; it need for survival and reproduction | ||
|
|
||
| # dying old - we need give them random starting age | ||
| self.age = self.random.randint(0, max_age) | ||
| self.max_age = max_age # it's maximum lifespan of predator agent | ||
|
|
||
| def random_move(self): | ||
| # here we make it hunt the prey if it is nearby | ||
| # we will same random walk logic used in prey agent | ||
| angle = self.random.uniform(0, 2 * math.pi) | ||
| dx = math.cos(angle) * self.speed | ||
| dy = math.sin(angle) * self.speed | ||
|
|
||
| new_pos = (self.position[0] + dx, self.position[1] + dy) | ||
| if self.model.space.torus: | ||
| new_pos = self.model.space.torus_correct(new_pos) | ||
| self.position = new_pos | ||
|
|
||
| def step(self): | ||
| # 1. DYING OLD | ||
| self.age += 1 # age increasing each step | ||
| if self.age > self.max_age: | ||
| self.remove() # agent removed when too old | ||
| return | ||
|
|
||
| self.random_move() | ||
| self.energy -= 1 # predator lose energy each step; it's cost of living | ||
|
|
||
| # get nearby agents within hunting radius (increased from 2.0 so they can find food easier) | ||
| neighbors, _ = self.get_neighbors_in_radius(radius=4.0) | ||
| prey_neighbors = [ | ||
| n for n in neighbors if isinstance(n, Prey) | ||
| ] # it filter the nearby agents to find the prey | ||
|
|
||
| if prey_neighbors: | ||
| prey_to_eat = self.random.choice( | ||
| prey_neighbors | ||
| ) # choose random prey to eat | ||
| self.energy += self.model.predator_gain_from_food # gain energy from eating | ||
|
|
||
| # Modern Mesa 4.0 removal (replaces all the contextlib fallbacks) | ||
| prey_to_eat.remove() # prey agent removed from simulation | ||
|
|
||
| # Starvation - if energy reaches 0, predator dies | ||
| if self.energy <= 0: | ||
| self.remove() # predator removed when starved | ||
| return | ||
|
|
||
| # 3. ENERGY REPRODUCTION: they MUST have high energy (>30) to reproduce now! | ||
| if self.energy > 30 and self.random.random() < self.model.predator_reproduce: | ||
| self.energy /= 2 # reproduction costs energy; parent loses half | ||
| # create new predator with half of parent's energy | ||
| Predator( | ||
| self.model.space, | ||
| self.model, | ||
| self.position, | ||
| self.speed, | ||
| int(self.energy), | ||
| self.max_age, | ||
| ) # new predator starts with half parent's energy | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| from agents import Predator, Prey | ||
| from mesa.visualization import Slider, SolaraViz, SpaceRenderer, make_plot_component | ||
| from mesa.visualization.components import AgentPortrayalStyle | ||
| from model import PredatorPreyModel | ||
|
|
||
|
|
||
| def agent_draw(agent): | ||
| """Define how the agents look in the UI.""" | ||
| if isinstance(agent, Prey): | ||
| return AgentPortrayalStyle( | ||
| color="blue", | ||
| size=15, | ||
| marker="o", # Circle for prey | ||
| ) | ||
| elif isinstance(agent, Predator): | ||
| return AgentPortrayalStyle( | ||
| color="red", | ||
| size=30, | ||
| marker="^", # Triangle for predator | ||
| ) | ||
|
|
||
|
|
||
| # Interactive sliders for the Solara UI | ||
| model_params = { | ||
| "initial_prey": Slider( | ||
| label="Initial Prey", | ||
| value=100, | ||
| min=10, | ||
| max=300, | ||
| step=10, | ||
| ), | ||
| "initial_predators": Slider( | ||
| label="Initial Predators", | ||
| value=20, | ||
| min=1, | ||
| max=100, | ||
| step=5, | ||
| ), | ||
| "prey_reproduce": Slider( | ||
| label="Prey Reproduction Rate", | ||
| value=0.04, | ||
| min=0.01, | ||
| max=0.2, | ||
| step=0.01, | ||
| ), | ||
| "predator_reproduce": Slider( | ||
| label="Predator Reproduction Rate", | ||
| value=0.05, | ||
| min=0.01, | ||
| max=0.2, | ||
| step=0.01, | ||
| ), | ||
| "width": 100, | ||
| "height": 100, | ||
| } | ||
|
|
||
|
|
||
| # Initialize the model | ||
| model = PredatorPreyModel() | ||
|
|
||
|
|
||
| # Set up the continuous space renderer | ||
| renderer = ( | ||
| SpaceRenderer( | ||
| model, | ||
| backend="matplotlib", | ||
| ) | ||
| .setup_agents(agent_draw) | ||
| .render() | ||
| ) | ||
|
|
||
|
|
||
| # Set up the Population Line Chart | ||
| population_chart = make_plot_component({"Prey": "blue", "Predators": "red"}) | ||
|
|
||
|
|
||
| # Create the Solara webpage | ||
| page = SolaraViz( | ||
| model, | ||
| renderer, # Pass renderer here as the second argument! | ||
| components=[population_chart], # ONLY the chart goes in the list! | ||
| model_params=model_params, | ||
| name="Continuous Space Predator-Prey", | ||
| ) | ||
| page # noqa |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need not to do it, its already handled via position.setter