├── examples
├── __init__.py
├── aco_tsp
│ ├── aco_tsp
│ │ ├── __init__.py
│ │ └── data
│ │ │ └── kroA100.tsp
│ ├── run_tsp.py
│ └── app.py
├── el_farol
│ ├── el_farol
│ │ ├── __init__.py
│ │ ├── model.py
│ │ └── agents.py
│ ├── requirements.txt
│ ├── tests.py
│ └── README.md
├── hotelling_law
│ ├── __init__.py
│ ├── hotelling_law
│ │ └── __init__.py
│ ├── requirements.txt
│ ├── hotelling_law_sim.png
│ ├── tests.py
│ └── Readme.md
├── termites
│ ├── termites
│ │ ├── __init__.py
│ │ ├── model.py
│ │ └── agents.py
│ ├── app.py
│ └── README.md
├── warehouse
│ ├── warehouse
│ │ ├── __init__.py
│ │ └── make_warehouse.py
│ ├── requirements.txt
│ ├── Readme.md
│ └── app.py
├── color_patches
│ ├── color_patches
│ │ └── __init__.py
│ ├── requirements.txt
│ ├── app.py
│ └── Readme.md
├── forest_fire
│ ├── forest_fire
│ │ ├── __init__.py
│ │ ├── agent.py
│ │ └── model.py
│ ├── requirements.txt
│ ├── app.py
│ └── readme.md
├── hex_snowflake
│ ├── requirements.txt
│ ├── run.py
│ ├── hex_snowflake
│ │ ├── server.py
│ │ ├── portrayal.py
│ │ ├── model.py
│ │ └── cell.py
│ └── Readme.md
├── shape_example
│ ├── requirements.txt
│ ├── run.py
│ ├── Readme.md
│ └── shape_example
│ │ ├── model.py
│ │ └── server.py
├── boltzmann_wealth_model_network
│ ├── boltzmann_wealth_model_network
│ │ ├── __init__.py
│ │ ├── agents.py
│ │ └── model.py
│ ├── README.md
│ └── app.py
├── charts
│ ├── requirements.txt
│ ├── run.py
│ ├── Readme.md
│ └── charts
│ │ └── server.py
├── bank_reserves
│ ├── requirements.txt
│ ├── batch_run.py
│ ├── app.py
│ └── Readme.md
├── virus_antibody
│ ├── requirements.txt
│ ├── images
│ │ ├── pattern.png
│ │ ├── viruses_win.png
│ │ ├── antibodies_win.png
│ │ ├── grow_virus_wins.png
│ │ ├── grow_antibody_wins.png
│ │ └── virus_antibody_architecture.png
│ ├── README.md
│ └── app.py
├── caching_and_replay
│ ├── requirements.txt
│ ├── run.py
│ ├── cacheablemodel.py
│ ├── server.py
│ ├── README.md
│ └── model.py
└── conways_game_of_life_fast
│ ├── GoL_fast_screenshot.png
│ ├── app.py
│ ├── model.py
│ └── Readme.md
├── rl
├── .gitignore
├── requirements.txt
├── boltzmann_money
│ ├── ppo_agent.gif
│ ├── train.py
│ ├── README.md
│ └── server.py
├── wolf_sheep
│ ├── resources
│ │ ├── wolf.png
│ │ ├── sheep.png
│ │ └── wolf_sheep.gif
│ ├── train_config.py
│ ├── README.md
│ ├── utility.py
│ └── agents.py
├── epstein_civil_violence
│ ├── resources
│ │ └── epstein.gif
│ ├── train_config.py
│ ├── README.md
│ ├── agent.py
│ └── utility.py
├── example.py
├── train.py
└── README.md
├── gis
├── geo_sir
│ ├── geo_sir
│ │ └── __init__.py
│ ├── requirements.txt
│ ├── README.md
│ └── app.py
├── rainfall
│ ├── rainfall
│ │ ├── __init__.py
│ │ └── space.py
│ ├── requirements.txt
│ ├── data
│ │ └── elevation.asc.gz
│ ├── app.py
│ └── README.md
├── population
│ ├── population
│ │ ├── __init__.py
│ │ ├── space.py
│ │ └── model.py
│ ├── requirements.txt
│ ├── data
│ │ ├── clip.zip
│ │ ├── lake.zip
│ │ └── popu.asc.gz
│ ├── app.py
│ └── README.md
├── agents_and_networks
│ ├── outputs
│ │ └── .gitkeep
│ ├── src
│ │ ├── __init__.py
│ │ ├── agent
│ │ │ ├── __init__.py
│ │ │ ├── geo_agents.py
│ │ │ └── building.py
│ │ ├── model
│ │ │ └── __init__.py
│ │ ├── space
│ │ │ ├── __init__.py
│ │ │ ├── campus.py
│ │ │ ├── road_network.py
│ │ │ └── utils.py
│ │ ├── visualization
│ │ │ ├── __init__.py
│ │ │ └── utils.py
│ │ └── logger.py
│ ├── data
│ │ ├── ub
│ │ │ ├── UB_Rds.zip
│ │ │ ├── UB_bld.zip
│ │ │ ├── hydrol.zip
│ │ │ ├── hydrop.zip
│ │ │ └── UB_walkway_line.zip
│ │ └── gmu
│ │ │ ├── hydrol.zip
│ │ │ ├── hydrop.zip
│ │ │ ├── Mason_Rds.zip
│ │ │ ├── Mason_bld.zip
│ │ │ └── Mason_walkway_line.zip
│ ├── setup.py
│ ├── requirements.txt
│ ├── .gitignore
│ ├── README.md
│ └── app.py
├── urban_growth
│ ├── urban_growth
│ │ └── __init__.py
│ ├── requirements.txt
│ ├── data
│ │ ├── road1_santafe.asc.gz
│ │ ├── slope_santafe.asc.gz
│ │ ├── urban_santafe.asc.gz
│ │ ├── landuse_santafe.asc.gz
│ │ └── excluded_santafe.asc.gz
│ ├── README.md
│ └── app.py
├── geo_schelling
│ ├── requirements.txt
│ ├── README.md
│ └── app.py
└── geo_schelling_points
│ ├── geo_schelling_points
│ ├── __init__.py
│ ├── space.py
│ ├── agents.py
│ └── model.py
│ ├── requirements.txt
│ ├── README.md
│ └── app.py
├── setup.cfg
├── .codespellignore
├── codecov.yaml
├── LICENSE
├── .pre-commit-config.yaml
├── test_gis_examples.py
├── test_examples.py
├── .gitignore
├── .github
└── workflows
│ ├── test_examples.yml
│ └── test_gis_examples.yml
├── pyproject.toml
└── .coderabbit.yaml
/examples/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/rl/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__/
--------------------------------------------------------------------------------
/gis/geo_sir/geo_sir/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/gis/rainfall/rainfall/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/aco_tsp/aco_tsp/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/el_farol/el_farol/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/hotelling_law/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/termites/termites/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/gis/population/population/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/warehouse/warehouse/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/gis/agents_and_networks/outputs/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/gis/agents_and_networks/src/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/gis/urban_growth/urban_growth/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/color_patches/color_patches/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/forest_fire/forest_fire/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/hex_snowflake/requirements.txt:
--------------------------------------------------------------------------------
1 | mesa~=2.0
--------------------------------------------------------------------------------
/examples/hotelling_law/hotelling_law/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/gis/agents_and_networks/src/agent/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/gis/agents_and_networks/src/model/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/gis/agents_and_networks/src/space/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/gis/geo_sir/requirements.txt:
--------------------------------------------------------------------------------
1 | mesa-geo~=0.9.0
2 |
--------------------------------------------------------------------------------
/gis/urban_growth/requirements.txt:
--------------------------------------------------------------------------------
1 | mesa-geo~=0.9.0
--------------------------------------------------------------------------------
/examples/hotelling_law/requirements.txt:
--------------------------------------------------------------------------------
1 | mesa
2 | scipy
--------------------------------------------------------------------------------
/examples/shape_example/requirements.txt:
--------------------------------------------------------------------------------
1 | mesa~=2.0
2 |
--------------------------------------------------------------------------------
/examples/warehouse/requirements.txt:
--------------------------------------------------------------------------------
1 | mesa[rec]>=3
2 |
--------------------------------------------------------------------------------
/gis/agents_and_networks/src/visualization/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/gis/population/requirements.txt:
--------------------------------------------------------------------------------
1 | mesa-geo~=0.9.0
2 |
--------------------------------------------------------------------------------
/gis/rainfall/requirements.txt:
--------------------------------------------------------------------------------
1 | mesa-geo~=0.9.0
2 |
--------------------------------------------------------------------------------
/gis/geo_schelling/requirements.txt:
--------------------------------------------------------------------------------
1 | mesa-geo~=0.9.0
2 |
--------------------------------------------------------------------------------
/gis/geo_schelling_points/geo_schelling_points/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [options]
2 | packages =
3 | examples
4 |
--------------------------------------------------------------------------------
/gis/geo_schelling_points/requirements.txt:
--------------------------------------------------------------------------------
1 | mesa-geo~=0.9.0
2 |
--------------------------------------------------------------------------------
/examples/forest_fire/requirements.txt:
--------------------------------------------------------------------------------
1 | jupyter
2 | mesa[viz]>=3.0
3 |
--------------------------------------------------------------------------------
/examples/color_patches/requirements.txt:
--------------------------------------------------------------------------------
1 | mesa[viz]>=3.0
2 | networkx
3 |
--------------------------------------------------------------------------------
/rl/requirements.txt:
--------------------------------------------------------------------------------
1 | stable-baselines3
2 | seaborn
3 | mesa
4 | tensorboard
--------------------------------------------------------------------------------
/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/charts/requirements.txt:
--------------------------------------------------------------------------------
1 | itertools
2 | mesa~=2.0
3 | numpy
4 | pandas
5 |
--------------------------------------------------------------------------------
/examples/el_farol/requirements.txt:
--------------------------------------------------------------------------------
1 | jupyter
2 | matplotlib
3 | mesa
4 | numpy
5 | seaborn
6 |
--------------------------------------------------------------------------------
/examples/bank_reserves/requirements.txt:
--------------------------------------------------------------------------------
1 | mesa[viz]>=3.1.4
2 | networkx
3 | numpy
4 | pandas
5 |
--------------------------------------------------------------------------------
/examples/charts/run.py:
--------------------------------------------------------------------------------
1 | from charts.server import server
2 |
3 | server.launch(open_browser=True)
4 |
--------------------------------------------------------------------------------
/examples/virus_antibody/requirements.txt:
--------------------------------------------------------------------------------
1 | mesa>=3.2
2 | numpy>=2
3 | matplotlib>=3.7
4 | solara>=1.50
5 |
--------------------------------------------------------------------------------
/gis/population/data/clip.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/gis/population/data/clip.zip
--------------------------------------------------------------------------------
/gis/population/data/lake.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/gis/population/data/lake.zip
--------------------------------------------------------------------------------
/.codespellignore:
--------------------------------------------------------------------------------
1 | hist
2 | hart
3 | mutch
4 | ist
5 | inactivate
6 | ue
7 | fpr
8 | falsy
9 | assertIn
10 | nD
--------------------------------------------------------------------------------
/examples/caching_and_replay/requirements.txt:
--------------------------------------------------------------------------------
1 | mesa
2 | git+https://github.com/Logende/mesa-replay@main#egg=Mesa-Replay
--------------------------------------------------------------------------------
/examples/hex_snowflake/run.py:
--------------------------------------------------------------------------------
1 | from hex_snowflake.server import server
2 |
3 | server.launch(open_browser=True)
4 |
--------------------------------------------------------------------------------
/examples/shape_example/run.py:
--------------------------------------------------------------------------------
1 | from shape_example.server import server
2 |
3 | server.launch(open_browser=True)
4 |
--------------------------------------------------------------------------------
/gis/population/data/popu.asc.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/gis/population/data/popu.asc.gz
--------------------------------------------------------------------------------
/rl/boltzmann_money/ppo_agent.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/rl/boltzmann_money/ppo_agent.gif
--------------------------------------------------------------------------------
/rl/wolf_sheep/resources/wolf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/rl/wolf_sheep/resources/wolf.png
--------------------------------------------------------------------------------
/gis/rainfall/data/elevation.asc.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/gis/rainfall/data/elevation.asc.gz
--------------------------------------------------------------------------------
/rl/wolf_sheep/resources/sheep.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/rl/wolf_sheep/resources/sheep.png
--------------------------------------------------------------------------------
/rl/wolf_sheep/resources/wolf_sheep.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/rl/wolf_sheep/resources/wolf_sheep.gif
--------------------------------------------------------------------------------
/examples/virus_antibody/images/pattern.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/examples/virus_antibody/images/pattern.png
--------------------------------------------------------------------------------
/gis/agents_and_networks/data/ub/UB_Rds.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/gis/agents_and_networks/data/ub/UB_Rds.zip
--------------------------------------------------------------------------------
/gis/agents_and_networks/data/ub/UB_bld.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/gis/agents_and_networks/data/ub/UB_bld.zip
--------------------------------------------------------------------------------
/gis/agents_and_networks/data/ub/hydrol.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/gis/agents_and_networks/data/ub/hydrol.zip
--------------------------------------------------------------------------------
/gis/agents_and_networks/data/ub/hydrop.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/gis/agents_and_networks/data/ub/hydrop.zip
--------------------------------------------------------------------------------
/gis/urban_growth/data/road1_santafe.asc.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/gis/urban_growth/data/road1_santafe.asc.gz
--------------------------------------------------------------------------------
/gis/urban_growth/data/slope_santafe.asc.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/gis/urban_growth/data/slope_santafe.asc.gz
--------------------------------------------------------------------------------
/gis/urban_growth/data/urban_santafe.asc.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/gis/urban_growth/data/urban_santafe.asc.gz
--------------------------------------------------------------------------------
/examples/hotelling_law/hotelling_law_sim.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/examples/hotelling_law/hotelling_law_sim.png
--------------------------------------------------------------------------------
/gis/agents_and_networks/data/gmu/hydrol.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/gis/agents_and_networks/data/gmu/hydrol.zip
--------------------------------------------------------------------------------
/gis/agents_and_networks/data/gmu/hydrop.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/gis/agents_and_networks/data/gmu/hydrop.zip
--------------------------------------------------------------------------------
/gis/urban_growth/data/landuse_santafe.asc.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/gis/urban_growth/data/landuse_santafe.asc.gz
--------------------------------------------------------------------------------
/examples/virus_antibody/images/viruses_win.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/examples/virus_antibody/images/viruses_win.png
--------------------------------------------------------------------------------
/gis/agents_and_networks/data/gmu/Mason_Rds.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/gis/agents_and_networks/data/gmu/Mason_Rds.zip
--------------------------------------------------------------------------------
/gis/agents_and_networks/data/gmu/Mason_bld.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/gis/agents_and_networks/data/gmu/Mason_bld.zip
--------------------------------------------------------------------------------
/gis/urban_growth/data/excluded_santafe.asc.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/gis/urban_growth/data/excluded_santafe.asc.gz
--------------------------------------------------------------------------------
/rl/epstein_civil_violence/resources/epstein.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/rl/epstein_civil_violence/resources/epstein.gif
--------------------------------------------------------------------------------
/examples/virus_antibody/images/antibodies_win.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/examples/virus_antibody/images/antibodies_win.png
--------------------------------------------------------------------------------
/examples/virus_antibody/images/grow_virus_wins.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/examples/virus_antibody/images/grow_virus_wins.png
--------------------------------------------------------------------------------
/gis/agents_and_networks/data/ub/UB_walkway_line.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/gis/agents_and_networks/data/ub/UB_walkway_line.zip
--------------------------------------------------------------------------------
/examples/virus_antibody/images/grow_antibody_wins.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/examples/virus_antibody/images/grow_antibody_wins.png
--------------------------------------------------------------------------------
/gis/agents_and_networks/data/gmu/Mason_walkway_line.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/gis/agents_and_networks/data/gmu/Mason_walkway_line.zip
--------------------------------------------------------------------------------
/examples/conways_game_of_life_fast/GoL_fast_screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/examples/conways_game_of_life_fast/GoL_fast_screenshot.png
--------------------------------------------------------------------------------
/examples/virus_antibody/images/virus_antibody_architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesa/mesa-examples/HEAD/examples/virus_antibody/images/virus_antibody_architecture.png
--------------------------------------------------------------------------------
/codecov.yaml:
--------------------------------------------------------------------------------
1 | coverage:
2 | status:
3 | project:
4 | default:
5 | target: 80%
6 | threshold: 1%
7 |
8 | ignore: []
9 |
10 | comment: off
11 |
--------------------------------------------------------------------------------
/gis/agents_and_networks/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import find_packages, setup
2 |
3 | setup(
4 | name="src",
5 | packages=find_packages(),
6 | version="0.1.0",
7 | description="GMU-Social Model in Python",
8 | author="Wang Boyu",
9 | license="",
10 | )
11 |
--------------------------------------------------------------------------------
/gis/agents_and_networks/requirements.txt:
--------------------------------------------------------------------------------
1 | # local package
2 | -e .
3 |
4 | # external requirements
5 | mesa-geo~=0.9.0
6 | geopandas
7 | numpy
8 | pandas
9 | matplotlib
10 | seaborn
11 | scikit-learn
12 | jupyter
13 | notebook
14 | jupyter_contrib_nbextensions
15 | jupyter_nbextensions_configurator
16 | autopep8
17 | tqdm
18 | momepy
19 | networkx
20 | black[jupyter]
21 |
--------------------------------------------------------------------------------
/gis/agents_and_networks/src/logger.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 |
4 | def logger(func):
5 | from functools import wraps
6 |
7 | @wraps(func)
8 | def wrapper(*args, **kwargs):
9 | logger = logging.getLogger(func.__name__)
10 | logger.info(f"About to run {func.__name__}")
11 | out = func(*args, **kwargs)
12 | logger.info(f"Done running {func.__name__}")
13 | return out
14 |
15 | return wrapper
16 |
--------------------------------------------------------------------------------
/examples/hex_snowflake/hex_snowflake/server.py:
--------------------------------------------------------------------------------
1 | import mesa
2 | from hex_snowflake.model import HexSnowflake
3 | from hex_snowflake.portrayal import portrayCell
4 |
5 | width, height = 50, 50
6 |
7 | # Make a world that is 50x50, on a 500x500 display.
8 | canvas_element = mesa.visualization.CanvasHexGrid(portrayCell, width, height, 500, 500)
9 |
10 | server = mesa.visualization.ModularServer(
11 | HexSnowflake, [canvas_element], "Hex Snowflake", {"height": height, "width": width}
12 | )
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2022 Core Mesa Team and contributors
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
--------------------------------------------------------------------------------
/examples/hex_snowflake/hex_snowflake/portrayal.py:
--------------------------------------------------------------------------------
1 | def portrayCell(cell):
2 | """This function is registered with the visualization server to be called
3 | each tick to indicate how to draw the cell in its current state.
4 | :param cell: the cell in the simulation
5 | :return: the portrayal dictionary.
6 | """
7 | if cell is None:
8 | raise AssertionError
9 | return {
10 | "Shape": "hex",
11 | "r": 1,
12 | "Filled": "true",
13 | "Layer": 0,
14 | "x": cell.x,
15 | "y": cell.y,
16 | "Color": "black" if cell.isAlive else "white",
17 | }
18 |
--------------------------------------------------------------------------------
/examples/el_farol/tests.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from el_farol.model import ElFarolBar
3 |
4 | np.random.seed(1)
5 | crowd_threshold = 60
6 |
7 |
8 | def test_convergence():
9 | # Testing that the attendance converges to crowd_threshold
10 | attendances = []
11 | for _ in range(10):
12 | model = ElFarolBar(N=100, crowd_threshold=crowd_threshold, memory_size=10)
13 | for _ in range(100):
14 | model.step()
15 | attendances.append(model.attendance)
16 | mean = np.mean(attendances)
17 | standard_deviation = np.std(attendances)
18 | deviation = abs(mean - crowd_threshold)
19 | assert deviation < standard_deviation
20 |
--------------------------------------------------------------------------------
/examples/forest_fire/forest_fire/agent.py:
--------------------------------------------------------------------------------
1 | from mesa.discrete_space import FixedAgent
2 |
3 |
4 | class TreeCell(FixedAgent):
5 | """A tree cell.
6 |
7 | Attributes:
8 | condition: Can be "Fine", "On Fire", or "Burned Out"
9 |
10 | """
11 |
12 | def __init__(self, model, cell):
13 | """Create a new tree.
14 |
15 | Args:
16 | model: standard model reference for agent.
17 | """
18 | super().__init__(model)
19 | self.condition = "Fine"
20 | self.cell = cell
21 |
22 | def step(self):
23 | """If the tree is on fire, spread it to fine trees nearby."""
24 | if self.condition == "On Fire":
25 | for neighbor in self.cell.neighborhood.agents:
26 | if neighbor.condition == "Fine":
27 | neighbor.condition = "On Fire"
28 | self.condition = "Burned Out"
29 |
--------------------------------------------------------------------------------
/gis/agents_and_networks/src/agent/geo_agents.py:
--------------------------------------------------------------------------------
1 | import mesa
2 | import mesa_geo as mg
3 | import pyproj
4 | from shapely.geometry import Point
5 |
6 |
7 | class Driveway(mg.GeoAgent):
8 | unique_id: int
9 | model: mesa.Model
10 | geometry: Point
11 | crs: pyproj.CRS
12 |
13 | def __init__(self, model, geometry, crs) -> None:
14 | super().__init__(model, geometry, crs)
15 |
16 |
17 | class LakeAndRiver(mg.GeoAgent):
18 | unique_id: int
19 | model: mesa.Model
20 | geometry: Point
21 | crs: pyproj.CRS
22 |
23 | def __init__(self, model, geometry, crs) -> None:
24 | super().__init__(model, geometry, crs)
25 |
26 |
27 | class Walkway(mg.GeoAgent):
28 | unique_id: int
29 | model: mesa.Model
30 | geometry: Point
31 | crs: pyproj.CRS
32 |
33 | def __init__(self, model, geometry, crs) -> None:
34 | super().__init__(model, geometry, crs)
35 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | ci:
2 | autoupdate_schedule: 'monthly'
3 |
4 | repos:
5 | - repo: https://github.com/astral-sh/ruff-pre-commit
6 | # Ruff version.
7 | rev: v0.11.8
8 | hooks:
9 | # Run the linter.
10 | - id: ruff
11 | types_or: [ python, pyi, jupyter ]
12 | args: [ --fix ]
13 | # Run the formatter.
14 | - id: ruff-format
15 | types_or: [ python, pyi, jupyter ]
16 | - repo: https://github.com/asottile/pyupgrade
17 | rev: v3.19.1
18 | hooks:
19 | - id: pyupgrade
20 | args: [--py311-plus]
21 | - repo: https://github.com/pre-commit/pre-commit-hooks
22 | rev: v5.0.0 # Use the ref you want to point at
23 | hooks:
24 | - id: trailing-whitespace
25 | - id: check-toml
26 | - id: check-yaml
27 | - repo: https://github.com/codespell-project/codespell
28 | rev: v2.4.1
29 | hooks:
30 | - id: codespell
31 | args: [
32 | "--ignore-words",
33 | ".codespellignore",
34 | ]
35 |
--------------------------------------------------------------------------------
/gis/urban_growth/README.md:
--------------------------------------------------------------------------------
1 | # Urban Growth Model
2 |
3 | [](https://www.youtube.com/watch?v=UNtTJL5N83g)
4 |
5 | ## Summary
6 |
7 | This is an implementation of the [UrbanGrowth Model](https://github.com/abmgis/abmgis/tree/master/Chapter06-IntegratingABMandGIS/Models/UrbanGrowth) in Python, using [Mesa](https://github.com/mesa/mesa) and [Mesa-Geo](https://github.com/mesa/mesa-geo).
8 |
9 | ## How to Run
10 |
11 | To run the model interactively, run `solara run app.py` in this directory. e.g.
12 |
13 | ```bash
14 | solara run app.py
15 | ```
16 |
17 | Then open your browser to [http://127.0.0.1:8765/](http://127.0.0.1:8765/) and press the play button `▶`.
18 |
19 | ## License
20 |
21 | The data is from the [UrbanGrowth Model](https://github.com/abmgis/abmgis/tree/master/Chapter06-IntegratingABMandGIS/Models/UrbanGrowth) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/).
22 |
--------------------------------------------------------------------------------
/test_gis_examples.py:
--------------------------------------------------------------------------------
1 | import importlib
2 | import os
3 |
4 | import pytest
5 | from mesa import Model
6 |
7 |
8 | def get_models(directory):
9 | models = []
10 | for root, _, files in os.walk(directory):
11 | for file in files:
12 | if file == "model.py":
13 | module_name = os.path.relpath(os.path.join(root, file[:-3])).replace(
14 | os.sep, "."
15 | )
16 |
17 | module = importlib.import_module(module_name)
18 | for item in dir(module):
19 | obj = getattr(module, item)
20 | if (
21 | isinstance(obj, type)
22 | and issubclass(obj, Model)
23 | and obj is not Model
24 | ):
25 | models.append(obj)
26 |
27 | return models
28 |
29 |
30 | @pytest.mark.parametrize("model_class", get_models("gis"))
31 | def test_model_steps(model_class):
32 | model = model_class() # Assume no arguments are needed
33 | for _ in range(10):
34 | model.step()
35 |
--------------------------------------------------------------------------------
/gis/geo_schelling/README.md:
--------------------------------------------------------------------------------
1 | # GeoSchelling Model (Polygons)
2 |
3 | [](https://www.youtube.com/watch?v=ZnBk_eSw0_M)
4 |
5 | ## Summary
6 |
7 | This is a geoversion of a simplified Schelling example. For the original implementation details please see the Mesa Schelling examples.
8 |
9 | ### GeoSpace
10 |
11 | Instead of an abstract grid space, we represent the space using NUTS-2 regions to create the GeoSpace in the model.
12 |
13 | ### GeoAgent
14 |
15 | NUTS-2 regions are the GeoAgents. The neighbors of a polygon are considered those polygons that touch its border (i.e., edge neighbours). During the running of the model, a polygon queries the colors of the surrounding polygon and if the ratio falls below a certain threshold (e.g., 40% of the same color), the agent moves to an uncolored polygon.
16 |
17 | ## How to Run
18 |
19 | To run the model interactively, run `solara run app.py` in this directory. e.g.
20 |
21 | ```bash
22 | solara run app.py
23 | ```
24 |
25 | Then open your browser to [http://127.0.0.1:8765/](http://127.0.0.1:8765/) and press the play button `▶`.
26 |
--------------------------------------------------------------------------------
/examples/hex_snowflake/Readme.md:
--------------------------------------------------------------------------------
1 | # Conway's Game Of "Life" on a hexagonal grid
2 |
3 | ## Summary
4 |
5 | In this model, each dead cell will become alive if it has exactly one neighbor. Alive cells stay alive forever.
6 |
7 |
8 | ## How to Run
9 |
10 | To run the model interactively, run ``mesa runserver`` in this directory. e.g.
11 |
12 | ```
13 | $ mesa runserver
14 | ```
15 |
16 | Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press ``run``.
17 |
18 | ## Files
19 |
20 | * ``hex_snowflake/cell.py``: Defines the behavior of an individual cell, which can be in two states: DEAD or ALIVE.
21 | * ``hex_snowflake/model.py``: Defines the model itself, initialized with one alive cell at the center.
22 | * ``hex_snowflake/portrayal.py``: Describes for the front end how to render a cell.
23 | * ``hex_snowflake/server.py``: Defines an interactive visualization.
24 | * ``run.py``: Launches the visualization
25 |
26 | ## Further Reading
27 | [Explanation of how hexagon neighbors are calculated. (The method is slightly different for Cartesian coordinates)](http://www.redblobgames.com/grids/hexagons/#neighbors-offset)
28 |
--------------------------------------------------------------------------------
/rl/example.py:
--------------------------------------------------------------------------------
1 | from epstein_civil_violence.model import EpsteinCivilViolenceRL
2 | from epstein_civil_violence.server import run_model
3 | from epstein_civil_violence.train_config import config
4 | from train import train_model
5 |
6 | # Load the environment
7 | env = EpsteinCivilViolenceRL()
8 | observation, info = env.reset(seed=42)
9 | # Running the environment on some random actions
10 | for _ in range(10):
11 | action_dict = {}
12 | for agent in env.schedule.agents:
13 | action_dict[agent.unique_id] = env.action_space.sample()
14 | observation, reward, terminated, truncated, info = env.step(action_dict)
15 |
16 | if terminated or truncated:
17 | observation, info = env.reset()
18 |
19 | # Training a model
20 | train_model(
21 | config, num_iterations=1, result_path="results.txt", checkpoint_dir="checkpoints"
22 | )
23 |
24 | # Running the model and visualizing it
25 | server = run_model(path="checkpoints")
26 | # You can also try running pre-trained checkpoints present in model folder
27 | # server = run_model(path='rl_models/epstein_civil_violence')
28 | server.port = 6005
29 | server.launch(open_browser=True)
30 |
--------------------------------------------------------------------------------
/test_examples.py:
--------------------------------------------------------------------------------
1 | import importlib
2 | import os
3 |
4 | import pytest
5 | from mesa import Model
6 |
7 |
8 | def get_models(directory):
9 | models = []
10 | for root, _, files in os.walk(directory):
11 | for file in files:
12 | if file == "model.py":
13 | module_name = os.path.relpath(os.path.join(root, file[:-3])).replace(
14 | os.sep, "."
15 | )
16 |
17 | module = importlib.import_module(module_name)
18 | for item in dir(module):
19 | obj = getattr(module, item)
20 | if (
21 | isinstance(obj, type)
22 | and issubclass(obj, Model)
23 | and obj is not Model
24 | ):
25 | models.append(obj)
26 |
27 | return models
28 |
29 |
30 | @pytest.mark.parametrize("model_class", get_models("examples"))
31 | def test_model_steps(model_class):
32 | model = model_class() # Assume no arguments are needed
33 | for _ in range(10):
34 | model.step()
35 | assert model.steps == 10
36 |
--------------------------------------------------------------------------------
/gis/geo_schelling/app.py:
--------------------------------------------------------------------------------
1 | import solara
2 | from mesa.visualization import Slider, SolaraViz, make_plot_component
3 | from mesa_geo.visualization import make_geospace_component
4 | from model import GeoSchelling
5 |
6 |
7 | def make_plot_happiness(model):
8 | return solara.Markdown(f"**Happy agents: {model.happy}**")
9 |
10 |
11 | model_params = {
12 | "density": Slider("Agent density", 0.6, 0.1, 1.0, 0.1),
13 | "minority_pc": Slider("Fraction minority", 0.2, 0.00, 1.0, 0.05),
14 | "export_data": False,
15 | }
16 |
17 |
18 | def schelling_draw(agent):
19 | """Portrayal Method for canvas"""
20 | portrayal = {}
21 | if agent.atype is None:
22 | portrayal["color"] = "Grey"
23 | elif agent.atype == 0:
24 | portrayal["color"] = "Red"
25 | else:
26 | portrayal["color"] = "Blue"
27 | return portrayal
28 |
29 |
30 | model = GeoSchelling()
31 | page = SolaraViz(
32 | model,
33 | [
34 | make_geospace_component(schelling_draw, zoom=4),
35 | make_plot_component(["happy"]),
36 | make_plot_happiness,
37 | ],
38 | model_params=model_params,
39 | name="GeoSchelling",
40 | )
41 |
42 | page # noqa
43 |
--------------------------------------------------------------------------------
/rl/boltzmann_money/train.py:
--------------------------------------------------------------------------------
1 | import argparse
2 |
3 | from model import NUM_AGENTS, BoltzmannWealthModelRL
4 | from stable_baselines3 import PPO
5 | from stable_baselines3.common.callbacks import EvalCallback
6 |
7 |
8 | def rl_model(args):
9 | # Create the environment
10 | env = BoltzmannWealthModelRL(N=NUM_AGENTS, width=NUM_AGENTS, height=NUM_AGENTS)
11 | eval_env = BoltzmannWealthModelRL(N=NUM_AGENTS, width=NUM_AGENTS, height=NUM_AGENTS)
12 | eval_callback = EvalCallback(
13 | eval_env, best_model_save_path="./logs/", log_path="./logs/", eval_freq=5000
14 | )
15 | # Define the PPO model
16 | model = PPO("MlpPolicy", env, verbose=1, tensorboard_log="./logs/")
17 |
18 | # Train the model
19 | model.learn(total_timesteps=args.stop_timesteps, callback=[eval_callback])
20 |
21 | # Save the model
22 | model.save("ppo_money_model")
23 |
24 |
25 | if __name__ == "__main__":
26 | # Define the command line arguments
27 | parser = argparse.ArgumentParser()
28 | parser.add_argument(
29 | "--stop-timesteps",
30 | type=int,
31 | default=NUM_AGENTS * 100,
32 | help="Number of timesteps to train.",
33 | )
34 | args = parser.parse_args()
35 | rl_model(args)
36 |
--------------------------------------------------------------------------------
/gis/geo_sir/README.md:
--------------------------------------------------------------------------------
1 | # GeoSIR Epidemics Model
2 |
3 | [](https://www.youtube.com/watch?v=oZShtptaIg4)
4 |
5 | ## Summary
6 |
7 | This is a geoversion of a simple agent-based pandemic SIR model, as an example to show the capabilities of mesa-geo.
8 |
9 | It uses geographical data of Toronto's regions on top of a an Leaflet map to show the location of agents (in a continuous space).
10 |
11 | Person agents are initially located in random positions in the city, then start moving around unless they die.
12 | A fraction of agents start with an infection and may recover or die in each step.
13 | Susceptible agents (those who have never been infected) who come in proximity with an infected agent may become infected.
14 |
15 | Neighbourhood agents represent neighbourhoods in the Toronto, and become hot-spots (colored red) if there are infected agents inside them.
16 | Data obtained from [this link](http://adamw523.com/toronto-geojson/).
17 |
18 | ## How to Run
19 |
20 | To run the model interactively, run `solara run app.py` in this directory. e.g.
21 |
22 | ```bash
23 | solara run app.py
24 | ```
25 |
26 | Then open your browser to [http://127.0.0.1:8765/](http://127.0.0.1:8765/) and press the play button `▶`.
27 |
--------------------------------------------------------------------------------
/rl/boltzmann_money/README.md:
--------------------------------------------------------------------------------
1 | # Balancing Wealth Inequality
2 | This folder showcases how to solve the Boltzmann wealth model with Proximal Policy Optimization (PPO) from Stable Baselines.
3 |
4 | ## Key features:
5 |
6 | - Boltzmann Wealth Model: Agents with varying wealth navigate a grid, aiming to minimize inequality measured by the Gini coefficient.
7 | - PPO Training: A PPO agent is trained to achieve this goal, receiving sparse rewards based on Gini coefficient improvement and a large terminal reward for achieving low inequality.
8 | - Mesa Data Collection and Visualization: The Mesa data collector tool tracks Gini values during training, allowing for real-time visualization.
9 | - Visualization Script: Visualize the trained agent's behavior with Mesa's visualization tools, presenting agent movement and Gini values within the grid. You can run `server.py` file to test it with pre-trained model.
10 |
11 | ## Model Behaviour
12 | As stable baselines controls multiple agents with the same weight, this results in the agents learning to move towards a corner of the grid. These brings all the agents together allowing exchange of money between them resulting in reward maximization.
13 |
14 |
15 |
--------------------------------------------------------------------------------
/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/agents.py:
--------------------------------------------------------------------------------
1 | from mesa.discrete_space import CellAgent
2 |
3 |
4 | class MoneyAgent(CellAgent):
5 | """An agent with fixed initial wealth.
6 |
7 | Each agent starts with 1 unit of wealth and can give 1 unit to other agents
8 | if they occupy the same cell.
9 |
10 | Attributes:
11 | wealth (int): The agent's current wealth (starts at 1)
12 | """
13 |
14 | def __init__(self, model):
15 | """Create a new agent.
16 |
17 | Args:
18 | model (Model): The model instance that contains the agent
19 | """
20 | super().__init__(model)
21 | self.wealth = 1
22 |
23 | def give_money(self):
24 | neighbors = [agent for agent in self.cell.neighborhood.agents if agent != self]
25 | if len(neighbors) > 0:
26 | other = self.random.choice(neighbors)
27 | other.wealth += 1
28 | self.wealth -= 1
29 |
30 | def step(self):
31 | empty_neighbors = [cell for cell in self.cell.neighborhood if cell.is_empty]
32 | if empty_neighbors:
33 | self.cell = self.random.choice(empty_neighbors)
34 |
35 | if self.wealth > 0:
36 | self.give_money()
37 |
--------------------------------------------------------------------------------
/examples/conways_game_of_life_fast/app.py:
--------------------------------------------------------------------------------
1 | from mesa.visualization import SolaraViz, make_plot_component, make_space_component
2 | from model import GameOfLifeModel
3 |
4 | propertylayer_portrayal = {
5 | "cell_layer": {
6 | "color": "Black",
7 | "alpha": 1,
8 | "colorbar": False,
9 | },
10 | }
11 |
12 | model_params = {
13 | "width": {
14 | "type": "SliderInt",
15 | "value": 30,
16 | "label": "Width",
17 | "min": 5,
18 | "max": 60,
19 | "step": 1,
20 | },
21 | "height": {
22 | "type": "SliderInt",
23 | "value": 30,
24 | "label": "Height",
25 | "min": 5,
26 | "max": 60,
27 | "step": 1,
28 | },
29 | "alive_fraction": {
30 | "type": "SliderFloat",
31 | "value": 0.2,
32 | "label": "Cells alive",
33 | "min": 0,
34 | "max": 1,
35 | "step": 0.01,
36 | },
37 | }
38 |
39 | gol = GameOfLifeModel()
40 |
41 | layer_viz = make_space_component(propertylayer_portrayal=propertylayer_portrayal)
42 | TotalAlivePlot = make_plot_component("Cells alive")
43 |
44 | page = SolaraViz(
45 | gol,
46 | components=[layer_viz, TotalAlivePlot],
47 | model_params=model_params,
48 | name="Game of Life Model",
49 | )
50 |
--------------------------------------------------------------------------------
/gis/geo_schelling_points/README.md:
--------------------------------------------------------------------------------
1 | # GeoSchelling Model (Points & Polygons)
2 |
3 | [](https://www.youtube.com/watch?v=iLMU6jfmir8)
4 |
5 | ## Summary
6 |
7 | This is a geoversion of a simplified Schelling example.
8 |
9 | ### GeoSpace
10 |
11 | The NUTS-2 regions are considered as a shared definition of neighborhood among all people agents, instead of a locally defined neighborhood such as Moore or von Neumann.
12 |
13 | ### GeoAgent
14 |
15 | There are two types of GeoAgents: people and regions. Each person resides in a randomly assigned region, and checks the color ratio of its region against a pre-defined "happiness" threshold at every time step. If the ratio falls below a certain threshold (e.g., 40%), the agent is found to be "unhappy", and randomly moves to another region. People are represented as points, with locations randomly chosen within their regions. The color of a region depends on the color of the majority population it contains (i.e., point in polygon calculations).
16 |
17 | ## How to Run
18 |
19 | To run the model interactively, run `solara run app.py` in this directory. e.g.
20 |
21 | ```bash
22 | solara run app.py
23 | ```
24 |
25 | Then open your browser to [http://127.0.0.1:8765/](http://127.0.0.1:8765/) and press the play button `▶`.
26 |
--------------------------------------------------------------------------------
/gis/population/app.py:
--------------------------------------------------------------------------------
1 | import mesa_geo as mg
2 | import solara
3 | from mesa.visualization import SolaraViz
4 | from mesa_geo.visualization import make_geospace_component
5 | from population.model import Population
6 | from population.space import UgandaCell
7 | from shapely.geometry import Point, Polygon
8 |
9 |
10 | def make_plot_num_agents(model):
11 | return solara.Markdown(f"**Number of Agents: {len(model.space.agents)}**")
12 |
13 |
14 | def agent_portrayal(agent):
15 | if isinstance(agent, mg.GeoAgent):
16 | if isinstance(agent.geometry, Point):
17 | return {
18 | "stroke": False,
19 | "color": "Green",
20 | "radius": 2,
21 | "fillOpacity": 0.3,
22 | }
23 | elif isinstance(agent.geometry, Polygon):
24 | return {
25 | "fillColor": "Blue",
26 | "fillOpacity": 1.0,
27 | }
28 | elif isinstance(agent, UgandaCell):
29 | return (agent.population, agent.population, agent.population, 1)
30 |
31 |
32 | model = Population()
33 | page = SolaraViz(
34 | model,
35 | [
36 | make_geospace_component(agent_portrayal),
37 | make_plot_num_agents,
38 | ],
39 | name="Population Model",
40 | )
41 |
42 | page # noqa
43 |
--------------------------------------------------------------------------------
/examples/shape_example/Readme.md:
--------------------------------------------------------------------------------
1 | # Shape Model -- Basic Grid with two agents
2 |
3 | ## Summary
4 |
5 | A very basic example model to showcase the visualization on web browser.
6 |
7 | A simple grid is displayed on browser with two agents. The example does not
8 | have any agent motion involved. This example does not have any movement of
9 | agents so as to keep it to the simplest of level possible.
10 |
11 | This model showcases following features:
12 |
13 | * A rectangular grid
14 | * Text Overlay on the agent's shape on CanvasGrid
15 | * ArrowHead shaped agent for displaying heading of the agent on CanvasGrid
16 |
17 | ## Installation
18 |
19 | To install the dependencies use pip and the requirements.txt in this directory.
20 | e.g.
21 |
22 | ```
23 | $ pip install -r requirements.txt
24 | ```
25 |
26 | ## How to Run
27 |
28 | To run the model interactively, run ``mesa runserver`` in this directory. e.g.
29 |
30 | ```
31 | $ mesa runserver
32 | ```
33 |
34 | Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and
35 | press Reset, then Run.
36 |
37 | ## Files
38 |
39 | * ``shape_model/model.py``: Defines the basic shape model and agents.
40 | * ``shape_model/server.py``: Sets up the interactive visualization server.
41 | * ``run.py``: Launches a model visualization server.
42 |
--------------------------------------------------------------------------------
/examples/shape_example/shape_example/model.py:
--------------------------------------------------------------------------------
1 | import mesa
2 | from mesa.discrete_space import OrthogonalMooreGrid
3 |
4 |
5 | class Walker(mesa.Agent):
6 | def __init__(self, model, heading=(1, 0)):
7 | super().__init__(model)
8 | self.heading = heading
9 | self.headings = {(1, 0), (0, 1), (-1, 0), (0, -1)}
10 |
11 |
12 | class ShapeExample(mesa.Model):
13 | def __init__(self, num_agents=2, width=20, height=10):
14 | super().__init__()
15 | self.num_agents = num_agents # num of agents
16 | self.headings = ((1, 0), (0, 1), (-1, 0), (0, -1)) # tuples are fast
17 | self.grid = OrthogonalMooreGrid((width, height), torus=True, random=self.random)
18 |
19 | self.make_walker_agents()
20 | self.running = True
21 |
22 | def make_walker_agents(self):
23 | for _ in range(self.num_agents):
24 | x = self.random.randrange(self.grid.dimensions[0])
25 | y = self.random.randrange(self.grid.dimensions[1])
26 | cell = self.grid[(x, y)]
27 | heading = self.random.choice(self.headings)
28 | # heading = (1, 0)
29 | if cell.is_empty:
30 | a = Walker(self, heading)
31 | a.cell = cell
32 |
33 | def step(self):
34 | self.agents.shuffle_do("step")
35 |
--------------------------------------------------------------------------------
/examples/shape_example/shape_example/server.py:
--------------------------------------------------------------------------------
1 | import mesa
2 |
3 | from .model import ShapeExample, Walker
4 |
5 |
6 | def agent_draw(agent):
7 | portrayal = None
8 | if agent is None:
9 | # Actually this if part is unnecessary, but still keeping it for
10 | # aesthetics
11 | pass
12 | elif isinstance(agent, Walker):
13 | print(f"Uid: {agent.unique_id}, Heading: {agent.heading}")
14 | portrayal = {
15 | "Shape": "arrowHead",
16 | "Filled": "true",
17 | "Layer": 2,
18 | "Color": ["#00FF00", "#99FF99"],
19 | "stroke_color": "#666666",
20 | "heading_x": agent.heading[0],
21 | "heading_y": agent.heading[1],
22 | "text": agent.unique_id,
23 | "text_color": "white",
24 | "scale": 0.8,
25 | }
26 | return portrayal
27 |
28 |
29 | width = 15
30 | height = 10
31 | num_agents = 2
32 | pixel_ratio = 50
33 | grid = mesa.visualization.CanvasGrid(
34 | agent_draw, width, height, width * pixel_ratio, height * pixel_ratio
35 | )
36 | server = mesa.visualization.ModularServer(
37 | ShapeExample,
38 | [grid],
39 | "Shape Model Example",
40 | {"N": num_agents, "width": width, "height": height},
41 | )
42 | server.max_steps = 0
43 | server.port = 8521
44 |
--------------------------------------------------------------------------------
/examples/caching_and_replay/run.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | import mesa
4 | from cacheablemodel import CacheableSchelling
5 | from server import canvas_element, get_happy_agents, happy_chart, model_params
6 |
7 | # As 'replay' is a simulation model parameter in this example, we need to make it available as such
8 | model_params["replay"] = mesa.visualization.Checkbox("Replay cached run?", False)
9 | model_params["cache_file_path"] = "./my_cache_file_path.cache"
10 |
11 |
12 | def get_cache_file_status(_):
13 | """Display an informational text about caching and the status of the cache file (existing versus not existing)"""
14 | cache_file = Path(model_params["cache_file_path"])
15 | return (
16 | f"Only activate the 'Replay cached run?' switch when a cache file already exists, otherwise it will fail. "
17 | f"Cache file existing: '{cache_file.exists()}'."
18 | )
19 |
20 |
21 | server = mesa.visualization.ModularServer(
22 | model_cls=CacheableSchelling, # Note that Schelling was replaced by CacheableSchelling here
23 | visualization_elements=[
24 | get_cache_file_status,
25 | canvas_element,
26 | get_happy_agents,
27 | happy_chart,
28 | ],
29 | name="Schelling Segregation Model",
30 | model_params=model_params,
31 | )
32 |
33 | server.launch()
34 |
--------------------------------------------------------------------------------
/gis/agents_and_networks/src/agent/building.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import uuid
4 | from random import randrange
5 |
6 | import mesa
7 | import mesa_geo as mg
8 | import pyproj
9 | from shapely.geometry import Polygon
10 |
11 |
12 | class Building(mg.GeoAgent):
13 | unique_id: int # an ID that represents the building
14 | model: mesa.Model
15 | geometry: Polygon
16 | crs: pyproj.CRS
17 | centroid: mesa.space.FloatCoordinate
18 | name: str
19 | function: float # 1.0 for work, 2.0 for home, 0.0 for neither
20 | entrance_pos: mesa.space.FloatCoordinate # nearest vertex on road
21 |
22 | def __init__(self, model, geometry, crs) -> None:
23 | super().__init__(model=model, geometry=geometry, crs=crs)
24 | self.entrance = None
25 | self.name = str(uuid.uuid4())
26 | self.function = randrange(3)
27 |
28 | def __repr__(self) -> str:
29 | return (
30 | f"{self.__class__.__name__}(unique_id={self.unique_id}, name={self.name}, "
31 | f"function={self.function}, centroid={self.centroid})"
32 | )
33 |
34 | def __eq__(self, other):
35 | if isinstance(other, Building):
36 | return self.unique_id == other.unique_id
37 | return False
38 |
39 | def __hash__(self) -> int:
40 | return hash(self.unique_id)
41 |
--------------------------------------------------------------------------------
/examples/el_farol/README.md:
--------------------------------------------------------------------------------
1 | # El Farol
2 |
3 | This folder contains an implementation of El Farol restaurant model. Agents (restaurant customers) decide whether to go to the restaurant or not based on their memory and reward from previous trials. Implications from the model have been used to explain how individual decision-making affects overall performance and fluctuation.
4 |
5 | The implementation is based on Fogel 1999 (in particular the calculation of the prediction), which is a refinement over Arthur 1994.
6 |
7 | ## How to Run
8 |
9 | Launch the model: You can run the model and perform analysis in el_farol.ipynb.
10 | You can test the model itself by running `pytest tests.py`.
11 |
12 | ## Files
13 | * [el_farol.ipynb](el_farol.ipynb): Run the model and visualization in a Jupyter notebook
14 | * [el_farol/model.py](el_farol/model.py): Core model file.
15 | * [el_farol/agents.py](el_farol/agents.py): The agent class.
16 | * [tests.py](tests.py): Tests to ensure the model is consistent with Arthur 1994, Fogel 1996.
17 |
18 | ## Further Reading
19 |
20 | 1. W. Brian Arthur Inductive Reasoning and Bounded Rationality (1994) https://www.jstor.org/stable/2117868
21 | 1. D.B. Fogel, K. Chellapilla, P.J. Angeline Inductive reasoning and bounded rationality reconsidered (1999)
22 | 1. NetLogo implementation of the El Farol bar problem https://ccl.northwestern.edu/netlogo/models/ElFarol
23 |
--------------------------------------------------------------------------------
/gis/geo_sir/app.py:
--------------------------------------------------------------------------------
1 | from geo_sir.agents import PersonAgent
2 | from geo_sir.model import GeoSir
3 | from mesa.visualization import Slider, SolaraViz, make_plot_component
4 | from mesa_geo.visualization import make_geospace_component
5 |
6 | model_params = {
7 | "pop_size": Slider("Population size", 30, 10, 100, 10),
8 | "init_infected": Slider("Fraction initial infection", 0.2, 0.00, 1.0, 0.05),
9 | "exposure_distance": Slider("Exposure distance", 500, 100, 1000, 100),
10 | }
11 |
12 |
13 | def infected_draw(agent):
14 | """Portrayal Method for canvas"""
15 | portrayal = {}
16 | if isinstance(agent, PersonAgent):
17 | portrayal["radius"] = "2"
18 | if agent.atype in ["hotspot", "infected"]:
19 | portrayal["color"] = "Red"
20 | elif agent.atype in ["safe", "susceptible"]:
21 | portrayal["color"] = "Green"
22 | elif agent.atype in ["recovered"]:
23 | portrayal["color"] = "Blue"
24 | elif agent.atype in ["dead"]:
25 | portrayal["color"] = "Black"
26 | return portrayal
27 |
28 |
29 | model = GeoSir()
30 | page = SolaraViz(
31 | model,
32 | [
33 | make_geospace_component(infected_draw, zoom=12),
34 | make_plot_component(["infected", "susceptible", "recovered", "dead"]),
35 | ],
36 | name="Basic agent-based SIR model",
37 | model_params=model_params,
38 | )
39 |
40 | page # noqa
41 |
--------------------------------------------------------------------------------
/examples/hex_snowflake/hex_snowflake/model.py:
--------------------------------------------------------------------------------
1 | import mesa
2 | from mesa.discrete_space import HexGrid
3 |
4 | from .cell import Cell
5 |
6 |
7 | class HexSnowflake(mesa.Model):
8 | """Represents the hex grid of cells. The grid is represented by a 2-dimensional array
9 | of cells with adjacency rules specific to hexagons.
10 | """
11 |
12 | def __init__(self, width=50, height=50, seed=None):
13 | """Create a new playing area of (width, height) cells."""
14 | super().__init__(seed=seed)
15 | # Use a hexagonal grid, where edges wrap around.
16 | self.grid = HexGrid((width, height), capacity=1, torus=True, random=self.random)
17 |
18 | # Place a dead cell at each location.
19 | for entry in self.grid.all_cells:
20 | Cell(entry, self)
21 |
22 | # activate the center(ish) cell.
23 | centerish_cell = self.grid[(width // 2, height // 2)]
24 | centerish_cell.agents[0].state = 1
25 | for a in centerish_cell.neighborhood.agents:
26 | a.is_considered = True
27 |
28 | self.running = True
29 |
30 | def step(self):
31 | """Perform the model step in two stages:
32 | - First, all cells assume their next state (whether they will be dead or alive)
33 | - Then, all cells change state to their next state
34 | """
35 | self.agents.do("determine_state")
36 | self.agents.do("assume_state")
37 |
--------------------------------------------------------------------------------
/rl/epstein_civil_violence/train_config.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from model import EpsteinCivilViolenceRL
4 | from ray.rllib.algorithms.ppo import PPOConfig
5 | from ray.rllib.policy.policy import PolicySpec
6 |
7 |
8 | # Configuration for the PPO algorithm
9 | # You can change the configuration as per your requirements
10 | def env_creator(_):
11 | return EpsteinCivilViolenceRL(
12 | width=20,
13 | height=20,
14 | citizen_density=0.5,
15 | cop_density=0.1,
16 | citizen_vision=4,
17 | cop_vision=4,
18 | legitimacy=0.82,
19 | max_jail_term=10,
20 | )
21 |
22 |
23 | config = {
24 | "env_name": "WorldcopModel-v0",
25 | "env_creator": env_creator,
26 | "framework": "torch",
27 | "train_batch_size": 800,
28 | "policies": {
29 | "policy_cop": PolicySpec(config=PPOConfig.overrides(framework_str="torch")),
30 | "policy_citizen": PolicySpec(config=PPOConfig.overrides(framework_str="torch")),
31 | },
32 | "policy_mapping_fn": lambda agent_id, *args, **kwargs: "policy_cop"
33 | if agent_id[0:3] == "cop"
34 | else "policy_citizen",
35 | "policies_to_train": ["policy_cop", "policy_citizen"],
36 | "num_gpus": int(os.environ.get("RLLIB_NUM_GPUS", "1")),
37 | "num_learners": 50,
38 | "num_env_runners": 20,
39 | "num_envs_per_env_runner": 1,
40 | "batch_mode": "truncate_episodes",
41 | "rollout_fragment_length": 40,
42 | }
43 |
--------------------------------------------------------------------------------
/gis/agents_and_networks/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # C extensions
6 | *.so
7 |
8 | # Distribution / packaging
9 | .Python
10 | env/
11 | venv/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *.cover
46 |
47 | # Translations
48 | *.mo
49 | *.pot
50 |
51 | # Django stuff:
52 | *.log
53 |
54 | # Sphinx documentation
55 | docs/_build/
56 |
57 | # PyBuilder
58 | target/
59 |
60 | # DotEnv configuration
61 | .env
62 |
63 | # Database
64 | *.db
65 | *.rdb
66 |
67 | # Pycharm
68 | .idea
69 |
70 | # VS Code
71 | .vscode/
72 |
73 | # Spyder
74 | .spyproject/
75 |
76 | # Jupyter NB Checkpoints
77 | .ipynb_checkpoints/
78 |
79 | # exclude data from source control by default
80 | # /data/
81 |
82 | # Mac OS-specific storage files
83 | .DS_Store
84 |
85 | # vim
86 | *.swp
87 | *.swo
88 |
89 | # Mypy cache
90 | .mypy_cache/
91 |
92 | **/*.pkl
93 |
--------------------------------------------------------------------------------
/gis/rainfall/app.py:
--------------------------------------------------------------------------------
1 | from mesa.visualization import Slider, SolaraViz, make_plot_component
2 | from mesa_geo.visualization import make_geospace_component
3 | from rainfall.model import Rainfall
4 | from rainfall.space import LakeCell
5 |
6 | model_params = {
7 | "rain_rate": Slider("rain rate", 500, 0, 500, 5),
8 | "water_height": Slider("water height", 5, 1, 5, 1),
9 | "num_steps": Slider("total number of steps", 20, 1, 100, 1),
10 | "export_data": False,
11 | }
12 |
13 |
14 | def cell_portrayal(cell: LakeCell) -> tuple[float, float, float, float]:
15 | if cell.water_level == 0:
16 | return cell.elevation, cell.elevation, cell.elevation, 1
17 | else:
18 | # return a blue color gradient based on the normalized water level
19 | # from the lowest water level colored as RGBA: (74, 141, 255, 1)
20 | # to the highest water level colored as RGBA: (0, 0, 255, 1)
21 | return (
22 | (1 - cell.water_level_normalized) * 74,
23 | (1 - cell.water_level_normalized) * 141,
24 | 255,
25 | 1,
26 | )
27 |
28 |
29 | model = Rainfall()
30 | page = SolaraViz(
31 | model,
32 | [
33 | make_geospace_component(cell_portrayal, zoom=11),
34 | make_plot_component(
35 | ["Total Amount of Water", "Total Contained", "Total Outflow"]
36 | ),
37 | ],
38 | name="Rainfall Model",
39 | model_params=model_params,
40 | )
41 |
42 | page # noqa
43 |
--------------------------------------------------------------------------------
/examples/charts/Readme.md:
--------------------------------------------------------------------------------
1 | # Mesa Charts Example
2 |
3 | ## Summary
4 |
5 | A modified version of the "bank_reserves" example made to provide examples of mesa's charting tools.
6 |
7 | The chart types included in this example are:
8 | - Line Charts for time-series data of multiple model parameters
9 | - Pie Charts for model parameters
10 | - Bar charts for both model and agent-level parameters
11 |
12 | ## Installation
13 |
14 | To install the dependencies use pip and the requirements.txt in this directory. e.g.
15 |
16 | ```
17 | $ pip install -r requirements.txt
18 | ```
19 |
20 | ## Interactive Model Run
21 |
22 | To run the model interactively, use `mesa runserver` in this directory:
23 |
24 | ```
25 | $ mesa runserver
26 | ```
27 |
28 | Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/), select the model parameters, press Reset, then Start.
29 |
30 | ## Files
31 |
32 | * ``bank_reserves/random_walker.py``: This defines a class that inherits from the Mesa Agent class. The main purpose is to provide a method for agents to move randomly one cell at a time.
33 | * ``bank_reserves/agents.py``: Defines the People and Bank classes.
34 | * ``bank_reserves/model.py``: Defines the Bank Reserves model and the DataCollector functions.
35 | * ``bank_reserves/server.py``: Sets up the interactive visualization server.
36 | * ``run.py``: Launches a model visualization server.
37 |
38 | ## Further Reading
39 |
40 | See the "bank_reserves" model for more information.
41 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # C extensions
6 | *.so
7 |
8 | # Distribution / packaging
9 | .Python
10 | env/
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | *.egg-info/
23 | .installed.cfg
24 | *.egg
25 |
26 | # ignore RL file - users download model on own
27 | rl
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .coverage
43 | .coverage.*
44 | .cache
45 | nosetests.xml
46 | coverage.xml
47 | *,cover
48 |
49 | # Translations
50 | *.mo
51 | *.pot
52 |
53 | # Django stuff:
54 | *.log
55 |
56 | # Sphinx documentation
57 | docs/_build/
58 |
59 | # PyBuilder
60 | target/
61 |
62 | # Jupyter and iPython notebook checkpoints
63 | *.ipynb_checkpoints
64 | *.virtual_documents
65 |
66 | # Spyder app workspace config file
67 | .spyderworkspace
68 |
69 | # PyCharm environment files
70 | .idea/
71 |
72 | # VSCode environment files
73 | .vscode/
74 | *.code-workspace
75 |
76 | # Apple OSX
77 | *.DS_Store
78 |
79 | # mypy
80 | .mypy_cache/
81 | .dmypy.json
82 | dmypy.json
83 |
84 | # Virtual environment
85 | venv/
86 |
87 | examples/caching_and_replay/my_cache_file_path.cache
88 |
--------------------------------------------------------------------------------
/gis/geo_schelling_points/app.py:
--------------------------------------------------------------------------------
1 | import solara
2 | from geo_schelling_points.agents import PersonAgent, RegionAgent
3 | from geo_schelling_points.model import GeoSchellingPoints
4 | from mesa.visualization import Slider, SolaraViz, make_plot_component
5 | from mesa_geo.visualization import make_geospace_component
6 |
7 |
8 | def make_plot_happiness(model):
9 | return solara.Markdown(f"**Happy agents: {model.happy}**")
10 |
11 |
12 | model_params = {
13 | "red_percentage": Slider("% red", 0.5, 0.00, 1.0, 0.05),
14 | "similarity_threshold": Slider("% similar wanted", 0.5, 0.00, 1.0, 0.05),
15 | }
16 |
17 |
18 | def schelling_draw(agent):
19 | portrayal = {}
20 | if isinstance(agent, RegionAgent):
21 | if agent.red_cnt > agent.blue_cnt:
22 | portrayal["color"] = "Red"
23 | elif agent.red_cnt < agent.blue_cnt:
24 | portrayal["color"] = "Blue"
25 | else:
26 | portrayal["color"] = "Grey"
27 | elif isinstance(agent, PersonAgent):
28 | portrayal["radius"] = 1
29 | portrayal["shape"] = "circle"
30 | portrayal["color"] = "Red" if agent.is_red else "Blue"
31 | return portrayal
32 |
33 |
34 | model = GeoSchellingPoints()
35 | page = SolaraViz(
36 | model,
37 | [
38 | make_geospace_component(schelling_draw, zoom=4),
39 | make_plot_component(["happy", "unhappy"]),
40 | make_plot_happiness,
41 | ],
42 | model_params=model_params,
43 | name="GeoSchellingPoints",
44 | )
45 |
46 | page # noqa
47 |
--------------------------------------------------------------------------------
/gis/urban_growth/app.py:
--------------------------------------------------------------------------------
1 | import solara
2 | from mesa.visualization import Slider, SolaraViz, make_plot_component
3 | from mesa_geo.visualization import make_geospace_component
4 | from urban_growth.model import UrbanGrowth
5 | from urban_growth.space import UrbanCell
6 |
7 |
8 | def cell_portrayal(cell: UrbanCell) -> tuple[float, float, float, float]:
9 | if cell.urban:
10 | if cell.old_urbanized:
11 | return 0, 0, 255, 1
12 | else:
13 | return 255, 0, 0, 1
14 | else:
15 | return 0, 0, 0, 0
16 |
17 |
18 | def make_plot_urbanized(model):
19 | return solara.Markdown(f"**Percentage Urbanized: {model.pct_urbanized:.2f}%**")
20 |
21 |
22 | model_params = {
23 | "max_coefficient": 100,
24 | "dispersion_coefficient": Slider("dispersion_coefficient", 20, 0, 100, 1),
25 | "spread_coefficient": Slider("spread_coefficient", 27, 0, 100, 1),
26 | "breed_coefficient": Slider("breed_coefficient", 5, 0, 100, 1),
27 | "rg_coefficient": Slider("rg_coefficient", 10, 0, 100, 1),
28 | "slope_coefficient": Slider("slope_coefficient", 50, 0, 100, 1),
29 | "critical_slope": Slider("critical_slope", 25, 0, 100, 1),
30 | "road_influence": False,
31 | }
32 |
33 | model = UrbanGrowth()
34 | page = SolaraViz(
35 | model,
36 | [
37 | make_geospace_component(cell_portrayal, zoom=12.1),
38 | make_plot_component(["Percentage Urbanized"]),
39 | make_plot_urbanized,
40 | ],
41 | name="Urban Growth Model",
42 | model_params=model_params,
43 | )
44 |
45 | page # noqa
46 |
--------------------------------------------------------------------------------
/gis/geo_schelling_points/geo_schelling_points/space.py:
--------------------------------------------------------------------------------
1 | import random
2 |
3 | import mesa_geo as mg
4 |
5 | from .agents import RegionAgent
6 |
7 |
8 | class Nuts2Eu(mg.GeoSpace):
9 | _id_region_map: dict[str, RegionAgent]
10 | num_people: int
11 |
12 | def __init__(self):
13 | super().__init__(warn_crs_conversion=False)
14 | self._id_region_map = {}
15 | self.num_people = 0
16 |
17 | def add_regions(self, agents):
18 | super().add_agents(agents)
19 | total_area = 0
20 | for agent in agents:
21 | self._id_region_map[agent.unique_id] = agent
22 | total_area += agent.SHAPE_AREA
23 | for _, agent in self._id_region_map.items():
24 | agent.SHAPE_AREA = agent.SHAPE_AREA / total_area * 100.0
25 |
26 | def add_person_to_region(self, person, region_id):
27 | person.region_id = region_id
28 | person.geometry = self._id_region_map[region_id].random_point()
29 | self._id_region_map[region_id].add_person(person)
30 | super().add_agents(person)
31 | self.num_people += 1
32 |
33 | def remove_person_from_region(self, person):
34 | self._id_region_map[person.region_id].remove_person(person)
35 | person.region_id = None
36 | super().remove_agent(person)
37 | self.num_people -= 1
38 |
39 | def get_random_region_id(self) -> str:
40 | return random.choice(list(self._id_region_map.keys()))
41 |
42 | def get_region_by_id(self, region_id) -> RegionAgent:
43 | return self._id_region_map.get(region_id)
44 |
--------------------------------------------------------------------------------
/examples/el_farol/el_farol/model.py:
--------------------------------------------------------------------------------
1 | import mesa
2 | import numpy as np
3 |
4 | from .agents import BarCustomer
5 |
6 |
7 | class ElFarolBar(mesa.Model):
8 | def __init__(
9 | self,
10 | crowd_threshold=60,
11 | num_strategies=10,
12 | memory_size=10,
13 | num_agents=100,
14 | ):
15 | super().__init__()
16 | self.running = True
17 | self.num_agents = num_agents
18 |
19 | # Initialize the previous attendance randomly so the agents have a history
20 | # to work with from the start.
21 | # The history is twice the memory, because we need at least a memory
22 | # worth of history for each point in memory to test how well the
23 | # strategies would have worked.
24 | self.history = np.random.randint(0, 100, size=memory_size * 2).tolist()
25 | self.attendance = self.history[-1]
26 | for _ in range(self.num_agents):
27 | BarCustomer(self, memory_size, crowd_threshold, num_strategies)
28 |
29 | self.datacollector = mesa.DataCollector(
30 | model_reporters={"Customers": "attendance"},
31 | agent_reporters={"Utility": "utility", "Attendance": "attend"},
32 | )
33 |
34 | def step(self):
35 | self.datacollector.collect(self)
36 | self.attendance = 0
37 | self.agents.shuffle_do("update_attendance")
38 | # We ensure that the length of history is constant
39 | # after each step.
40 | self.history.pop(0)
41 | self.history.append(self.attendance)
42 | self.agents.shuffle_do("update_strategies")
43 |
--------------------------------------------------------------------------------
/gis/population/population/space.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import gzip
4 |
5 | import geopandas as gpd
6 | import mesa
7 | from mesa_geo.geoagent import GeoAgent
8 | from mesa_geo.geospace import GeoSpace
9 | from mesa_geo.raster_layers import Cell, RasterLayer
10 |
11 |
12 | class UgandaCell(Cell):
13 | population: float | None
14 |
15 | def __init__(
16 | self,
17 | model,
18 | pos: mesa.space.Coordinate | None = None,
19 | indices: mesa.space.Coordinate | None = None,
20 | ):
21 | super().__init__(model, pos, indices)
22 | self.population = None
23 |
24 | def step(self):
25 | pass
26 |
27 |
28 | class Lake(GeoAgent):
29 | pass
30 |
31 |
32 | class UgandaArea(GeoSpace):
33 | def __init__(self, crs):
34 | super().__init__(crs=crs)
35 |
36 | def load_data(self, population_gzip_file, lake_zip_file, world_zip_file, model):
37 | world_size = gpd.GeoDataFrame.from_file(world_zip_file)
38 | raster_layer = RasterLayer.from_file(
39 | population_gzip_file,
40 | model=model,
41 | cell_cls=UgandaCell,
42 | attr_name="population",
43 | rio_opener=gzip.open,
44 | )
45 | raster_layer.crs = world_size.crs
46 | raster_layer.total_bounds = world_size.total_bounds
47 | self.add_layer(raster_layer)
48 | self.lake = gpd.GeoDataFrame.from_file(lake_zip_file).geometry[0]
49 | self.add_agents(GeoAgent(model, self.lake, self.crs))
50 |
51 | @property
52 | def population_layer(self):
53 | return self.layers[0]
54 |
--------------------------------------------------------------------------------
/examples/aco_tsp/run_tsp.py:
--------------------------------------------------------------------------------
1 | from collections import defaultdict
2 |
3 | import matplotlib.pyplot as plt
4 | from aco_tsp.model import AcoTspModel, TSPGraph
5 |
6 |
7 | def main():
8 | # tsp_graph = TSPGraph.from_random(num_cities=20, seed=1)
9 | tsp_graph = TSPGraph.from_tsp_file("aco_tsp/data/kroA100.tsp")
10 | model_params = {
11 | "num_agents": tsp_graph.num_cities,
12 | "tsp_graph": tsp_graph,
13 | }
14 | number_of_episodes = 50
15 |
16 | results = defaultdict(list)
17 |
18 | best_path = None
19 | best_distance = float("inf")
20 |
21 | model = AcoTspModel(**model_params)
22 |
23 | for e in range(number_of_episodes):
24 | # model = AcoTspModel(**model_params)
25 | model.step()
26 | results["best_distance"].append(model.best_distance)
27 | results["best_path"].append(model.best_path)
28 | print(
29 | f"Episode={e + 1}; Min. distance={model.best_distance:.2f}; pheromone_1_8={model.grid.G[17][15]['pheromone']:.4f}"
30 | )
31 | if model.best_distance < best_distance:
32 | best_distance = model.best_distance
33 | best_path = model.best_path
34 | print(f"New best distance: distance={best_distance:.2f}")
35 |
36 | print(f"Best distance: {best_distance:.2f}")
37 | print(f"Best path: {best_path}")
38 | # print(model.datacollector.get_model_vars_dataframe())
39 |
40 | _, ax = plt.subplots()
41 | ax.plot(results["best_distance"])
42 | ax.set(xlabel="Episode", ylabel="Best distance", title="Best distance per episode")
43 | plt.show()
44 |
45 |
46 | if __name__ == "__main__":
47 | main()
48 |
--------------------------------------------------------------------------------
/examples/caching_and_replay/cacheablemodel.py:
--------------------------------------------------------------------------------
1 | from mesa_replay import CacheableModel, CacheState
2 | from model import Schelling
3 |
4 |
5 | class CacheableSchelling(CacheableModel):
6 | """A wrapper around the original Schelling model to make the simulation cacheable
7 | and replay-able. Uses CacheableModel from the Mesa-Replay library,
8 | which is a wrapper that can be put around any regular mesa model to make it
9 | "cacheable".
10 | From outside, a CacheableSchelling instance can be treated like any
11 | regular Mesa model.
12 | The only difference is that the model will write the state of every simulation step
13 | to a cache file or when in replay mode use a given cache file to replay that cached
14 | simulation run.
15 | """
16 |
17 | def __init__(
18 | self,
19 | width=20,
20 | height=20,
21 | density=0.8,
22 | minority_pc=0.2,
23 | homophily=3,
24 | radius=1,
25 | cache_file_path="./my_cache_file_path.cache",
26 | # Note that this is an additional parameter we add to our model,
27 | # which decides whether to simulate or replay
28 | replay=False,
29 | ):
30 | actual_model = Schelling(
31 | width=width,
32 | height=height,
33 | density=density,
34 | minority_pc=minority_pc,
35 | homophily=homophily,
36 | radius=radius,
37 | )
38 | cache_state = CacheState.REPLAY if replay else CacheState.RECORD
39 | super().__init__(
40 | model=actual_model,
41 | cache_file_path=cache_file_path,
42 | cache_state=cache_state,
43 | )
44 |
--------------------------------------------------------------------------------
/gis/population/README.md:
--------------------------------------------------------------------------------
1 | # Population Model
2 |
3 | [](https://www.youtube.com/watch?v=0k8tsYPVwQs)
4 |
5 | ## Summary
6 |
7 | This is an implementation of the [Uganda Example](https://github.com/abmgis/abmgis/tree/master/Chapter05-GIS/Models/UgandaExample) in Python, using [Mesa](https://github.com/mesa/mesa) and [Mesa-Geo](https://github.com/mesa/mesa-geo).
8 |
9 | ### GeoSpace
10 |
11 | The GeoSpace consists of both a raster and a vector layer. The raster layer contains population data for each cell, and it is this data that is used for model initialisation, in the sense creating the agents. The vector layer shown in blue color represents a lake in Uganda. It overlays with the raster layer to mask out the cells that agents cannot move into.
12 |
13 | ### GeoAgent
14 |
15 | The GeoAgents are people, created based on the population data. As this is a simple example model, the agents only move randomly to neighboring cells at each time step. To make the simulation more realistic and visually appealing, the agents in the same cell have a randomized position within the cell, so that they don’t stand on top of each other at exactly the same coordinate.
16 |
17 | ## How to Run
18 |
19 | To run the model interactively, run `solara run app.py` in this directory. e.g.
20 |
21 | ```bash
22 | solara run app.py
23 | ```
24 |
25 | Then open your browser to [http://127.0.0.1:8765/](http://127.0.0.1:8765/) and press the play button `▶`.
26 |
27 | ## License
28 |
29 | The data is from the [Uganda Example](https://github.com/abmgis/abmgis/tree/master/Chapter05-GIS/Models/UgandaExample) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/).
30 |
--------------------------------------------------------------------------------
/examples/forest_fire/app.py:
--------------------------------------------------------------------------------
1 | from forest_fire.model import ForestFire
2 | from mesa.visualization import (
3 | SolaraViz,
4 | make_plot_component,
5 | make_space_component,
6 | )
7 | from mesa.visualization.user_param import (
8 | Slider,
9 | )
10 |
11 | COLORS = {"Fine": "#00AA00", "On Fire": "#880000", "Burned Out": "#000000"}
12 |
13 |
14 | def forest_fire_portrayal(tree):
15 | if tree is None:
16 | return
17 | portrayal = {"Shape": "rect", "w": 1, "h": 1, "Filled": "true", "Layer": 0}
18 | (x, y) = (tree.cell.coordinate[i] for i in (0, 1))
19 | portrayal["x"] = x
20 | portrayal["y"] = y
21 | portrayal["color"] = COLORS[tree.condition]
22 | return portrayal
23 |
24 |
25 | def post_process_space(ax):
26 | ax.set_aspect("equal")
27 | ax.set_xticks([])
28 | ax.set_yticks([])
29 |
30 |
31 | def post_process_lines(ax):
32 | ax.legend(loc="center left", bbox_to_anchor=(1, 0.9))
33 |
34 |
35 | space_component = make_space_component(
36 | forest_fire_portrayal,
37 | draw_grid=False,
38 | post_process=post_process_space,
39 | )
40 | lineplot_component = make_plot_component(
41 | COLORS,
42 | post_process=post_process_lines,
43 | )
44 | # TODO: add back in pie chart component
45 | # # no current pie chart equivalent in mesa>=3.0
46 | # pie_chart = mesa.visualization.PieChartModule(
47 | # [{"Label": label, "Color": color} for (label, color) in COLORS.items()]
48 | # )
49 | model = ForestFire()
50 | model_params = {
51 | "height": 100,
52 | "width": 100,
53 | "density": Slider("Tree density", 0.65, 0.01, 1.0, 0.01),
54 | }
55 | page = SolaraViz(
56 | model,
57 | components=[space_component, lineplot_component],
58 | model_params=model_params,
59 | name="Forest Fire",
60 | )
61 |
--------------------------------------------------------------------------------
/examples/color_patches/app.py:
--------------------------------------------------------------------------------
1 | """handles the definition of the canvas parameters and
2 | the drawing of the model representation on the canvas
3 | """
4 |
5 | # import webbrowser
6 | from color_patches.model import ColorPatches
7 | from mesa.visualization import (
8 | SolaraViz,
9 | make_space_component,
10 | )
11 |
12 | _COLORS = [
13 | "Aqua",
14 | "Blue",
15 | "Fuchsia",
16 | "Gray",
17 | "Green",
18 | "Lime",
19 | "Maroon",
20 | "Navy",
21 | "Olive",
22 | "Orange",
23 | "Purple",
24 | "Red",
25 | "Silver",
26 | "Teal",
27 | "White",
28 | "Yellow",
29 | ]
30 |
31 |
32 | grid_rows = 50
33 | grid_cols = 25
34 | cell_size = 10
35 | canvas_width = grid_rows * cell_size
36 | canvas_height = grid_cols * cell_size
37 |
38 |
39 | def color_patch_draw(cell):
40 | """This function is registered with the visualization server to be called
41 | each tick to indicate how to draw the cell in its current state.
42 |
43 | :param cell: the cell in the simulation
44 |
45 | :return: the portrayal dictionary.
46 | """
47 | if cell is None:
48 | raise AssertionError
49 | portrayal = {"Shape": "rect", "w": 1, "h": 1, "Filled": "true", "Layer": 0}
50 | portrayal["x"] = cell.get_row()
51 | portrayal["y"] = cell.get_col()
52 | portrayal["color"] = _COLORS[cell.state]
53 | return portrayal
54 |
55 |
56 | space_component = make_space_component(
57 | color_patch_draw,
58 | draw_grid=False,
59 | )
60 | model = ColorPatches()
61 | page = SolaraViz(
62 | model,
63 | components=[space_component],
64 | model_params={"width": grid_rows, "height": grid_cols},
65 | name="Color Patches",
66 | )
67 | # webbrowser.open('http://127.0.0.1:8521') # TODO: make this configurable
68 |
--------------------------------------------------------------------------------
/rl/train.py:
--------------------------------------------------------------------------------
1 | from ray import tune
2 | from ray.rllib.algorithms.ppo import PPOConfig
3 | from ray.tune.logger import pretty_print
4 |
5 |
6 | # Custom function to get the configuration
7 | def get_config(custom_config):
8 | config = (
9 | PPOConfig()
10 | .environment(custom_config["env_name"])
11 | .framework(custom_config["framework"])
12 | .training(train_batch_size=custom_config["train_batch_size"])
13 | .multi_agent(
14 | policies=custom_config["policies"],
15 | policy_mapping_fn=custom_config["policy_mapping_fn"],
16 | policies_to_train=custom_config["policies_to_train"],
17 | )
18 | .resources(num_gpus=custom_config["num_gpus"])
19 | .learners(num_learners=custom_config["num_learners"])
20 | .env_runners(
21 | num_env_runners=custom_config["num_env_runners"],
22 | num_envs_per_env_runner=custom_config["num_envs_per_env_runner"],
23 | batch_mode=custom_config["batch_mode"],
24 | rollout_fragment_length=custom_config["rollout_fragment_length"],
25 | )
26 | )
27 | return config
28 |
29 |
30 | # Training the model
31 | def train_model(
32 | config, num_iterations=5, result_path="results.txt", checkpoint_dir="checkpoints"
33 | ):
34 | tune.register_env(config["env_name"], config["env_creator"])
35 |
36 | algo_config = get_config(config)
37 | algo = algo_config.build()
38 |
39 | for _ in range(num_iterations):
40 | result = algo.train()
41 | print(pretty_print(result))
42 |
43 | with open(result_path, "w") as file:
44 | file.write(pretty_print(result))
45 |
46 | checkpoint_dir = algo.save(checkpoint_dir).checkpoint.path
47 | print(f"Checkpoint saved in directory {checkpoint_dir}")
48 |
--------------------------------------------------------------------------------
/rl/wolf_sheep/train_config.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from model import WolfSheepRL
4 | from ray.rllib.algorithms.ppo import PPOConfig
5 | from ray.rllib.policy.policy import PolicySpec
6 |
7 |
8 | # Configuration to train the model
9 | # Feel free to adjust the configuration as necessary
10 | def env_creator(_):
11 | return WolfSheepRL(
12 | width=20,
13 | height=20,
14 | initial_sheep=100,
15 | initial_wolves=25,
16 | sheep_reproduce=0.04,
17 | wolf_reproduce=0.05,
18 | wolf_gain_from_food=20,
19 | grass=True,
20 | grass_regrowth_time=30,
21 | sheep_gain_from_food=4,
22 | )
23 |
24 |
25 | config = {
26 | "env_name": "WorldSheepModel-v0",
27 | "env_creator": env_creator,
28 | "framework": "torch", # Assuming you want to use PyTorch
29 | "train_batch_size": 150, # Assuming a default value, adjust as necessary
30 | "policies": {
31 | "policy_sheep": PolicySpec(config=PPOConfig.overrides(framework_str="torch")),
32 | "policy_wolf": PolicySpec(config=PPOConfig.overrides(framework_str="torch")),
33 | },
34 | "policy_mapping_fn": lambda agent_id, *args, **kwargs: "policy_sheep"
35 | if agent_id[0:5] == "sheep"
36 | else "policy_wolf",
37 | "policies_to_train": ["policy_sheep", "policy_wolf"],
38 | "num_gpus": int(os.environ.get("RLLIB_NUM_GPUS", "1")),
39 | "num_learners": 50, # Assuming a default value, adjust as necessary
40 | "num_env_runners": 20, # Assuming a default value, adjust as necessary
41 | "num_envs_per_env_runner": 1, # Assuming a default value, adjust as necessary
42 | "batch_mode": "truncate_episodes", # Assuming a default value, adjust as necessary
43 | "rollout_fragment_length": "auto", # Assuming a default value, adjust as necessary
44 | }
45 |
--------------------------------------------------------------------------------
/examples/warehouse/warehouse/make_warehouse.py:
--------------------------------------------------------------------------------
1 | import random
2 | import string
3 |
4 | import numpy as np
5 |
6 | # Constants
7 | DEFAULT_ROWS = 22
8 | DEFAULT_COLS = 20
9 | DEFAULT_HEIGHT = 4
10 | LOADING_DOCK_COORDS = [(0, i, 0) for i in range(0, 10, 2)]
11 | CHARGING_STATION_COORDS = [(21, i, 0) for i in range(19, 10, -2)]
12 |
13 |
14 | def generate_item_code() -> str:
15 | """Generate a random item code (1 letter + 2 numbers)."""
16 | letter = random.choice(string.ascii_uppercase)
17 | number = random.randint(10, 99)
18 | return f"{letter}{number}"
19 |
20 |
21 | def make_warehouse(
22 | rows: int = DEFAULT_ROWS, cols: int = DEFAULT_COLS, height: int = DEFAULT_HEIGHT
23 | ) -> np.ndarray:
24 | """Generate a warehouse layout with designated LD, CS, and storage rows as a NumPy array.
25 |
26 | Args:
27 | rows (int): Number of rows in the warehouse.
28 | cols (int): Number of columns in the warehouse.
29 | height (int): Number of levels in the warehouse.
30 |
31 | Returns:
32 | np.ndarray: A 3D NumPy array representing the warehouse layout.
33 | """
34 | # Initialize empty warehouse layout
35 | warehouse = np.full((rows, cols, height), " ", dtype=object)
36 |
37 | # Place Loading Docks (LD)
38 | for r, c, h in LOADING_DOCK_COORDS:
39 | warehouse[r, c, h] = "LD"
40 |
41 | # Place Charging Stations (CS)
42 | for r, c, h in CHARGING_STATION_COORDS:
43 | warehouse[r, c, h] = "CS"
44 |
45 | # Fill storage rows with item codes
46 | for r in range(3, rows - 2, 3): # Skip row 0,1,2 (LD) and row 17,18,19 (CS)
47 | for c in range(2, cols, 3): # Leave 2 spaces between each item row
48 | for h in range(height):
49 | warehouse[r, c, h] = generate_item_code()
50 |
51 | return warehouse
52 |
--------------------------------------------------------------------------------
/examples/color_patches/Readme.md:
--------------------------------------------------------------------------------
1 | # Color Patches
2 |
3 |
4 | This is a cellular automaton model where each agent lives in a cell on a 2D grid, and never moves.
5 |
6 | An agent's state represents its "opinion" and is shown by the color of the cell the agent lives in. Each color represents an opinion - there are 16 of them. At each time step, an agent's opinion is influenced by that of its neighbors, and changes to the most common one found; ties are randomly arbitrated. As an agent adapts its thinking to that of its neighbors, the cell color changes.
7 |
8 | ### Parameters you can play with:
9 | (you must change the code to alter the parameters at this stage)
10 | * Vary the number of opinions.
11 | * Vary the size of the grid
12 | * Change the grid from fixed borders to a torus continuum
13 |
14 | ### Observe
15 | * how groups of like minded agents form and evolve
16 | * how sometimes a single opinion prevails
17 | * how some minority or fragmented opinions rapidly disappear
18 |
19 | ## How to Run
20 |
21 | To run the model interactively, run ``mesa runserver` in this directory. e.g.
22 |
23 | ```
24 | $ mesa runserver
25 | ```
26 |
27 | Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run.
28 |
29 | ## Files
30 |
31 | * ``color_patches/model.py``: Defines the cell and model classes. The cell class governs each cell's behavior. The model class itself controls the lattice on which the cells live and interact.
32 | * ``color_patches/server.py``: Defines an interactive visualization.
33 | * ``run.py``: Launches an interactive visualization
34 |
35 | ## Further Reading
36 |
37 | Inspired from [this model](http://www.cs.sjsu.edu/~pearce/modules/lectures/abs/as/ca.htm) from San Jose University
38 | Other similar models: [Schelling Segregation Model](https://github.com/mesa/mesa/tree/main/examples/schelling)
39 |
--------------------------------------------------------------------------------
/rl/epstein_civil_violence/README.md:
--------------------------------------------------------------------------------
1 | # Modelling Violence: Epstein Civil Violence Model
2 |
3 | This project demonstrates the use of the RLlib library to implement Multi-Agent Reinforcement Learning (MARL) in the classic Epstein-Civil Violence problem. The environment details can be found on the Mesa project's GitHub repository [here](https://github.com/mesa/mesa-examples/tree/main/examples/epstein_civil_violence).
4 |
5 | ## Key Features
6 |
7 | **RLlib and Multi-Agent Learning**:
8 | - **Library Utilized**: The project leverages the RLlib library to concurrently train two independent PPO (Proximal Policy Optimization) agents.
9 | - **Agents**:
10 | - **Police**: Aims to control violence (Reduce active agent)
11 | - **Citizen**: Aims to show resistance (be active) without getting arrested
12 |
13 | **Input and Observation Space**:
14 | - **Observation Grid**: Each agent's policy receives a 4 radius grid centered on itself as input.
15 |
16 | **Action Space**:
17 | - **Action Space**: For citizen the action space is the ID of the neighboring tile to which the agent wants to move along with choice to be active. For cop the action space is ID of neighbourng tile it wants to move along with ID of active citizen in it's neigbhood that it wants to arrest.
18 | **Behavior and Training Outcomes**:
19 |
20 | **Optimal Behavior**:
21 | - **Cops**: Learns to move towards active agents and arrest them.
22 | - **Citizens**: Learns to run away from cops and be active only if a cop isn't around.
23 | - **Density Variations**: You can vary the densities of sheep and wolves to observe different results.
24 |
25 | By leveraging RLlib and Multi-Agent Learning, this project provides insights into the dynamics of violence in a society and various strategies in a simulated environment.
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/examples/hotelling_law/tests.py:
--------------------------------------------------------------------------------
1 | from scipy.stats import linregress
2 |
3 | from .hotelling_law.model import HotellingModel
4 |
5 |
6 | def check_slope(data, increasing=True):
7 | """Checks the slope of a dataset to determine
8 | if it's increasing or decreasing.
9 | """
10 | slope = get_slope(data)
11 | return (slope > 0) if increasing else (slope < 0)
12 |
13 |
14 | def get_slope(data):
15 | slope, _, _, _, _ = linregress(range(len(data)), data)
16 | print(slope)
17 | return slope
18 |
19 |
20 | def test_decreasing_price_variance():
21 | """Test to ensure the price variance decreases over time,
22 | in line with Hotelling's law.
23 | """
24 | model = HotellingModel(
25 | N_stores=5,
26 | width=20,
27 | height=20,
28 | mode="default",
29 | consumer_preferences="default",
30 | environment_type="grid",
31 | mobility_rate=80,
32 | )
33 | model.run_model(step_count=50)
34 |
35 | df_model = model.datacollector.get_model_vars_dataframe()
36 |
37 | assert check_slope(df_model["Price Variance"], increasing=False), (
38 | "The price variance should decrease over time."
39 | )
40 |
41 |
42 | def test_constant_price_variance():
43 | """Test to ensure the price variance constant over time,
44 | with Rules location_only without changing price
45 | """
46 | model = HotellingModel(
47 | N_stores=5,
48 | width=20,
49 | height=20,
50 | mode="location_only",
51 | consumer_preferences="default",
52 | environment_type="grid",
53 | mobility_rate=80,
54 | )
55 | model.run_model(step_count=50)
56 |
57 | df_model = model.datacollector.get_model_vars_dataframe()
58 |
59 | assert get_slope(df_model["Price Variance"]) == 0, (
60 | "The price variance constant over time."
61 | )
62 |
--------------------------------------------------------------------------------
/examples/bank_reserves/batch_run.py:
--------------------------------------------------------------------------------
1 | """The following code was adapted from the Bank Reserves model included in Netlogo
2 | Model information can be found at:
3 | http://ccl.northwestern.edu/netlogo/models/BankReserves
4 | Accessed on: November 2, 2017
5 | Author of NetLogo code:
6 | Wilensky, U. (1998). NetLogo Bank Reserves model.
7 | http://ccl.northwestern.edu/netlogo/models/BankReserves.
8 | Center for Connected Learning and Computer-Based Modeling,
9 | Northwestern University, Evanston, IL.
10 |
11 | This version of the model has a BatchRunner at the bottom. This
12 | is for collecting data on parameter sweeps. It is not meant to
13 | be run with run.py, since run.py starts up a server for visualization, which
14 | isn't necessary for the BatchRunner. To run a parameter sweep, call
15 | batch_run.py in the command line.
16 |
17 | The BatchRunner is set up to collect step by step data of the model. It does
18 | this by collecting the DataCollector object in a model_reporter (i.e. the
19 | DataCollector is collecting itself every step).
20 |
21 | The end result of the batch run will be a CSV file created in the same
22 | directory from which Python was run. The CSV file will contain the data from
23 | every step of every run.
24 | """
25 |
26 | import mesa
27 | import pandas as pd
28 | from bank_reserves.model import BankReservesModel
29 |
30 |
31 | def main():
32 | # parameter lists for each parameter to be tested in batch run
33 | br_params = {
34 | "init_people": [25, 100],
35 | "rich_threshold": [5, 10],
36 | "reserve_percent": 5,
37 | }
38 |
39 | # The existing batch run logic here
40 | data = mesa.batch_run(
41 | BankReservesModel,
42 | br_params,
43 | )
44 | br_df = pd.DataFrame(data)
45 | br_df.to_csv("BankReservesModel_Data.csv")
46 |
47 |
48 | if __name__ == "__main__":
49 | main()
50 |
--------------------------------------------------------------------------------
/examples/termites/termites/model.py:
--------------------------------------------------------------------------------
1 | from mesa import Model
2 | from mesa.discrete_space import OrthogonalMooreGrid, PropertyLayer
3 |
4 | from .agents import Termite
5 |
6 |
7 | class TermiteModel(Model):
8 | """A simulation that shows behavior of termite agents gathering wood chips into piles."""
9 |
10 | def __init__(
11 | self, num_termites=100, width=100, height=100, wood_chip_density=0.1, seed=42
12 | ):
13 | """Initialize the model.
14 |
15 | Args:
16 | num_termites: Number of Termite Agents,
17 | width: Grid width.
18 | height: Grid heights.
19 | wood_chip_density: Density of wood chips in the grid.
20 | seed : Random seed for reproducibility.
21 | """
22 | super().__init__(seed=seed)
23 | self.num_termites = num_termites
24 | self.wood_chip_density = wood_chip_density
25 |
26 | self.grid = OrthogonalMooreGrid((width, height), torus=True, random=self.random)
27 |
28 | self.wood_chips_layer = PropertyLayer(
29 | "woodcell", (width, height), default_value=False, dtype=bool
30 | )
31 |
32 | # Randomly distribute wood chips, by directly modifying the layer's underlying ndarray
33 | self.wood_chips_layer.data = self.rng.choice(
34 | [True, False],
35 | size=(width, height),
36 | p=[self.wood_chip_density, 1 - self.wood_chip_density],
37 | )
38 |
39 | self.grid.add_property_layer(self.wood_chips_layer)
40 |
41 | # Create agents and randomly distribute them over the grid
42 | Termite.create_agents(
43 | model=self,
44 | n=self.num_termites,
45 | cell=self.random.sample(self.grid.all_cells.cells, k=self.num_termites),
46 | )
47 |
48 | def step(self):
49 | self.agents.shuffle_do("step")
50 |
--------------------------------------------------------------------------------
/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/model.py:
--------------------------------------------------------------------------------
1 | import networkx as nx
2 | from mesa import Model
3 | from mesa.datacollection import DataCollector
4 | from mesa.discrete_space import Network
5 |
6 | from .agents import MoneyAgent
7 |
8 |
9 | class BoltzmannWealthModelNetwork(Model):
10 | """A model with some number of agents."""
11 |
12 | def __init__(self, n=7, num_nodes=10, seed=None):
13 | super().__init__(seed=seed)
14 |
15 | self.num_agents = n
16 | self.num_nodes = num_nodes if num_nodes >= self.num_agents else self.num_agents
17 | self.G = nx.erdos_renyi_graph(n=self.num_nodes, p=0.5)
18 | self.grid = Network(self.G, capacity=1, random=self.random)
19 |
20 | # Set up data collection
21 | self.datacollector = DataCollector(
22 | model_reporters={"Gini": self.compute_gini},
23 | agent_reporters={"Wealth": "wealth"},
24 | )
25 |
26 | # Create agents; add the agent to a random node
27 | # TODO: change to MoneyAgent.create_agents(...)
28 | list_of_random_nodes = self.random.sample(list(self.G), self.num_agents)
29 | for position in list_of_random_nodes:
30 | agent = MoneyAgent(self)
31 | agent.move_to(self.grid[position])
32 |
33 | self.running = True
34 | self.datacollector.collect(self)
35 |
36 | def step(self):
37 | self.agents.shuffle_do("step") # Activate all agents in random order
38 | self.datacollector.collect(self) # collect data
39 |
40 | def compute_gini(self):
41 | agent_wealths = [agent.wealth for agent in self.agents]
42 | x = sorted(agent_wealths)
43 | num_agents = self.num_agents
44 | B = sum(xi * (num_agents - i) for i, xi in enumerate(x)) / (num_agents * sum(x)) # noqa: N806
45 | return 1 + (1 / num_agents) - 2 * B
46 |
--------------------------------------------------------------------------------
/rl/wolf_sheep/README.md:
--------------------------------------------------------------------------------
1 | # Collaborative Survival: Wolf-Sheep Predation Model
2 |
3 | This project demonstrates the use of the RLlib library to implement Multi-Agent Reinforcement Learning (MARL) in the classic Wolf-Sheep predation problem. The environment details can be found on the Mesa project's GitHub repository [here](https://github.com/mesa/mesa-examples/tree/main/examples/wolf_sheep).
4 |
5 | ## Key Features
6 |
7 | **RLlib and Multi-Agent Learning**:
8 | - **Library Utilized**: The project leverages the RLlib library to concurrently train two independent PPO (Proximal Policy Optimization) agents.
9 | - **Agents**:
10 | - **Wolf**: Predatory agent survives by eating sheep
11 | - **Sheep**: Prey agent survives by eating grass
12 | - **Grass**: Grass is eaten by sheep and regrows with time
13 |
14 | **Input and Observation Space**:
15 | - **Observation Grid**: Each agent's policy receives a 10x10 grid centered on itself as input.
16 | - **Grid Details**: The grid incorporates information about the presence of other agents (wolves, sheep, and grass) within the grid.
17 | - **Agent's Energy Level**: The agent's current energy level is also included in the observations.
18 |
19 | **Action Space**:
20 | - **Action Space**: The action space is the ID of the neighboring tile to which the agent wants to move.
21 |
22 | **Behavior and Training Outcomes**:
23 | - **Optimal Behavior**:
24 | - **Wolf**: Learns to move towards the nearest sheep.
25 | - **Sheep**: Learns to run away from wolves and is attracted to grass.
26 | - **Density Variations**: You can vary the densities of sheep and wolves to observe different results.
27 |
28 | By leveraging RLlib and Multi-Agent Learning, this project provides insights into the dynamics of predator-prey relationships and optimal behavior strategies in a simulated environment.
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/.github/workflows/test_examples.yml:
--------------------------------------------------------------------------------
1 | name: Test example models
2 |
3 | on:
4 | push:
5 | paths:
6 | - 'examples/**/*.py' # If an example model is modified
7 | - 'test_examples.py' # If the test script is modified
8 | - '.github/workflows/test_examples.yml' # If this workflow is modified
9 | pull_request:
10 | paths:
11 | - 'examples/**/*.py'
12 | - 'test_examples.py'
13 | - '.github/workflows/test_examples.yml'
14 | workflow_dispatch:
15 | schedule:
16 | - cron: '0 6 * * 1' # Monday at 6:00 UTC
17 |
18 | jobs:
19 | # build-stable:
20 | # runs-on: ubuntu-latest
21 | # steps:
22 | # - uses: actions/checkout@v4
23 | # - name: Set up Python
24 | # uses: actions/setup-python@v5
25 | # with:
26 | # python-version: "3.12"
27 | # - name: Install dependencies
28 | # run: pip install mesa[network] pytest
29 | # - name: Test with pytest
30 | # run: pytest -rA -Werror test_examples.py
31 |
32 | build-pre:
33 | runs-on: ubuntu-latest
34 | steps:
35 | - uses: actions/checkout@v4
36 | - name: Set up Python
37 | uses: actions/setup-python@v5
38 | with:
39 | python-version: "3.12"
40 | - name: Install dependencies
41 | run: |
42 | pip install mesa[network] --pre
43 | pip install .[test]
44 | - name: Test with pytest
45 | run: pytest -rA -Werror -Wdefault::FutureWarning test_examples.py
46 |
47 | build-main:
48 | runs-on: ubuntu-latest
49 | steps:
50 | - uses: actions/checkout@v4
51 | - name: Set up Python
52 | uses: actions/setup-python@v5
53 | with:
54 | python-version: "3.12"
55 | - name: Install dependencies
56 | run: |
57 | pip install .[test]
58 | pip install -U git+https://github.com/mesa/mesa@main#egg=mesa[network]
59 | - name: Test with pytest
60 | run: pytest -rA -Werror -Wdefault::FutureWarning test_examples.py
61 |
--------------------------------------------------------------------------------
/examples/aco_tsp/aco_tsp/data/kroA100.tsp:
--------------------------------------------------------------------------------
1 | NAME: kroA100
2 | TYPE: TSP
3 | COMMENT: 100-city problem A (Krolak/Felts/Nelson)
4 | DIMENSION: 100
5 | EDGE_WEIGHT_TYPE : EUC_2D
6 | NODE_COORD_SECTION
7 | 1 1380 939
8 | 2 2848 96
9 | 3 3510 1671
10 | 4 457 334
11 | 5 3888 666
12 | 6 984 965
13 | 7 2721 1482
14 | 8 1286 525
15 | 9 2716 1432
16 | 10 738 1325
17 | 11 1251 1832
18 | 12 2728 1698
19 | 13 3815 169
20 | 14 3683 1533
21 | 15 1247 1945
22 | 16 123 862
23 | 17 1234 1946
24 | 18 252 1240
25 | 19 611 673
26 | 20 2576 1676
27 | 21 928 1700
28 | 22 53 857
29 | 23 1807 1711
30 | 24 274 1420
31 | 25 2574 946
32 | 26 178 24
33 | 27 2678 1825
34 | 28 1795 962
35 | 29 3384 1498
36 | 30 3520 1079
37 | 31 1256 61
38 | 32 1424 1728
39 | 33 3913 192
40 | 34 3085 1528
41 | 35 2573 1969
42 | 36 463 1670
43 | 37 3875 598
44 | 38 298 1513
45 | 39 3479 821
46 | 40 2542 236
47 | 41 3955 1743
48 | 42 1323 280
49 | 43 3447 1830
50 | 44 2936 337
51 | 45 1621 1830
52 | 46 3373 1646
53 | 47 1393 1368
54 | 48 3874 1318
55 | 49 938 955
56 | 50 3022 474
57 | 51 2482 1183
58 | 52 3854 923
59 | 53 376 825
60 | 54 2519 135
61 | 55 2945 1622
62 | 56 953 268
63 | 57 2628 1479
64 | 58 2097 981
65 | 59 890 1846
66 | 60 2139 1806
67 | 61 2421 1007
68 | 62 2290 1810
69 | 63 1115 1052
70 | 64 2588 302
71 | 65 327 265
72 | 66 241 341
73 | 67 1917 687
74 | 68 2991 792
75 | 69 2573 599
76 | 70 19 674
77 | 71 3911 1673
78 | 72 872 1559
79 | 73 2863 558
80 | 74 929 1766
81 | 75 839 620
82 | 76 3893 102
83 | 77 2178 1619
84 | 78 3822 899
85 | 79 378 1048
86 | 80 1178 100
87 | 81 2599 901
88 | 82 3416 143
89 | 83 2961 1605
90 | 84 611 1384
91 | 85 3113 885
92 | 86 2597 1830
93 | 87 2586 1286
94 | 88 161 906
95 | 89 1429 134
96 | 90 742 1025
97 | 91 1625 1651
98 | 92 1187 706
99 | 93 1787 1009
100 | 94 22 987
101 | 95 3640 43
102 | 96 3756 882
103 | 97 776 392
104 | 98 1724 1642
105 | 99 198 1810
106 | 100 3950 1558
107 | EOF
--------------------------------------------------------------------------------
/examples/caching_and_replay/server.py:
--------------------------------------------------------------------------------
1 | """This file was copied over from the original Schelling mesa example."""
2 |
3 | import mesa
4 | from model import Schelling
5 |
6 |
7 | def get_happy_agents(model):
8 | """Display a text count of how many happy agents there are."""
9 | return f"Happy agents: {model.happy}"
10 |
11 |
12 | def schelling_draw(agent):
13 | """Portrayal Method for canvas"""
14 | if agent is None:
15 | return
16 | portrayal = {"Shape": "circle", "r": 0.5, "Filled": "true", "Layer": 0}
17 |
18 | if agent.type == 0:
19 | portrayal["Color"] = ["#FF0000", "#FF9999"]
20 | portrayal["stroke_color"] = "#00FF00"
21 | else:
22 | portrayal["Color"] = ["#0000FF", "#9999FF"]
23 | portrayal["stroke_color"] = "#000000"
24 | return portrayal
25 |
26 |
27 | canvas_element = mesa.visualization.CanvasGrid(
28 | portrayal_method=schelling_draw,
29 | grid_width=20,
30 | grid_height=20,
31 | canvas_width=500,
32 | canvas_height=500,
33 | )
34 | happy_chart = mesa.visualization.ChartModule([{"Label": "happy", "Color": "Black"}])
35 |
36 | model_params = {
37 | "height": 20,
38 | "width": 20,
39 | "density": mesa.visualization.Slider(
40 | name="Agent density", value=0.8, min_value=0.1, max_value=1.0, step=0.1
41 | ),
42 | "minority_pc": mesa.visualization.Slider(
43 | name="Fraction minority", value=0.2, min_value=0.00, max_value=1.0, step=0.05
44 | ),
45 | "homophily": mesa.visualization.Slider(
46 | name="Homophily", value=3, min_value=0, max_value=8, step=1
47 | ),
48 | "radius": mesa.visualization.Slider(
49 | name="Search Radius", value=1, min_value=1, max_value=5, step=1
50 | ),
51 | }
52 |
53 | server = mesa.visualization.ModularServer(
54 | model_cls=Schelling,
55 | visualization_elements=[canvas_element, get_happy_agents, happy_chart],
56 | name="Schelling Segregation Model",
57 | model_params=model_params,
58 | )
59 |
--------------------------------------------------------------------------------
/gis/rainfall/README.md:
--------------------------------------------------------------------------------
1 | # Rainfall Model
2 |
3 | [](https://www.youtube.com/watch?v=T2FQwFnPDR8)
4 |
5 | ## Summary
6 |
7 | This is an implementation of the [Rainfall Model](https://github.com/abmgis/abmgis/tree/master/Chapter06-IntegratingABMandGIS/Models/Rainfall) in Python, using [Mesa](https://github.com/mesa/mesa) and [Mesa-Geo](https://github.com/mesa/mesa-geo). Inspired by the NetLogo [Grand Canyon model](http://ccl.northwestern.edu/netlogo/models/GrandCanyon), this is an example of how a digital elevation model (DEM) can be used to create an artificial world.
8 |
9 | ### GeoSpace
10 |
11 | The GeoSpace contains a raster layer representing elevations. It is this elevation value that impacts how the raindrops move over the terrain. Apart from `elevation`, each cell of the raster layer also has a `water_level` attribute that is used to track the amount of water it contains.
12 |
13 | ### GeoAgent
14 |
15 | In this example, the raindrops are the GeoAgents. At each time step, raindrops are randomly created across the landscape to simulate rainfall. The raindrops flow from cells of higher elevation to lower elevation based on their eight surrounding cells (i.e., Moore neighbourhood). The raindrop also has its own height, which allows them to accumulate, gain height and flow if they are trapped at places such as potholes, pools, or depressions. When they reach the boundary of the GeoSpace, they are removed from the model as outflow.
16 |
17 | ## How to Run
18 |
19 | To run the model interactively, run `solara run app.py` in this directory. e.g.
20 |
21 | ```bash
22 | solara run app.py
23 | ```
24 |
25 | Then open your browser to [http://127.0.0.1:8765/](http://127.0.0.1:8765/) and press the play button `▶`.
26 |
27 | ## License
28 |
29 | The data is from the [Rainfall Model](https://github.com/abmgis/abmgis/tree/master/Chapter06-IntegratingABMandGIS/Models/Rainfall) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/).
30 |
--------------------------------------------------------------------------------
/examples/forest_fire/forest_fire/model.py:
--------------------------------------------------------------------------------
1 | import mesa
2 | from mesa.discrete_space import OrthogonalMooreGrid
3 |
4 | from .agent import TreeCell
5 |
6 |
7 | class ForestFire(mesa.Model):
8 | """Simple Forest Fire model."""
9 |
10 | def __init__(self, width=100, height=100, density=0.65, seed=None):
11 | """Create a new forest fire model.
12 |
13 | Args:
14 | width, height: The size of the grid to model
15 | density: What fraction of grid cells have a tree in them.
16 | """
17 | super().__init__(seed=seed)
18 |
19 | # Set up model objects
20 |
21 | self.grid = OrthogonalMooreGrid((width, height), capacity=1, random=self.random)
22 | self.datacollector = mesa.DataCollector(
23 | {
24 | "Fine": lambda m: self.count_type(m, "Fine"),
25 | "On Fire": lambda m: self.count_type(m, "On Fire"),
26 | "Burned Out": lambda m: self.count_type(m, "Burned Out"),
27 | }
28 | )
29 |
30 | # Place a tree in each cell with Prob = density
31 | for cell in self.grid.all_cells:
32 | if self.random.random() < density:
33 | # Create a tree
34 | new_tree = TreeCell(self, cell)
35 | # Set all trees in the first column on fire.
36 | if cell.coordinate[0] == 0:
37 | new_tree.condition = "On Fire"
38 |
39 | self.running = True
40 | self.datacollector.collect(self)
41 |
42 | def step(self):
43 | """Advance the model by one step."""
44 | self.agents.shuffle_do("step")
45 | # collect data
46 | self.datacollector.collect(self)
47 |
48 | # Halt if no more fire
49 | if self.count_type(self, "On Fire") == 0:
50 | self.running = False
51 |
52 | @staticmethod
53 | def count_type(model, tree_condition):
54 | """Helper method to count trees in a given condition in a given model."""
55 | return len(model.agents.select(lambda x: x.condition == tree_condition))
56 |
--------------------------------------------------------------------------------
/examples/termites/app.py:
--------------------------------------------------------------------------------
1 | from mesa.visualization import SolaraViz
2 | from mesa.visualization.components.matplotlib_components import make_mpl_space_component
3 | from termites.model import TermiteModel
4 |
5 | wood_chip_portrayal = {
6 | "woodcell": {
7 | "color": "blue",
8 | "alpha": 0.6,
9 | "colorbar": False,
10 | "vmin": 0,
11 | "vmax": 2,
12 | },
13 | }
14 |
15 |
16 | def agent_portrayal(agent):
17 | return {
18 | "marker": ">",
19 | "color": "red" if agent.has_woodchip else "black",
20 | "size": 10,
21 | }
22 |
23 |
24 | model_params = {
25 | "seed": {
26 | "type": "InputText",
27 | "value": 42,
28 | "label": "Seed",
29 | },
30 | "num_termites": {
31 | "type": "SliderInt",
32 | "value": 100,
33 | "label": "No. of Termites",
34 | "min": 10,
35 | "max": 1000,
36 | "step": 1,
37 | },
38 | "wood_chip_density": {
39 | "type": "SliderFloat",
40 | "value": 0.1,
41 | "label": "Wood Chip Density",
42 | "min": 0.01,
43 | "max": 1,
44 | "step": 0.1,
45 | },
46 | "width": {
47 | "type": "SliderInt",
48 | "value": 100,
49 | "label": "Width",
50 | "min": 10,
51 | "max": 500,
52 | "step": 1,
53 | },
54 | "height": {
55 | "type": "SliderInt",
56 | "value": 100,
57 | "label": "Height",
58 | "min": 10,
59 | "max": 500,
60 | "step": 1,
61 | },
62 | }
63 |
64 | model = TermiteModel()
65 |
66 |
67 | def post_process(ax):
68 | ax.set_aspect("equal")
69 | ax.set_xticks([])
70 | ax.set_yticks([])
71 |
72 |
73 | woodchips_space = make_mpl_space_component(
74 | agent_portrayal=agent_portrayal,
75 | propertylayer_portrayal=wood_chip_portrayal,
76 | post_process=post_process,
77 | draw_grid=False,
78 | )
79 |
80 | page = SolaraViz(
81 | model,
82 | components=[woodchips_space],
83 | model_params=model_params,
84 | name="Termites Model",
85 | )
86 |
--------------------------------------------------------------------------------
/.github/workflows/test_gis_examples.yml:
--------------------------------------------------------------------------------
1 | name: Test GIS models
2 |
3 | on:
4 | push:
5 | paths:
6 | - 'gis/**/*.py' # If a gis model is modified
7 | - 'test_gis_examples.py' # If the gis test script is modified
8 | - '.github/workflows/test_gis_examples.yml' # If this workflow is modified
9 | pull_request:
10 | paths:
11 | - 'gis/**/*.py'
12 | - 'test_gis_examples.py'
13 | - '.github/workflows/test_gis_examples.yml'
14 | workflow_dispatch:
15 | schedule:
16 | - cron: '0 6 * * 1' # Monday at 6:00 UTC
17 |
18 | jobs:
19 | # build-stable:
20 | # runs-on: ubuntu-latest
21 | # steps:
22 | # - uses: actions/checkout@v4
23 | # - name: Set up Python
24 | # uses: actions/setup-python@v5
25 | # with:
26 | # python-version: "3.12"
27 | # - name: Install dependencies
28 | # run: pip install mesa pytest
29 | # - name: Test with pytest
30 | # run: pytest -rA -Werror test_examples.py
31 |
32 | build-pre:
33 | runs-on: ubuntu-latest
34 | steps:
35 | - uses: actions/checkout@v4
36 | - name: Set up Python
37 | uses: actions/setup-python@v5
38 | with:
39 | python-version: "3.12"
40 | - name: Install dependencies
41 | run: |
42 | pip install mesa-geo --pre
43 | pip install .[test_gis]
44 | - name: Test with pytest
45 | run: pytest -rA -Werror test_gis_examples.py
46 |
47 | build-main:
48 | runs-on: ubuntu-latest
49 | steps:
50 | - uses: actions/checkout@v4
51 | - name: Set up Python
52 | uses: actions/setup-python@v5
53 | with:
54 | python-version: "3.12"
55 | - name: Install dependencies
56 | run: |
57 | pip install -U git+https://github.com/mesa/mesa-geo@main#egg=mesa-geo
58 | pip install .[test_gis]
59 | - name: Test with pytest
60 | run: pytest -rA -Werror test_gis_examples.py --cov-report=xml
61 | - name: Codecov
62 | uses: codecov/codecov-action@v5
63 | with:
64 | fail_ci_if_error: true
65 | token: ${{ secrets.CODECOV_TOKEN }}
66 |
--------------------------------------------------------------------------------
/examples/hex_snowflake/hex_snowflake/cell.py:
--------------------------------------------------------------------------------
1 | from mesa.discrete_space import FixedAgent
2 |
3 |
4 | class Cell(FixedAgent):
5 | """Represents a single ALIVE or DEAD cell in the simulation."""
6 |
7 | DEAD = 0
8 | ALIVE = 1
9 |
10 | def __init__(self, cell, model, init_state=DEAD):
11 | """Create a cell, in the given state, at the given x, y position."""
12 | super().__init__(model)
13 | self.cell = cell
14 | self.state = init_state
15 | self._next_state = None
16 | self.is_considered = False
17 |
18 | @property
19 | def is_alive(self):
20 | return self.state == self.ALIVE
21 |
22 | @property
23 | def considered(self):
24 | return self.is_considered is True
25 |
26 | def determine_state(self):
27 | """Compute if the cell will be dead or alive at the next tick. A dead
28 | cell will become alive if it has only one neighbor. The state is not
29 | changed here, but is just computed and stored in self._next_state,
30 | because our current state may still be necessary for our neighbors
31 | to calculate their next state.
32 | When a cell is made alive, its neighbors are able to be considered
33 | in the next step. Only cells that are considered check their neighbors
34 | for performance reasons.
35 | """
36 | # assume no state change
37 | self._next_state = self.state
38 |
39 | if not self.is_alive and self.is_considered:
40 | # Get the neighbors and apply the rules on whether to be alive or dead
41 | # at the next tick.
42 | live_neighbors = sum(
43 | neighbor.is_alive for neighbor in self.cell.neighborhood.agents
44 | )
45 |
46 | if live_neighbors == 1:
47 | self._next_state = self.ALIVE
48 | for a in self.cell.neighborhood.agents:
49 | a.is_considered = True
50 |
51 | def assume_state(self):
52 | """Set the state to the new computed state"""
53 | self.state = self._next_state
54 |
--------------------------------------------------------------------------------
/examples/aco_tsp/app.py:
--------------------------------------------------------------------------------
1 | """Configure visualization elements and instantiate a server"""
2 |
3 | import networkx as nx
4 | import solara
5 | from aco_tsp.model import AcoTspModel, TSPGraph
6 | from matplotlib.figure import Figure
7 | from mesa.visualization import SolaraViz, make_plot_component
8 |
9 |
10 | def circle_portrayal_example(agent):
11 | return {"node_size": 20, "width": 0.1}
12 |
13 |
14 | tsp_graph = TSPGraph.from_tsp_file("aco_tsp/data/kroA100.tsp")
15 | model_params = {
16 | "num_agents": tsp_graph.num_cities,
17 | "tsp_graph": tsp_graph,
18 | "ant_alpha": {
19 | "type": "SliderFloat",
20 | "value": 1.0,
21 | "label": "Alpha: pheromone exponent",
22 | "min": 0.0,
23 | "max": 10.0,
24 | "step": 0.1,
25 | },
26 | "ant_beta": {
27 | "type": "SliderFloat",
28 | "value": 5.0,
29 | "label": "Beta: heuristic exponent",
30 | "min": 0.0,
31 | "max": 10.0,
32 | "step": 0.1,
33 | },
34 | }
35 |
36 | model = AcoTspModel()
37 |
38 |
39 | def make_graph(model):
40 | fig = Figure()
41 | ax = fig.subplots()
42 | ax.set_title("Cities and pheromone trails")
43 | graph = model.grid.G
44 | pos = model.tsp_graph.pos
45 | weights = [graph[u][v]["pheromone"] for u, v in graph.edges()]
46 | # normalize the weights
47 | weights = [w / max(weights) for w in weights]
48 |
49 | nx.draw(
50 | graph,
51 | ax=ax,
52 | pos=pos,
53 | node_size=10,
54 | width=weights,
55 | edge_color="gray",
56 | )
57 |
58 | return solara.FigureMatplotlib(fig)
59 |
60 |
61 | def ant_level_distances(model):
62 | # ant_distances = model.datacollector.get_agent_vars_dataframe()
63 | # Plot so that the step index is the x-axis, there's a line for each agent,
64 | # and the y-axis is the distance traveled
65 | # ant_distances['tsp_distance'].unstack(level=1).plot(ax=ax)
66 | pass
67 |
68 |
69 | page = SolaraViz(
70 | model,
71 | components=[
72 | make_plot_component(["best_distance_iter", "best_distance"]),
73 | make_graph,
74 | ],
75 | model_params=model_params,
76 | play_interval=1,
77 | )
78 |
--------------------------------------------------------------------------------
/rl/boltzmann_money/server.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import mesa
4 | from mesa.visualization.ModularVisualization import ModularServer
5 | from mesa.visualization.modules import ChartModule
6 | from model import BoltzmannWealthModelRL
7 | from stable_baselines3 import PPO
8 |
9 |
10 | # Modify the MoneyModel class to take actions from the RL model
11 | class MoneyModelRL(BoltzmannWealthModelRL):
12 | def __init__(self, n, width, height):
13 | super().__init__(n, width, height)
14 | model_path = os.path.join(
15 | os.path.dirname(__file__), "..", "model", "boltzmann_money.zip"
16 | )
17 | self.rl_model = PPO.load(model_path)
18 | self.reset()
19 |
20 | def step(self):
21 | # Collect data
22 | self.datacollector.collect(self)
23 |
24 | # Get observations which is the wealth of each agent and their position
25 | obs = self._get_obs()
26 |
27 | action, _states = self.rl_model.predict(obs)
28 | self.action_dict = action
29 | self.schedule.step()
30 |
31 |
32 | # Define the agent portrayal with different colors for different wealth levels
33 | def agent_portrayal(agent):
34 | if agent.wealth > 10:
35 | color = "purple"
36 | elif agent.wealth > 7:
37 | color = "red"
38 | elif agent.wealth > 5:
39 | color = "orange"
40 | elif agent.wealth > 3:
41 | color = "yellow"
42 | else:
43 | color = "blue"
44 |
45 | portrayal = {
46 | "Shape": "circle",
47 | "Filled": "true",
48 | "Layer": 0,
49 | "Color": color,
50 | "r": 0.5,
51 | }
52 | return portrayal
53 |
54 |
55 | if __name__ == "__main__":
56 | # Define a grid visualization
57 | grid = mesa.visualization.CanvasGrid(agent_portrayal, 10, 10, 500, 500)
58 |
59 | # Define a chart visualization
60 | chart = ChartModule(
61 | [{"Label": "Gini", "Color": "Black"}], data_collector_name="datacollector"
62 | )
63 |
64 | # Create a modular server
65 | server = ModularServer(
66 | MoneyModelRL, [grid, chart], "Money Model", {"N": 10, "width": 10, "height": 10}
67 | )
68 | server.port = 8521 # The default
69 | server.launch()
70 |
--------------------------------------------------------------------------------
/rl/epstein_civil_violence/agent.py:
--------------------------------------------------------------------------------
1 | from mesa.examples.advanced.epstein_civil_violence.agents import Citizen, Cop
2 |
3 | from .utility import move
4 |
5 |
6 | class CitizenRL(Citizen):
7 | def step(self):
8 | # Get action from action_dict
9 | action_tuple = self.model.action_dict[self.unique_id]
10 | # If in jail decrease sentence, else update condition
11 | if self.jail_sentence > 0:
12 | self.jail_sentence -= 1
13 | else:
14 | # RL Logic
15 | # Update condition and position based on action
16 | self.condition = "Active" if action_tuple[0] == 1 else "Quiescent"
17 | # Update neighbors for updated empty neighbors
18 | self.update_neighbors()
19 | if self.model.movement:
20 | move(
21 | self,
22 | action_tuple[1],
23 | self.empty_neighbors,
24 | )
25 |
26 | # Update the neighbors for observation space
27 | self.update_neighbors()
28 |
29 |
30 | class CopRL(Cop):
31 | def step(self):
32 | # RL Logics
33 | # Arrest if active citizen is indicated in action
34 | action_tuple = self.model.action_dict[self.unique_id]
35 | arrest_pos = self.neighborhood[action_tuple[0]]
36 | for agent in self.model.grid.get_cell_list_contents(self.neighborhood):
37 | if (
38 | isinstance(agent, CitizenRL)
39 | and agent.condition == "Active"
40 | and agent.jail_sentence == 0
41 | and agent.pos == arrest_pos
42 | ):
43 | agent.jail_sentence = self.random.randint(1, self.model.max_jail_term)
44 | agent.condition = "Quiescent"
45 | self.arrest_made = True
46 | break
47 | else:
48 | self.arrest_made = False
49 | # Update neighbors for updated empty neighbors
50 | self.update_neighbors()
51 | # Move based on action
52 | if self.model.movement:
53 | move(self, action_tuple[1], self.empty_neighbors)
54 | # Update the neighbors for observation space
55 | self.update_neighbors()
56 |
--------------------------------------------------------------------------------
/gis/agents_and_networks/README.md:
--------------------------------------------------------------------------------
1 | Agents and Networks Model
2 | =========================
3 |
4 | [](https://www.youtube.com/watch?v=zIRMNPTBESc)
5 |
6 | ## Summary
7 |
8 | This is an implementation of the [GMU-Social Model](https://github.com/abmgis/abmgis/blob/master/Chapter08-Networks/Models/GMU-Social/README.md) in Python, using [Mesa](https://github.com/mesa/mesa) and [Mesa-Geo](https://github.com/mesa/mesa-geo).
9 |
10 | In this model, buildings are randomly assigned to agents as their home and work places, and the buildings' nearest road vertices are used as their entrances. Agents' commute routes can be found as the shortest path between entrances of their home and work places. These commute routes are segmented according to agents' walking speed. In this way, the movements of agents are constrained on the road network.
11 |
12 | ### GeoSpace
13 |
14 | The GeoSpace contains multiple vector layers, including buildings, lakes, and a road network. More specifically, the road network is constructed from the polyline data and implemented by two underlying data structures: a topological network and a k-d tree. First, by treating road vertices as nodes and line segments as links, a topological network is created using the NetworkX and momepy libraries. NetworkX also provides several methods for shortest path computations (e.g., Dijkstra, A-star). Second, a k-d tree is built for all road vertices through the Scikit-learn library for the purpose of nearest vertex searches.
15 |
16 | ### GeoAgent
17 |
18 | The commuters are the GeoAgents.
19 |
20 | ## How to run
21 |
22 | First install the dependencies:
23 |
24 | ```bash
25 | python3 -m pip install -r requirements.txt
26 | ```
27 |
28 | Then run the model:
29 |
30 | ```bash
31 | solara run app.py -- --campus ub
32 | ```
33 |
34 | Change `ub` to `gmu` for a different campus map.
35 |
36 | Then open your browser to [http://127.0.0.1:8765/](http://127.0.0.1:8765/) and press the play button `▶`.
37 |
38 | ## License
39 |
40 | The data is from the [GMU-Social Model](https://github.com/abmgis/abmgis/blob/master/Chapter08-Networks/Models/GMU-Social/README.md) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/).
41 |
--------------------------------------------------------------------------------
/examples/boltzmann_wealth_model_network/README.md:
--------------------------------------------------------------------------------
1 | # Boltzmann Wealth Model with Network
2 |
3 | ## Summary
4 |
5 | This is the same Boltzmann Wealth Model, but with a network grid implementation.
6 |
7 | A simple model of agents exchanging wealth. All agents start with the same amount of money. Every step, each agent with one unit of money or more gives one unit of wealth to another random agent. This is the model described in the [Intro Tutorial](https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html).
8 |
9 | In this network implementation, agents must be located on a node, with a limit of one agent per node. In order to give or receive the unit of money, the agent must be directly connected to the other agent (there must be a direct link between the nodes).
10 |
11 | As the model runs, the distribution of wealth among agents goes from being perfectly uniform (all agents have the same starting wealth), to highly skewed -- a small number have high wealth, more have none at all.
12 |
13 | ## Installation
14 |
15 | To install the dependencies use `pip` to install `mesa[rec]`
16 |
17 | ```bash
18 | $ pip install mesa[rec]
19 | ```
20 |
21 | ## How to Run
22 |
23 | To run the model interactively, run ``solara run`` in this directory. e.g.
24 |
25 | ```bash
26 | $ solara run app.py
27 | ```
28 |
29 | Then open your browser to [http://localhost:8765/](http://localhost:8765/) and press Reset, then Run.
30 |
31 | ## Files
32 |
33 | * ``model.py``: Contains creation of agents, the network, and management of agent execution.
34 | * ``agents.py``: Contains logic for giving money, and moving on the network.
35 | * ``app.py``: Contains the code for the interactive Solara visualization.
36 |
37 | ## Further Reading
38 |
39 | The full tutorial describing how the model is built can be found at:
40 | https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html
41 |
42 | This model is drawn from econophysics and presents a statistical mechanics approach to wealth distribution. Some examples of further reading on the topic can be found at:
43 |
44 | [Milakovic, M. A Statistical Equilibrium Model of Wealth Distribution. February, 2001.](https://editorialexpress.com/cgi-bin/conference/download.cgi?db_name=SCE2001&paper_id=214)
45 |
46 | [Dragulescu, A and Yakovenko, V. Statistical Mechanics of Money, Income, and Wealth: A Short Survey. November, 2002](http://arxiv.org/pdf/cond-mat/0211175v1.pdf)
47 |
--------------------------------------------------------------------------------
/gis/geo_schelling_points/geo_schelling_points/agents.py:
--------------------------------------------------------------------------------
1 | import random
2 |
3 | import mesa_geo as mg
4 | from shapely.geometry import Point
5 |
6 |
7 | class PersonAgent(mg.GeoAgent):
8 | SIMILARITY_THRESHOLD = 0.3
9 |
10 | def __init__(self, model, geometry, crs, is_red, region_id):
11 | super().__init__(model, geometry, crs)
12 | self.is_red = is_red
13 | self.region_id = region_id
14 |
15 | @property
16 | def is_unhappy(self):
17 | if self.is_red:
18 | return (
19 | self.model.space.get_region_by_id(self.region_id).red_pct
20 | < self.SIMILARITY_THRESHOLD
21 | )
22 | else:
23 | return (
24 | 1 - self.model.space.get_region_by_id(self.region_id).red_pct
25 | ) < self.SIMILARITY_THRESHOLD
26 |
27 | def step(self):
28 | if self.is_unhappy:
29 | random_region_id = self.model.space.get_random_region_id()
30 | self.model.space.remove_person_from_region(self)
31 | self.model.space.add_person_to_region(self, region_id=random_region_id)
32 |
33 |
34 | class RegionAgent(mg.GeoAgent):
35 | init_num_people: int
36 | red_cnt: int
37 | blue_cnt: int
38 |
39 | def __init__(self, model, geometry, crs, init_num_people=5):
40 | super().__init__(model, geometry, crs)
41 | self.init_num_people = init_num_people
42 | self.red_cnt = 0
43 | self.blue_cnt = 0
44 |
45 | @property
46 | def red_pct(self):
47 | if self.red_cnt == 0:
48 | return 0
49 | elif self.blue_cnt == 0:
50 | return 1
51 | else:
52 | return self.red_cnt / (self.red_cnt + self.blue_cnt)
53 |
54 | def random_point(self):
55 | min_x, min_y, max_x, max_y = self.geometry.bounds
56 | while not self.geometry.contains(
57 | random_point := Point(
58 | random.uniform(min_x, max_x), random.uniform(min_y, max_y)
59 | )
60 | ):
61 | continue
62 | return random_point
63 |
64 | def add_person(self, person):
65 | if person.is_red:
66 | self.red_cnt += 1
67 | else:
68 | self.blue_cnt += 1
69 |
70 | def remove_person(self, person):
71 | if person.is_red:
72 | self.red_cnt -= 1
73 | else:
74 | self.blue_cnt -= 1
75 |
--------------------------------------------------------------------------------
/gis/agents_and_networks/app.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | from mesa.visualization import Slider, SolaraViz, make_plot_component
4 | from mesa_geo.visualization import make_geospace_component
5 | from src.model.model import AgentsAndNetworks
6 | from src.visualization.utils import agent_draw, make_plot_clock
7 |
8 |
9 | def parse_args():
10 | campus = "ub"
11 | if "--campus" in sys.argv:
12 | campus = sys.argv[sys.argv.index("--campus") + 1]
13 | return campus
14 |
15 |
16 | if __name__ == "__main__":
17 | campus = parse_args()
18 |
19 | if campus == "ub":
20 | data_file_prefix = "UB"
21 | elif campus == "gmu":
22 | data_file_prefix = "Mason"
23 | else:
24 | raise ValueError("Invalid campus name. Choose from ub or gmu.")
25 |
26 | campus_params = {
27 | "ub": {"data_crs": "epsg:4326", "commuter_speed": 0.5, "zoom": 14},
28 | "gmu": {"data_crs": "epsg:2283", "commuter_speed": 0.4, "zoom": 16},
29 | }
30 | model_params = {
31 | "campus": campus,
32 | "data_crs": campus_params[campus]["data_crs"],
33 | "buildings_file": f"data/{campus}/{data_file_prefix}_bld.zip",
34 | "walkway_file": f"data/{campus}/{data_file_prefix}_walkway_line.zip",
35 | "lakes_file": f"data/{campus}/hydrop.zip",
36 | "rivers_file": f"data/{campus}/hydrol.zip",
37 | "driveway_file": f"data/{campus}/{data_file_prefix}_Rds.zip",
38 | "output_dir": "outputs",
39 | "show_walkway": True,
40 | "show_lakes_and_rivers": True,
41 | "show_driveway": True,
42 | "num_commuters": Slider(
43 | "Number of Commuters", value=50, min=10, max=150, step=10
44 | ),
45 | "commuter_speed": Slider(
46 | "Commuter Walking Speed (m/s)",
47 | value=campus_params[campus]["commuter_speed"],
48 | min=0.1,
49 | max=1.5,
50 | step=0.1,
51 | ),
52 | }
53 | model = AgentsAndNetworks()
54 | page = SolaraViz(
55 | model,
56 | [
57 | make_geospace_component(agent_draw, zoom=campus_params[campus]["zoom"]),
58 | make_plot_clock,
59 | make_plot_component(["status_home", "status_work", "status_traveling"]),
60 | make_plot_component(["friendship_home", "friendship_work"]),
61 | ],
62 | name="Agents and Networks",
63 | model_params=model_params,
64 | )
65 |
66 | page # noqa
67 |
--------------------------------------------------------------------------------
/examples/warehouse/Readme.md:
--------------------------------------------------------------------------------
1 | # Pseudo-Warehouse Model (Meta-Agent Example)
2 |
3 | ## Summary
4 |
5 | The purpose of this model is to demonstrate Mesa's meta-agent capability and some of its implementation approaches, not to be an accurate warehouse simulation.
6 |
7 | **Overview of meta agent:** Complex systems often have multiple levels of components. A city is not a single entity, but it is made of districts,neighborhoods, buildings, and people. A forest comprises an ecosystem of trees, plants, animals, and microorganisms. An organization is not one entity, but is made of departments, sub-departments, and people. A person is not a single entity, but it is made of micro biomes, organs and cells.
8 |
9 | This reality is the motivation for meta-agents. It allows users to represent these multiple levels, where each level can have agents with sub-agents.
10 |
11 | In this simulation, robots are given tasks to take retrieve inventory items and then take those items to the loading docks.
12 |
13 | Each `RobotAgent` is made up of sub-components that are treated as separate agents. For this simulation, each robot as a `SensorAgent`, `RouterAgent`, and `WorkerAgent`.
14 |
15 | This model demonstrates deliberate meta-agent creation. It shows the basics of meta-agent creation and different ways to use and reference sub-agent and meta-agent functions and attributes. (The alliance formation demonstrates emergent meta-agent creation.)
16 |
17 | In its current configuration, agents being part of multiple meta-agents is not supported
18 |
19 | An additional item of note is that to reference the RobotAgent created in model you will see `type(self.RobotAgent)` or `type(model.RobotAgent)` in various places. If you have any ideas for how to make this more user friendly please let us know or do a pull request.
20 |
21 | ## Installation
22 |
23 | This model requires Mesa's recommended install
24 |
25 | ```
26 | $ pip install 'mesa[rec]>=3'
27 | ```
28 |
29 | ## How to Run
30 |
31 | To run the model interactively, in this directory, run the following command
32 |
33 | ```
34 | $ solara run app.py
35 | ```
36 |
37 | ## Files
38 |
39 | - `model.py`: Contains creation of agents, the network and management of agent execution.
40 | - `agents.py`: Contains logic for forming alliances and creation of new agents
41 | - `app.py`: Contains the code for the interactive Solara visualization.
42 | - `make_warehouse`: Generates a warehouse numpy array with loading docks, inventory, and charging stations.
--------------------------------------------------------------------------------
/rl/README.md:
--------------------------------------------------------------------------------
1 | # Reinforcement Learning Implementations with Mesa
2 |
3 | This repository demonstrates various applications of reinforcement learning (RL) using the Mesa agent-based modeling framework.
4 |
5 |
6 |
7 |
8 |
9 | ## Getting Started
10 |
11 | ### Installation
12 |
13 | *Given the number of dependencies required, we recommend starting by creating a Conda environment or a Python virtual environment.*
14 | 1. **Install Mesa Models**
15 | Begin by installing the Mesa models:
16 |
17 | #TODO: Update this -- do release?
18 |
19 | ```bash
20 | pip install -U -e git+https://github.com/mesa/mesa-examples@mesa-2.x#egg=mesa-models
21 | ```
22 |
23 | 3. **Install RLlib for Multi-Agent Training**
24 | Next, install RLlib along with TensorFlow and PyTorch to support multi-agent training algorithms:
25 |
26 | ```bash
27 | pip install "ray[rllib]" tensorflow torch
28 | ```
29 | #TODO Update requirements to mesa[rec] >3.0
30 |
31 | 4. **Install Additional Dependencies**
32 | Finally, install any remaining dependencies:
33 |
34 | ```bash
35 | pip install -r requirements.txt
36 | ```
37 |
38 | 5. **Download Pre-Trained Weights**
39 | Download pre-trained weights from hugging face:
40 |
41 | ```bash
42 | git clone https://huggingface.co/projectmesa/rl_models/
43 | ```
44 |
45 | ### Running the Examples
46 |
47 | To test the code, simply execute `example.py`:
48 |
49 | ```bash
50 | python example.py
51 | ```
52 |
53 | *Note: Pre-trained models might not work in some cases because of difference in versions of libraries used to train and test.*
54 |
55 | To learn about individual implementations, please refer to the README files of specific environments.
56 |
57 |
58 | ## Tutorials
59 |
60 | For detailed tutorials on how to use these implementations and guidance on starting your own projects, please refer to [Tutorials.md](./Tutorials.md).
61 |
62 | Here's a refined version of your contribution guide:
63 |
64 |
65 | ## Contribution Guide
66 |
67 | We welcome contributions to our project! A great way to get started is by implementing the remaining examples listed in the [Mesa-Examples](https://github.com/mesa/mesa-examples) repository with reinforcement learning (RL).
68 |
69 | Additionally, if you have your own Mesa environments that you think would benefit from RL integration, we encourage you to share them with us. Simply start an issue on our GitHub repository with your suggestion, and we can collaborate on bringing it to life!
--------------------------------------------------------------------------------
/gis/rainfall/rainfall/space.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import gzip
4 |
5 | import mesa
6 | import mesa_geo as mg
7 | import numpy as np
8 |
9 |
10 | class LakeCell(mg.Cell):
11 | elevation: int | None
12 | water_level: int | None
13 | water_level_normalized: float | None
14 |
15 | def __init__(
16 | self,
17 | model,
18 | pos: mesa.space.Coordinate | None = None,
19 | indices: mesa.space.Coordinate | None = None,
20 | ):
21 | super().__init__(model, pos, indices)
22 | self.elevation = None
23 | self.water_level = None
24 | self.water_level_normalized = None
25 |
26 | def step(self):
27 | pass
28 |
29 |
30 | class CraterLake(mg.GeoSpace):
31 | def __init__(self, crs, water_height, model):
32 | super().__init__(crs=crs)
33 | self.model = model
34 | self.water_height = water_height
35 | self.outflow = 0
36 |
37 | def set_elevation_layer(self, elevation_gzip_file, crs):
38 | raster_layer = mg.RasterLayer.from_file(
39 | elevation_gzip_file,
40 | model=self.model,
41 | cell_cls=LakeCell,
42 | attr_name="elevation",
43 | rio_opener=gzip.open,
44 | )
45 | raster_layer.crs = crs
46 | raster_layer.apply_raster(
47 | data=np.zeros(shape=(1, raster_layer.height, raster_layer.width)),
48 | attr_name="water_level",
49 | )
50 | super().add_layer(raster_layer)
51 |
52 | @property
53 | def raster_layer(self):
54 | return self.layers[0]
55 |
56 | def is_at_boundary(self, row_idx, col_idx):
57 | return (
58 | row_idx == 0
59 | or row_idx == self.raster_layer.height
60 | or col_idx == 0
61 | or col_idx == self.raster_layer.width
62 | )
63 |
64 | def move_raindrop(self, raindrop, new_pos):
65 | self.remove_raindrop(raindrop)
66 | raindrop.pos = new_pos
67 | self.add_raindrop(raindrop)
68 |
69 | def add_raindrop(self, raindrop):
70 | x, y = raindrop.pos
71 | row_ind, col_ind = raindrop.indices
72 | if self.is_at_boundary(row_ind, col_ind):
73 | raindrop.is_at_boundary = True
74 | self.outflow += 1
75 | else:
76 | self.raster_layer.cells[x][y].water_level += self.water_height
77 |
78 | def remove_raindrop(self, raindrop):
79 | x, y = raindrop.pos
80 | self.raster_layer.cells[x][y].water_level -= self.water_height
81 |
--------------------------------------------------------------------------------
/examples/conways_game_of_life_fast/model.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from mesa import Model
3 | from mesa.datacollection import DataCollector
4 | from mesa.space import PropertyLayer
5 | from scipy.signal import convolve2d
6 |
7 |
8 | # fmt: off
9 | class GameOfLifeModel(Model):
10 | def __init__(self, width=10, height=10, alive_fraction=0.2):
11 | super().__init__()
12 | # Initialize the property layer for cell states
13 | self.cell_layer = PropertyLayer("cells", width, height, False, dtype=bool)
14 | # Randomly set cells to alive
15 | self.cell_layer.data = np.random.choice([True, False], size=(width, height), p=[alive_fraction, 1 - alive_fraction])
16 |
17 | # Metrics and datacollector
18 | self.cells = width * height
19 | self.alive_count = 0
20 | self.alive_fraction = 0
21 | self.datacollector = DataCollector(
22 | model_reporters={"Cells alive": "alive_count",
23 | "Fraction alive": "alive_fraction"}
24 | )
25 | self.datacollector.collect(self)
26 |
27 | def step(self):
28 | # Define a kernel for counting neighbors. The kernel has 1s around the center cell (which is 0).
29 | # This setup allows us to count the live neighbors of each cell when we apply convolution.
30 | kernel = np.array([[1, 1, 1],
31 | [1, 0, 1],
32 | [1, 1, 1]])
33 |
34 | # Count neighbors using convolution.
35 | # convolve2d applies the kernel to each cell of the grid, summing up the values of neighbors.
36 | # boundary="wrap" ensures that the grid wraps around, simulating a toroidal surface.
37 | neighbor_count = convolve2d(self.cell_layer.data, kernel, mode="same", boundary="wrap")
38 |
39 | # Apply Game of Life rules:
40 | # 1. A live cell with 2 or 3 live neighbors survives, otherwise it dies.
41 | # 2. A dead cell with exactly 3 live neighbors becomes alive.
42 | # These rules are implemented using logical operations on the grid.
43 | self.cell_layer.data = np.logical_or(
44 | np.logical_and(self.cell_layer.data, np.logical_or(neighbor_count == 2, neighbor_count == 3)),
45 | # Rule for live cells
46 | np.logical_and(~self.cell_layer.data, neighbor_count == 3) # Rule for dead cells
47 | )
48 |
49 | # Metrics
50 | self.alive_count = np.sum(self.cell_layer.data)
51 | self.alive_fraction = self.alive_count / self.cells
52 | self.datacollector.collect(self)
53 |
--------------------------------------------------------------------------------
/examples/termites/README.md:
--------------------------------------------------------------------------------
1 | # Termite WoodChip Behaviour
2 |
3 | This model simulates termites interacting with wood chips, inspired by the [NetLogo Termites model](https://ccl.northwestern.edu/netlogo/models/Termites). It explores emergent behavior in decentralized systems, demonstrating how simple agents (termites) collectively organize wood chips into piles without centralized coordination.
4 |
5 | ## Summary
6 |
7 | In this simulation, multiple termite agents move randomly on a grid containing scattered wood chips. Each termite follows simple rules:
8 |
9 | 1. Search for a wood chip. If found, pick it up and move away.
10 | 2. When carrying a wood chip, search for a pile (another wood chip).
11 | 3. When a pile is found, find a nearby empty space to place the carried chip.
12 | 4. After dropping a chip, move away from the pile.
13 |
14 | Over time, these simple interactions lead to the formation of wood chip piles, illustrating decentralized organization without a central coordinator.
15 |
16 | ## Installation
17 |
18 | Make sure that you have installed the `latest` version of mesa.
19 |
20 | ## Usage
21 |
22 | To run the simulation:
23 | ```bash
24 | solara run app.py
25 | ```
26 |
27 | ## Model Details
28 |
29 | ### Agents
30 |
31 | - **Termite:** An agent that moves within the grid environment, capable of carrying a single wood chip at a time. The termite follows the precise logic of the original NetLogo model, with each behavior (searching, finding piles, dropping chips) continuing until successful.
32 |
33 | ### Environment
34 |
35 | - **Grid:** A two-dimensional toroidal grid where termites interact with the wood chips. The toroidal nature means agents exiting one edge re-enter from the opposite edge.
36 | - **PropertyLayer:** A data structure overlaying the grid, storing the presence of wood chips at each cell.
37 |
38 | ### Agent Behaviors
39 |
40 | - **wiggle():** Simulates random movement by selecting a random neighboring cell.
41 | - **search_for_chip():** Looks for a wood chip. If found, picks it up and moves forward significantly.
42 | - **find_new_pile():** When carrying a chip, searches for a cell that already has a wood chip.
43 | - **put_down_chip():** Attempts to place the carried wood chip in an empty cell near a pile.
44 | - **get_away():** After dropping a chip, moves away from the pile to prevent clustering.
45 |
46 | ## References
47 |
48 | - Wilensky, U. (1997). NetLogo Termites model. Center for Connected Learning and Computer-Based Modeling, Northwestern University, Evanston, IL. Available at: [NetLogo Termites Model](https://ccl.northwestern.edu/netlogo/models/Termites)
--------------------------------------------------------------------------------
/examples/hotelling_law/Readme.md:
--------------------------------------------------------------------------------
1 | # Hotelling's Law Mesa Simulation
2 |
3 | ## Overview
4 |
5 | This project is an agent-based model implemented using the Mesa framework in Python. It simulates market dynamics based on Hotelling's Law, exploring the behavior of stores in a competitive market environment. Stores adjust their prices and locations if it's increases market share to maximize revenue, providing insights into the effects of competition and customer behavior on market outcomes.
6 |
7 | ## Hotelling's Law
8 |
9 | Hotelling's Law is an economic theory that predicts competitors in a market will end up in a state of minimum differentiation, often referred to as the "principle of minimum differentiation" or "Hotelling's linear city model". This model explores how businesses choose their location in relation to competitors and how this affects pricing and consumer choice.
10 |
11 | ## Installation
12 |
13 | To run this simulation, you will need Python 3.x and the following Python libraries:
14 |
15 | - mesa
16 | - scipy
17 |
18 | You can install all required libraries by running:
19 |
20 | ```bash
21 | pip install -r requirements.txt
22 | ```
23 |
24 | ## Project Structure
25 |
26 | ```plaintext
27 | mesa-examples/
28 | └── examples/
29 | └── hotelling_law/
30 | ├── hotelling_law/
31 | │ ├── __init__.py
32 | │ ├── model.py
33 | │ └── agents.py
34 | ├── __init__.py
35 | ├── app.py
36 | ├── Readme.md
37 | ├── requirements.txt
38 | └── tests.py
39 | ```
40 |
41 | ## Running the Simulation
42 |
43 | To start the simulation, navigate to the project directory and execute the following command:
44 |
45 | ```bash
46 | solara run app.py
47 | ```
48 |
49 | # Project Details
50 |
51 | ### Professor: [Vipin P. Veetil](https://www.vipinveetil.com/)
52 | ### Indian Institute of Management, Kozhikode
53 |
54 | ### Project by
55 |
56 | | Group 8 | | |
57 | |-|---------------------------|---------------|
58 | | Name | Email Id | Roll No |
59 | | Amrita Tripathy | amrita15d@iimk.edu.in | EPGP-15D-010 |
60 | | Anirban Mondal | anirban15e@iimk.edu.in | EPGP-15E-006 |
61 | | Namita Das | namita15d@iimk.edu.in | EPGP-15D-046 |
62 | | Sandeep Shenoy | sandeep15c@iimk.edu.in | EPGP-15C-076 |
63 | | Sanjeeb Kumar Dhinda | sanjeeb15d@iimk.edu.in | EPGP-15D-074 |
64 | | Umashankar Ankuri | umashankar15d@iimk.edu.in | EPGP-15D-096 |
65 | | Vinayak Nair | vinayak15d@iimk.edu.in | EPGP-15D-102 |
66 | | Wayne Joseph Unger | wayne15d@iimk.edu.in | EPGP-15D-104 |
67 |
68 |
69 | ### Hotelling Law Simulation - Visualization
70 | 
--------------------------------------------------------------------------------
/examples/forest_fire/readme.md:
--------------------------------------------------------------------------------
1 | # Forest Fire Model
2 |
3 | ## Summary
4 |
5 | The [forest fire model](http://en.wikipedia.org/wiki/Forest-fire_model) is a simple, cellular automaton simulation of a fire spreading through a forest. The forest is a grid of cells, each of which can either be empty or contain a tree. Trees can be unburned, on fire, or burned. The fire spreads from every on-fire tree to unburned neighbors; the on-fire tree then becomes burned. This continues until the fire dies out.
6 |
7 | ## How to Run
8 |
9 | To run the model interactively, run ``mesa runserver`` in this directory. e.g.
10 |
11 | ```
12 | $ mesa runserver
13 | ```
14 |
15 | Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run.
16 |
17 | To view and run the model analyses, use the ``Forest Fire Model`` Notebook.
18 |
19 | ## Files
20 |
21 | ### ``forest_fire/model.py``
22 |
23 | This defines the model. There is one agent class, **TreeCell**. Each TreeCell object which has (x, y) coordinates on the grid, and its condition is *Fine* by default. Every step, if the tree's condition is *On Fire*, it spreads the fire to any *Fine* trees in its [Von Neumann neighborhood](http://en.wikipedia.org/wiki/Von_Neumann_neighborhood) before changing its own condition to *Burned Out*.
24 |
25 | The **ForestFire** class is the model container. It is instantiated with width and height parameters which define the grid size, and density, which is the probability of any given cell having a tree in it. When a new model is instantiated, cells are randomly filled with trees with probability equal to density. All the trees in the left-hand column (x=0) are set to *On Fire*.
26 |
27 | Each step of the model, trees are activated in random order, spreading the fire and burning out. This continues until there are no more trees on fire -- the fire has completely burned out.
28 |
29 |
30 | ### ``forest_fire/server.py``
31 |
32 | This code defines and launches the in-browser visualization for the ForestFire model. It includes the **forest_fire_draw** method, which takes a TreeCell object as an argument and turns it into a portrayal to be drawn in the browser. Each tree is drawn as a rectangle filling the entire cell, with a color based on its condition. *Fine* trees are green, *On Fire* trees red, and *Burned Out* trees are black.
33 |
34 | ## Further Reading
35 |
36 | Read about the Forest Fire model on Wikipedia: http://en.wikipedia.org/wiki/Forest-fire_model
37 |
38 | This is directly based on the comparable NetLogo model:
39 |
40 | Wilensky, U. (1997). NetLogo Fire model. http://ccl.northwestern.edu/netlogo/models/Fire. Center for Connected Learning and Computer-Based Modeling, Northwestern University, Evanston, IL.
41 |
42 |
--------------------------------------------------------------------------------
/examples/caching_and_replay/README.md:
--------------------------------------------------------------------------------
1 | # Schelling Model with Caching and Replay
2 |
3 | ## Summary
4 |
5 | This example applies caching on the Mesa [Schelling example](https://github.com/mesa/mesa-examples/tree/main/examples/schelling).
6 | It enables a simulation run to be "cached" or in other words recorded. The recorded simulation run is persisted on the local file system and can be replayed at any later point.
7 |
8 | It uses the [Mesa-Replay](https://github.com/Logende/mesa-replay) library and puts the Schelling model inside a so-called `CacheableModel` wrapper that we name `CacheableSchelling`.
9 | From the user's perspective, the new model behaves the same way as the original Schelling model, but additionally supports caching.
10 |
11 | Note that the main purpose of this example is to demonstrate that caching and replaying simulation runs is possible.
12 | The example is designed to be accessible.
13 | In practice, someone who wants to replay their simulation might not necessarily embed a replay button into the web view, but instead have a dedicated script to run a simulation that is being cached, separate from a script to replay a simulation run from a given cache file.
14 | More examples of caching and replay can be found in the [Mesa-Replay Repository](https://github.com/Logende/mesa-replay/tree/main/examples).
15 |
16 | ## Installation
17 |
18 | To install the dependencies use pip and the requirements.txt in this directory. e.g.
19 |
20 | ```
21 | $ pip install -r requirements.txt
22 | ```
23 |
24 | ## How to Run
25 |
26 | To run the model interactively, run ``mesa runserver`` in this directory. e.g.
27 |
28 | ```
29 | $ mesa runserver
30 | ```
31 |
32 | or
33 |
34 | Directly run the file ``run.py`` in the terminal. e.g.
35 |
36 | ```
37 | $ python run.py
38 | ```
39 |
40 | Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run.
41 |
42 | First, run the **simulation** with the 'Replay' switch disabled.
43 | When the simulation run is finished (e.g. all agents are happy, no more new steps are simulated), the run will automatically be stored in a cache file.
44 |
45 | Next, **replay** your latest cached simulation run by enabling the Replay switch and then pressing Reset.
46 |
47 | ## Files
48 |
49 | * ``run.py``: Launches a model visualization server and uses `CacheableModelSchelling` as simulation model
50 | * ``cacheablemodel.py``: Implements `CacheableModelSchelling` to make the original Schelling model cacheable
51 | * ``model.py``: Taken from the original Mesa Schelling example
52 | * ``server.py``: Taken from the original Mesa Schelling example
53 |
54 | ## Further Reading
55 |
56 | * [Mesa-Replay library](https://github.com/Logende/mesa-replay)
57 | * [More caching and replay examples](https://github.com/Logende/mesa-replay/tree/main/examples)
58 |
--------------------------------------------------------------------------------
/examples/bank_reserves/app.py:
--------------------------------------------------------------------------------
1 | from bank_reserves.agents import Person
2 | from bank_reserves.model import BankReservesModel
3 | from mesa.visualization import (
4 | SolaraViz,
5 | make_plot_component,
6 | make_space_component,
7 | )
8 | from mesa.visualization.user_param import (
9 | Slider,
10 | )
11 |
12 | # The colors here are taken from Matplotlib's tab10 palette
13 | # Green
14 | RICH_COLOR = "#2ca02c"
15 | # Red
16 | POOR_COLOR = "#d62728"
17 | # Blue
18 | MID_COLOR = "#1f77b4"
19 |
20 |
21 | def person_portrayal(agent):
22 | if agent is None:
23 | return
24 |
25 | portrayal = {}
26 |
27 | # update portrayal characteristics for each Person object
28 | if isinstance(agent, Person):
29 | portrayal["Shape"] = "circle"
30 | portrayal["r"] = 0.5
31 | portrayal["Layer"] = 0
32 | portrayal["Filled"] = "true"
33 |
34 | color = MID_COLOR
35 |
36 | # set agent color based on savings and loans
37 | if agent.savings > agent.model.rich_threshold:
38 | color = RICH_COLOR
39 | if agent.savings < 10 and agent.loans < 10:
40 | color = MID_COLOR
41 | if agent.loans > 10:
42 | color = POOR_COLOR
43 |
44 | portrayal["color"] = color
45 |
46 | return portrayal
47 |
48 |
49 | def post_process_space(ax):
50 | ax.set_aspect("equal")
51 | ax.set_xticks([])
52 | ax.set_yticks([])
53 |
54 |
55 | def post_process_lines(ax):
56 | ax.legend(loc="center left", bbox_to_anchor=(1, 0.9))
57 |
58 |
59 | # dictionary of user settable parameters - these map to the model __init__ parameters
60 | model_params = {
61 | "init_people": Slider(
62 | "People",
63 | 25,
64 | 1,
65 | 200,
66 | # description="Initial Number of People"
67 | ),
68 | "rich_threshold": Slider(
69 | "Rich Threshold",
70 | 10,
71 | 1,
72 | 20,
73 | # description="Upper End of Random Initial Wallet Amount",
74 | ),
75 | "reserve_percent": Slider(
76 | "Reserves",
77 | 50,
78 | 1,
79 | 100,
80 | # description="Percent of deposits the bank has to hold in reserve",
81 | ),
82 | }
83 |
84 | space_component = make_space_component(
85 | person_portrayal,
86 | draw_grid=False,
87 | post_process=post_process_space,
88 | )
89 | lineplot_component = make_plot_component(
90 | {"Rich": RICH_COLOR, "Poor": POOR_COLOR, "Middle Class": MID_COLOR},
91 | post_process=post_process_lines,
92 | )
93 | model = BankReservesModel()
94 |
95 | page = SolaraViz(
96 | model,
97 | components=[space_component, lineplot_component],
98 | model_params=model_params,
99 | name="Bank Reserves Model",
100 | )
101 | page # noqa
102 |
--------------------------------------------------------------------------------
/gis/geo_schelling_points/geo_schelling_points/model.py:
--------------------------------------------------------------------------------
1 | import random
2 | from pathlib import Path
3 |
4 | import geopandas as gpd
5 | import libpysal
6 | import mesa
7 | import mesa_geo as mg
8 |
9 | from .agents import PersonAgent, RegionAgent
10 | from .space import Nuts2Eu
11 |
12 | script_directory = Path(__file__).resolve().parent
13 |
14 |
15 | def get_largest_connected_components(gdf):
16 | """Get the largest connected component of a GeoDataFrame."""
17 | # create spatial weights matrix
18 | w = libpysal.weights.Queen.from_dataframe(
19 | gdf, use_index=True, silence_warnings=True
20 | )
21 | # get component labels
22 | gdf["component"] = w.component_labels
23 | # get the largest component
24 | largest_component = gdf["component"].value_counts().idxmax()
25 | # subset the GeoDataFrame
26 | gdf = gdf[gdf["component"] == largest_component]
27 | return gdf
28 |
29 |
30 | class GeoSchellingPoints(mesa.Model):
31 | def __init__(self, red_percentage=0.5, similarity_threshold=0.5):
32 | super().__init__()
33 |
34 | self.red_percentage = red_percentage
35 | PersonAgent.SIMILARITY_THRESHOLD = similarity_threshold
36 |
37 | self.space = Nuts2Eu()
38 |
39 | self.datacollector = mesa.DataCollector(
40 | {"unhappy": "unhappy", "happy": "happy"}
41 | )
42 |
43 | # Set up the grid with patches for every NUTS region
44 | ac = mg.AgentCreator(RegionAgent, model=self)
45 | data_path = script_directory / "../data/nuts_rg_60M_2013_lvl_2.geojson"
46 | regions_gdf = gpd.read_file(data_path)
47 | regions_gdf = get_largest_connected_components(regions_gdf)
48 | regions = ac.from_GeoDataFrame(regions_gdf)
49 | self.space.add_regions(regions)
50 |
51 | for region in regions:
52 | for _ in range(region.init_num_people):
53 | person = PersonAgent(
54 | model=self,
55 | crs=self.space.crs,
56 | geometry=region.random_point(),
57 | is_red=random.random() < self.red_percentage,
58 | region_id=region.unique_id,
59 | )
60 | self.space.add_person_to_region(person, region_id=region.unique_id)
61 |
62 | self.datacollector.collect(self)
63 |
64 | @property
65 | def unhappy(self):
66 | num_unhappy = 0
67 | for agent in self.space.agents:
68 | if isinstance(agent, PersonAgent) and agent.is_unhappy:
69 | num_unhappy += 1
70 | return num_unhappy
71 |
72 | @property
73 | def happy(self):
74 | return self.space.num_people - self.unhappy
75 |
76 | def step(self):
77 | self.agents.shuffle_do("step")
78 | self.datacollector.collect(self)
79 |
80 | if not self.unhappy:
81 | self.running = False
82 |
--------------------------------------------------------------------------------
/examples/termites/termites/agents.py:
--------------------------------------------------------------------------------
1 | from mesa.discrete_space import CellAgent
2 |
3 |
4 | class Termite(CellAgent):
5 | """A Termite agent that has ability to carry woodchip.
6 |
7 | Attributes:
8 | has_woodchip(bool): True if the agent is carrying a wood chip.
9 | """
10 |
11 | def __init__(self, model, cell):
12 | """Args:
13 | model: The model instance.
14 | cell: The starting cell (position) of the agent.
15 | """
16 | super().__init__(model)
17 | self.cell = cell
18 | self.has_woodchip = False
19 |
20 | def wiggle(self):
21 | self.cell = self.model.random.choice(self.model.grid.all_cells.cells)
22 |
23 | def search_for_chip(self):
24 | if self.cell.woodcell:
25 | self.cell.woodcell = False
26 | self.has_woodchip = True
27 |
28 | for _ in range(10):
29 | new_cell = self.cell.neighborhood.select_random_cell()
30 | if new_cell.is_empty:
31 | self.cell = new_cell
32 | break
33 | return True
34 | else:
35 | # No chip found, wiggle and return False to continue searching
36 | self.wiggle()
37 | return False
38 |
39 | def find_new_pile(self):
40 | # Continue wiggling until finding a cell with a wood chip.
41 | if not self.cell.woodcell:
42 | self.wiggle()
43 | return False
44 | return True
45 |
46 | def put_down_chip(self):
47 | if not self.has_woodchip:
48 | return True
49 |
50 | if not self.cell.woodcell:
51 | self.cell.woodcell = True
52 | self.has_woodchip = False
53 |
54 | self.get_away()
55 | return True
56 | else:
57 | empty_neighbors = [c for c in self.cell.neighborhood if c.is_empty]
58 | if empty_neighbors:
59 | self.cell = self.model.random.choice(empty_neighbors)
60 | return False
61 |
62 | def get_away(self):
63 | for _ in range(10):
64 | new_cell = self.cell.neighborhood.select_random_cell()
65 | if new_cell.is_empty:
66 | self.cell = new_cell
67 | if self.cell.woodcell:
68 | return self.get_away()
69 | break
70 |
71 | def step(self):
72 | """Protocol which termite agent follows:
73 | 1. Search for a wood chip if not carrying one.
74 | 2. Find a new pile (a cell with a wood chip) if carrying a chip.
75 | 3. Put down the chip if a suitable location is found.
76 | """
77 | if not self.has_woodchip:
78 | while not self.search_for_chip():
79 | pass
80 |
81 | while not self.find_new_pile():
82 | pass
83 |
84 | while not self.put_down_chip():
85 | pass
86 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["hatchling"]
3 | build-backend = "hatchling.build"
4 |
5 | [tool.hatch.build.targets.wheel]
6 | packages = ["examples", "gis", "rl"]
7 |
8 | [project]
9 | name = "mesa-models"
10 | description = "Importable Mesa models."
11 | license = {file = "LICENSE"}
12 | requires-python = ">=3.8"
13 | authors = [
14 | {name = "Project Mesa Team", email = "maintainers@projectmesa.dev"}
15 | ]
16 | version = "0.1.0"
17 | readme = "README.md"
18 |
19 | [project.optional-dependencies]
20 | test = [
21 | "pytest",
22 | "scipy",
23 | "pytest-cov",
24 | ]
25 | test_gis = [
26 | "pytest",
27 | "momepy",
28 | "pytest-cov",
29 | ]
30 | rl_example = [
31 | "stable-baselines3",
32 | "seaborn",
33 | "mesa",
34 | "tensorboard"
35 | ]
36 |
37 | [tool.ruff]
38 | extend-include = ["*.ipynb"]
39 |
40 | [tool.ruff.lint]
41 | # See https://github.com/charliermarsh/ruff#rules for error code definitions.
42 | select = [
43 | # "ANN", # annotations TODO
44 | "B", # bugbear
45 | "C4", # comprehensions
46 | "DTZ", # naive datetime
47 | "E", # style errors
48 | "F", # flakes
49 | "I", # import sorting
50 | "ISC", # string concatenation
51 | "N", # naming
52 | "PGH", # pygrep-hooks
53 | "PIE", # miscellaneous
54 | "PLC", # pylint convention
55 | "PLE", # pylint error
56 | # "PLR", # pylint refactor TODO
57 | "PLW", # pylint warning
58 | "Q", # quotes
59 | "RUF", # Ruff
60 | "S", # security
61 | "SIM", # simplify
62 | "T10", # debugger
63 | "UP", # upgrade
64 | "W", # style warnings
65 | "YTT", # sys.version
66 | # "D", # docstring TODO
67 | ]
68 | # Ignore list taken from https://github.com/psf/black/blob/master/.flake8
69 | # E203 Whitespace before ':'
70 | # E266 Too many leading '#' for block comment
71 | # W503 Line break occurred before a binary operator
72 | # But we don't specify them because ruff's formatter
73 | # checks for it.
74 | # See https://github.com/charliermarsh/ruff/issues/1842#issuecomment-1381210185
75 | extend-ignore = [
76 | "E501",
77 | "S101", # Use of `assert` detected
78 | "B017", # `assertRaises(Exception)` should be considered evil TODO
79 | "PGH004", # Use specific rule codes when using `noqa` TODO
80 | "B905", # `zip()` without an explicit `strict=` parameter
81 | "N802", # Function name should be lowercase
82 | "N999", # Invalid module name. We should revisit this in the future, TODO
83 | "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` TODO
84 | "S310", # Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
85 | "S603", # `subprocess` call: check for execution of untrusted input
86 | "ISC001", # ruff format asks to disable this feature
87 | "S311", # Standard pseudo-random generators are not suitable for cryptographic purposes
88 | ]
89 |
90 | [tool.ruff.lint.pydocstyle]
91 | convention = "google"
--------------------------------------------------------------------------------
/examples/el_farol/el_farol/agents.py:
--------------------------------------------------------------------------------
1 | import mesa
2 | import numpy as np
3 |
4 |
5 | class BarCustomer(mesa.Agent):
6 | def __init__(self, model, memory_size, crowd_threshold, num_strategies):
7 | super().__init__(model)
8 | # Random values from -1.0 to 1.0
9 | self.strategies = np.random.rand(num_strategies, memory_size + 1) * 2 - 1
10 | self.best_strategy = self.strategies[0]
11 | self.attend = False
12 | self.memory_size = memory_size
13 | self.crowd_threshold = crowd_threshold
14 | self.utility = 0
15 | self.update_strategies()
16 |
17 | def update_attendance(self):
18 | prediction = self.predict_attendance(
19 | self.best_strategy, self.model.history[-self.memory_size :]
20 | )
21 | if prediction <= self.crowd_threshold:
22 | self.attend = True
23 | self.model.attendance += 1
24 | else:
25 | self.attend = False
26 |
27 | def update_strategies(self):
28 | # Pick the best strategy based on new history window
29 | best_score = float("inf")
30 | for strategy in self.strategies:
31 | score = 0
32 | for week in range(self.memory_size):
33 | last = week + self.memory_size
34 | prediction = self.predict_attendance(
35 | strategy, self.model.history[week:last]
36 | )
37 | score += abs(self.model.history[last] - prediction)
38 | if score <= best_score:
39 | best_score = score
40 | self.best_strategy = strategy
41 | should_attend = self.model.history[-1] <= self.crowd_threshold
42 | if should_attend != self.attend:
43 | self.utility -= 1
44 | else:
45 | self.utility += 1
46 |
47 | def predict_attendance(self, strategy, subhistory):
48 | # This is extracted from the source code of the model in
49 | # https://ccl.northwestern.edu/netlogo/models/ElFarol.
50 | # This reports an agent's prediction of the current attendance
51 | # using a particular strategy and portion of the attendance history.
52 | # More specifically, the strategy is then described by the formula
53 | # p(t) = x(t - 1) * a(t - 1) + x(t - 2) * a(t - 2) +..
54 | # ... + x(t - memory_size) * a(t - memory_size) + c * 100,
55 | # where p(t) is the prediction at time t, x(t) is the attendance of the
56 | # bar at time t, a(t) is the weight for time t, c is a constant, and
57 | # MEMORY-SIZE is an external parameter.
58 |
59 | # The first element of the strategy is the constant, c, in the
60 | # prediction formula. one can think of it as the the agent's prediction
61 | # of the bar's attendance in the absence of any other data then we
62 | # multiply each week in the history by its respective weight.
63 | return strategy[0] * 100 + np.dot(strategy[1:], subhistory)
64 |
--------------------------------------------------------------------------------
/examples/conways_game_of_life_fast/Readme.md:
--------------------------------------------------------------------------------
1 | ## Conway's Game of Life (Fast)
2 | This example demonstrates a fast and efficient implementation of Conway's Game of Life using the [`PropertyLayer`](https://github.com/mesa/mesa/pull/1898) from the Mesa framework.
3 |
4 | 
5 |
6 | ### Overview
7 | Conway's [Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life) is a classic cellular automaton where each cell on a grid can either be alive or dead. The state of each cell changes over time based on a set of simple rules that depend on the number of alive neighbors.
8 |
9 | #### Key features:
10 | - **No grid or agents:** This implementation uses the `PropertyLayer` to manage the state of cells, eliminating the need for traditional grids or agents.
11 | - **Fast:** By using 2D convolution to count neighbors, the model efficiently applies the rules of the Game of Life across the entire grid.
12 | - **Toroidal:** The grid wraps around at the edges, creating a seamless, continuous surface.
13 |
14 | #### Performance
15 | The model is benchmarked in https://github.com/mesa/mesa/pull/1898#issuecomment-1849000346 to be about 100x faster over a traditional implementation.
16 |
17 | 
18 |
19 | - Benchmark code: [benchmark_gol.zip](https://github.com/mesa/mesa/files/13628343/benchmark_gol.zip)
20 |
21 | ### Getting Started
22 | #### Prerequisites
23 | - Python 3.10 or higher
24 | - Mesa 2.3 or higher (3.0.0b0 or higher for the visualisation)
25 | - NumPy and SciPy
26 |
27 | #### Running the Model
28 | To run the model, open a new file or notebook and add:
29 |
30 | ```Python
31 | from model import GameOfLifeModel
32 | model = GameOfLifeModel(width=10, height=10, alive_fraction=0.2)
33 | for i in range(10):
34 | model.step()
35 | ```
36 | Or to run visualized with Solara, run in your terminal:
37 |
38 | ```bash
39 | solara run app.py
40 | ```
41 |
42 | ### Understanding the Code
43 | - **Model initialization:** The grid is represented by a `PropertyLayer` where each cell is randomly initialized as alive or dead based on a given probability.
44 | - **`PropertyLayer`:** In the `cell_layer` (which is a `PropertyLayer`), each cell has either a value of 1 (alive) or 0 (dead).
45 | - **Step function:** Each simulation step calculates the number of alive neighbors for each cell and applies the Game of Life rules.
46 | - **Data collection:** The model tracks and reports the number of alive cells and the fraction of the grid that is alive.
47 |
48 | ### Customization
49 | You can easily modify the model parameters such as grid size and initial alive fraction to explore different scenarios. You can also add more metrics or visualisations.
50 |
51 | ### Summary
52 | This example provides a fast approach to modeling cellular automata using Mesa's `PropertyLayer`.
53 |
54 | ### Future work
55 | Add visualisation of the `PropertyLayer` in SolaraViz. See:
56 | - https://github.com/mesa/mesa/issues/2138
57 |
--------------------------------------------------------------------------------
/rl/epstein_civil_violence/utility.py:
--------------------------------------------------------------------------------
1 | from .agent import CitizenRL, CopRL
2 |
3 |
4 | def create_initial_agents(self):
5 | # Create agents
6 | unique_id = 0
7 | if self.cop_density + self.citizen_density > 1:
8 | raise ValueError("CopRL density + citizen density must be less than 1")
9 | cops = []
10 | citizens = []
11 | for _, (x, y) in self.grid.coord_iter():
12 | if self.random.random() < self.cop_density:
13 | unique_id_str = f"cop_{unique_id}"
14 | cop = CopRL(unique_id_str, self, (x, y), vision=self.cop_vision)
15 | unique_id += 1
16 | self.grid[x][y] = cop
17 | cops.append(cop)
18 | elif self.random.random() < (self.cop_density + self.citizen_density):
19 | unique_id_str = f"citizen_{unique_id}"
20 | citizen = CitizenRL(
21 | unique_id_str,
22 | self,
23 | (x, y),
24 | hardship=self.random.random(),
25 | regime_legitimacy=self.legitimacy,
26 | risk_aversion=self.random.random(),
27 | threshold=0,
28 | vision=self.citizen_vision,
29 | )
30 | unique_id += 1
31 | self.grid[x][y] = citizen
32 | citizens.append(citizen)
33 | # Initializing cops then citizens
34 | # This ensures cops act out their step before citizens
35 | for cop in cops:
36 | self.add(cop)
37 | for citizen in citizens:
38 | self.add(citizen)
39 |
40 |
41 | def grid_to_observation(self):
42 | # Convert neighborhood to observation grid
43 | self.obs_grid = []
44 | for i in self.grid._grid:
45 | row = []
46 | for j in i:
47 | if j is None:
48 | row.append(0) # Empty cell
49 | elif isinstance(j, CitizenRL):
50 | if j.condition == "Quiescent":
51 | row.append(
52 | 3 if j.jail_sentence > 0 else 1
53 | ) # Quiescent citizen (jailed or not)
54 | elif j.condition == "Active":
55 | row.append(2) # Active citizen
56 | else:
57 | row.append(4) # Cop
58 | self.obs_grid.append(row)
59 |
60 |
61 | def move(self, action, empty_neighbors):
62 | # Define the movement deltas
63 | moves = {
64 | 0: (1, 0), # Move right
65 | 1: (-1, 0), # Move left
66 | 2: (0, -1), # Move up
67 | 3: (0, 1), # Move down
68 | }
69 |
70 | # Get the delta for the action, defaulting to (0, 0) if the action is invalid
71 | dx, dy = moves.get(int(action), (0, 0))
72 |
73 | # Calculate the new position and wrap around the grid
74 | new_position = (
75 | (self.pos[0] + dx) % self.model.grid.width,
76 | (self.pos[1] + dy) % self.model.grid.height,
77 | )
78 |
79 | # Move the agent if the new position is in empty_neighbors
80 | if new_position in empty_neighbors:
81 | self.model.grid.move_agent(self, new_position)
82 |
--------------------------------------------------------------------------------
/examples/boltzmann_wealth_model_network/app.py:
--------------------------------------------------------------------------------
1 | from boltzmann_wealth_model_network.model import BoltzmannWealthModelNetwork
2 | from mesa.mesa_logging import INFO, log_to_stderr
3 | from mesa.visualization import (
4 | SolaraViz,
5 | make_plot_component,
6 | make_space_component,
7 | )
8 |
9 | log_to_stderr(INFO)
10 |
11 |
12 | # Tells Solara how to draw each agent.
13 | def agent_portrayal(agent):
14 | return {
15 | "color": agent.wealth, # using a colormap to convert wealth to color
16 | "size": 50,
17 | }
18 |
19 |
20 | model_params = {
21 | "seed": {
22 | "type": "InputText",
23 | "value": 42,
24 | "label": "Random seed",
25 | },
26 | "n": {
27 | "type": "SliderInt",
28 | "value": 7,
29 | "label": "Number of agents",
30 | "min": 2,
31 | "max": 10,
32 | "step": 1,
33 | # "description": "Choose how many agents to include in the model",
34 | },
35 | "num_nodes": {
36 | "type": "SliderInt",
37 | "value": 10,
38 | "label": "Number of nodes",
39 | "min": 3,
40 | "max": 12,
41 | "step": 1,
42 | # "description": "Choose how many nodes to include in the model, with at least the same number of agents",
43 | },
44 | }
45 |
46 |
47 | def post_process(ax):
48 | ax.get_figure().colorbar(ax.collections[0], label="wealth", ax=ax)
49 |
50 |
51 | # Create initial model instance
52 | money_model = BoltzmannWealthModelNetwork(n=7, num_nodes=10, seed=42)
53 |
54 | # Create visualization elements. The visualization elements are Solara
55 | # components that receive the model instance as a "prop" and display it in a
56 | # certain way. Under the hood these are just classes that receive the model
57 | # instance. You can also author your own visualization elements, which can also
58 | # be functions that receive the model instance and return a valid Solara
59 | # component.
60 |
61 | SpaceGraph = make_space_component(
62 | agent_portrayal, cmap="viridis", vmin=0, vmax=10, post_process=post_process
63 | )
64 | GiniPlot = make_plot_component("Gini")
65 |
66 | # Create the SolaraViz page. This will automatically create a server and display
67 | # the visualization elements in a web browser.
68 | #
69 | # Display it using the following command in the example directory:
70 | # solara run app.py
71 | # It will automatically update and display any changes made to this file.
72 |
73 | page = SolaraViz(
74 | money_model,
75 | components=[SpaceGraph, GiniPlot],
76 | model_params=model_params,
77 | name="Boltzmann Wealth Model: Network",
78 | )
79 | page # noqa
80 |
81 |
82 | # In a notebook environment, we can also display the visualization elements
83 | # directly.
84 | #
85 | # SpaceGraph(model1)
86 | # GiniPlot(model1)
87 |
88 | # The plots will be static. If you want to pick up model steps,
89 | # you have to make the model reactive first
90 | #
91 | # reactive_model = solara.reactive(model1)
92 | # SpaceGraph(reactive_model)
93 |
94 | # In a different notebook block:
95 | #
96 | # reactive_model.value.step()
97 |
--------------------------------------------------------------------------------
/rl/wolf_sheep/utility.py:
--------------------------------------------------------------------------------
1 | from .agents import GrassPatch, SheepRL, WolfRL
2 |
3 |
4 | def create_initial_agents(self):
5 | # Create sheep:
6 | for _ in range(self.initial_sheep):
7 | x = self.random.randrange(self.width)
8 | y = self.random.randrange(self.height)
9 | energy = self.random.randrange(2 * self.sheep_gain_from_food)
10 | unique_id_str = f"sheep_{self.next_id()}"
11 | sheep = SheepRL(unique_id_str, None, self, True, energy)
12 | self.grid.place_agent(sheep, (x, y))
13 | self.add(sheep)
14 |
15 | # Create wolves
16 | for _ in range(self.initial_wolves):
17 | x = self.random.randrange(self.width)
18 | y = self.random.randrange(self.height)
19 | energy = self.random.randrange(2 * self.wolf_gain_from_food)
20 | unique_id_str = f"wolf_{self.next_id()}"
21 | wolf = WolfRL(unique_id_str, None, self, True, energy)
22 | self.grid.place_agent(wolf, (x, y))
23 | self.add(wolf)
24 |
25 | # Create grass patches
26 | if self.grass:
27 | for _, (x, y) in self.grid.coord_iter():
28 | fully_grown = self.random.choice([True, False])
29 |
30 | if fully_grown:
31 | countdown = self.grass_regrowth_time
32 | else:
33 | countdown = self.random.randrange(self.grass_regrowth_time)
34 |
35 | unique_id_str = f"grass_{self.next_id()}"
36 | patch = GrassPatch(unique_id_str, None, self, fully_grown, countdown)
37 | self.grid.place_agent(patch, (x, y))
38 | self.add(patch)
39 |
40 |
41 | def move(self, action):
42 | empty_neighbors = self.model.grid.get_neighborhood(
43 | self.pos, moore=True, include_center=False
44 | )
45 |
46 | # Define the movement deltas
47 | moves = {
48 | 0: (1, 0), # Move right
49 | 1: (-1, 0), # Move left
50 | 2: (0, -1), # Move up
51 | 3: (0, 1), # Move down
52 | }
53 |
54 | # Get the delta for the action, defaulting to (0, 0) if the action is invalid
55 | dx, dy = moves.get(int(action), (0, 0))
56 |
57 | # Calculate the new position and wrap around the grid
58 | new_position = (
59 | (self.pos[0] + dx) % self.model.grid.width,
60 | (self.pos[1] + dy) % self.model.grid.height,
61 | )
62 |
63 | # Move the agent if the new position is in empty_neighbors
64 | if new_position in empty_neighbors:
65 | self.model.grid.move_agent(self, new_position)
66 |
67 |
68 | def grid_to_observation(self):
69 | # Convert grid to matrix for better representation
70 | self.obs_grid = []
71 | for i in self.grid._grid:
72 | row = []
73 | for j in i:
74 | value = [0, 0, 0]
75 | for agent in j:
76 | if isinstance(agent, SheepRL):
77 | value[0] = 1
78 | elif isinstance(agent, WolfRL):
79 | value[1] = 1
80 | elif isinstance(agent, GrassPatch) and agent.fully_grown:
81 | value[2] = 1
82 | row.append(value)
83 | self.obs_grid.append(row)
84 |
--------------------------------------------------------------------------------
/examples/caching_and_replay/model.py:
--------------------------------------------------------------------------------
1 | """This file was copied over from the original Schelling mesa example."""
2 |
3 | import mesa
4 | from mesa.discrete_space import CellAgent, OrthogonalMooreGrid
5 |
6 |
7 | class SchellingAgent(CellAgent):
8 | """Schelling segregation agent"""
9 |
10 | def __init__(self, model, agent_type):
11 | """Create a new Schelling agent.
12 |
13 | Args:
14 | x, y: Agent initial location.
15 | agent_type: Indicator for the agent's type (minority=1, majority=0)
16 | """
17 | super().__init__(model)
18 | self.type = agent_type
19 |
20 | def step(self):
21 | similar = 0
22 | for agent in self.cell.get_neighborhood(radius=self.model.radius).agents:
23 | if agent.type == self.type:
24 | similar += 1
25 |
26 | # If unhappy, move:
27 | if similar < self.model.homophily:
28 | self.cell = self.model.grid.select_random_empty_cell()
29 | else:
30 | self.model.happy += 1
31 |
32 |
33 | class Schelling(mesa.Model):
34 | """Model class for the Schelling segregation model."""
35 |
36 | def __init__(
37 | self,
38 | height=20,
39 | width=20,
40 | homophily=3,
41 | radius=1,
42 | density=0.8,
43 | minority_pc=0.3,
44 | seed=None,
45 | ):
46 | """Create a new Schelling model.
47 |
48 | Args:
49 | width, height: Size of the space.
50 | density: Initial Chance for a cell to populated
51 | minority_pc: Chances for an agent to be in minority class
52 | homophily: Minimum number of agents of same class needed to be happy
53 | radius: Search radius for checking similarity
54 | seed: Seed for Reproducibility
55 | """
56 | super().__init__(seed=seed)
57 | self.height = height
58 | self.width = width
59 | self.density = density
60 | self.minority_pc = minority_pc
61 | self.homophily = homophily
62 | self.radius = radius
63 |
64 | self.grid = OrthogonalMooreGrid((width, height), torus=True, random=self.random)
65 |
66 | self.happy = 0
67 | self.datacollector = mesa.DataCollector(
68 | model_reporters={"happy": "happy"}, # Model-level count of happy agents
69 | )
70 |
71 | # Set up agents
72 | # We use a grid iterator that returns
73 | # the coordinates of a cell as well as
74 | # its contents. (coord_iter)
75 | for cell in self.grid.all_cells:
76 | if self.random.random() < self.density:
77 | agent_type = 1 if self.random.random() < self.minority_pc else 0
78 | agent = SchellingAgent(self, agent_type)
79 | agent.cell = cell
80 |
81 | self.datacollector.collect(self)
82 |
83 | def step(self):
84 | """Run one step of the model."""
85 | self.happy = 0 # Reset counter of happy agents
86 | self.agents.shuffle_do("step")
87 |
88 | self.datacollector.collect(self)
89 |
90 | if self.happy == len(self.agents):
91 | self.running = False
92 |
--------------------------------------------------------------------------------
/rl/wolf_sheep/agents.py:
--------------------------------------------------------------------------------
1 | from mesa.examples.advanced.wolf_sheep.agents import GrassPatch, Sheep, Wolf
2 |
3 | from .utility import move
4 |
5 |
6 | class SheepRL(Sheep):
7 | def step(self):
8 | """The code is exactly same as mesa-example with the only difference being the move function and new sheep creation class.
9 | Link : https://github.com/mesa/mesa-examples/blob/main/examples/wolf_sheep/wolf_sheep/agents.py
10 | """
11 | action = self.model.action_dict[self.unique_id]
12 | move(self, action)
13 |
14 | living = True
15 |
16 | if self.model.grass:
17 | # Reduce energy
18 | self.energy -= 1
19 |
20 | # If there is grass available, eat it
21 | this_cell = self.model.grid.get_cell_list_contents([self.pos])
22 | grass_patch = next(obj for obj in this_cell if isinstance(obj, GrassPatch))
23 | if grass_patch.fully_grown:
24 | self.energy += self.model.sheep_gain_from_food
25 | grass_patch.fully_grown = False
26 |
27 | # Death
28 | if self.energy < 0:
29 | self.model.grid.remove_agent(self)
30 | self.model.remove(self)
31 | living = False
32 |
33 | if living and self.random.random() < self.model.sheep_reproduce:
34 | # Create a new sheep:
35 | if self.model.grass:
36 | self.energy /= 2
37 | unique_id_str = f"sheep_{self.model.next_id()}"
38 | lamb = SheepRL(unique_id_str, self.pos, self.model, self.moore, self.energy)
39 | self.model.grid.place_agent(lamb, self.pos)
40 | self.model.add(lamb)
41 |
42 |
43 | class WolfRL(Wolf):
44 | def step(self):
45 | """The code is exactly same as mesa-example with the only difference being the move function and new wolf creation class.
46 | Link : https://github.com/mesa/mesa-examples/blob/main/examples/wolf_sheep/wolf_sheep/agents.py
47 | """
48 | action = self.model.action_dict[self.unique_id]
49 | move(self, action)
50 |
51 | self.energy -= 1
52 |
53 | # If there are sheep present, eat one
54 | x, y = self.pos
55 | this_cell = self.model.grid.get_cell_list_contents([self.pos])
56 | sheep = [obj for obj in this_cell if isinstance(obj, Sheep)]
57 | if len(sheep) > 0:
58 | sheep_to_eat = self.random.choice(sheep)
59 | self.energy += self.model.wolf_gain_from_food
60 |
61 | # Kill the sheep
62 | self.model.grid.remove_agent(sheep_to_eat)
63 | self.model.remove(sheep_to_eat)
64 |
65 | # Death or reproduction
66 | if self.energy < 0:
67 | self.model.grid.remove_agent(self)
68 | self.model.remove(self)
69 | else:
70 | if self.random.random() < self.model.wolf_reproduce:
71 | # Create a new wolf cub
72 | self.energy /= 2
73 | unique_id_str = f"wolf_{self.model.next_id()}"
74 | cub = WolfRL(
75 | unique_id_str, self.pos, self.model, self.moore, self.energy
76 | )
77 | self.model.grid.place_agent(cub, cub.pos)
78 | self.model.add(cub)
79 |
--------------------------------------------------------------------------------
/examples/bank_reserves/Readme.md:
--------------------------------------------------------------------------------
1 | # Bank Reserves Model
2 |
3 | ## Summary
4 |
5 | A highly abstracted, simplified model of an economy, with only one type of agent and a single bank representing all banks in an economy. People (represented by circles) move randomly within the grid. If two or more people are on the same grid location, there is a 50% chance that they will trade with each other. If they trade, there is an equal chance of giving the other agent $5 or $2. A positive trade balance will be deposited in the bank as savings. If trading results in a negative balance, the agent will try to withdraw from its savings to cover the balance. If it does not have enough savings to cover the negative balance, it will take out a loan from the bank to cover the difference. The bank is required to keep a certain percentage of deposits as reserves. If run.py is used to run the model, then the percent of deposits the bank is required to retain is a user settable parameter. The amount the bank is able to loan at any given time is a function of the amount of deposits, its reserves, and its current total outstanding loan amount.
6 |
7 | The model demonstrates the following Mesa features:
8 | - MultiGrid for creating shareable space for agents
9 | - DataCollector for collecting data on individual model runs
10 | - Slider for adjusting initial model parameters
11 | - ModularServer for visualization of agent interaction
12 | - Agent object inheritance
13 | - Using a BatchRunner to collect data on multiple combinations of model parameters
14 |
15 | ## Installation
16 |
17 | To install the dependencies use pip and the requirements.txt in this directory. e.g.
18 |
19 | ```
20 | $ pip install -r requirements.txt
21 | ```
22 |
23 | ## Interactive Model Run
24 |
25 | To run the model interactively, use `solara run app.py` in this directory:
26 |
27 | ```
28 | $ solara run app.py
29 | ```
30 |
31 | Then open your browser to [http://localhost:8765/](http://localhost:8765/), select the model parameters, press Reset, then Start.
32 |
33 | ## Batch Run
34 |
35 | To run the model as a batch run to collect data on multiple combinations of model parameters, run "batch_run.py" in this directory.
36 |
37 | ```
38 | $ python batch_run.py
39 | ```
40 | A progress status bar will display.
41 |
42 | To update the parameters to test other parameter sweeps, edit the list of parameters in the dictionary named "br_params" in "batch_run.py".
43 |
44 | ## Files
45 |
46 | * ``app.py``: Launches visualization on Solara. Customize the visualization here.
47 | * ``bank_reserves/random_walker.py``: This defines a class that inherits from the Mesa Agent class. The main purpose is to provide a method for agents to move randomly one cell at a time.
48 | * ``bank_reserves/agents.py``: Defines the People and Bank classes.
49 | * ``bank_reserves/model.py``: Defines the Bank Reserves model and the DataCollector functions.
50 | * ``batch_run.py``: Basically the same as model.py, but includes a Mesa BatchRunner. The result of the batch run will be a .csv file with the data from every step of every run.
51 |
52 | ## Further Reading
53 |
54 | This model is a Mesa implementation of the Bank Reserves model from NetLogo:
55 |
56 | Wilensky, U. (1998). NetLogo Bank Reserves model. http://ccl.northwestern.edu/netlogo/models/BankReserves. Center for Connected Learning and Computer-Based Modeling, Northwestern University, Evanston, IL.
57 |
58 |
--------------------------------------------------------------------------------
/.coderabbit.yaml:
--------------------------------------------------------------------------------
1 | language: en-US
2 | tone_instructions: ''
3 | early_access: false
4 | enable_free_tier: true
5 | reviews:
6 | profile: chill
7 | request_changes_workflow: false
8 | high_level_summary: true
9 | high_level_summary_placeholder: '@coderabbitai summary'
10 | high_level_summary_in_walkthrough: false
11 | auto_title_placeholder: '@coderabbitai'
12 | auto_title_instructions: ''
13 | review_status: false
14 | commit_status: true
15 | fail_commit_status: false
16 | collapse_walkthrough: false
17 | changed_files_summary: true
18 | sequence_diagrams: true
19 | assess_linked_issues: true
20 | related_issues: true
21 | related_prs: true
22 | suggested_labels: true
23 | auto_apply_labels: false
24 | suggested_reviewers: true
25 | auto_assign_reviewers: false
26 | poem: true
27 | labeling_instructions: []
28 | path_filters: []
29 | path_instructions: []
30 | abort_on_close: true
31 | disable_cache: false
32 | auto_review:
33 | enabled: false
34 | auto_incremental_review: false
35 | ignore_title_keywords: []
36 | labels: []
37 | drafts: false
38 | base_branches: []
39 | finishing_touches:
40 | docstrings:
41 | enabled: true
42 | tools:
43 | ast-grep:
44 | rule_dirs: []
45 | util_dirs: []
46 | essential_rules: true
47 | packages: []
48 | shellcheck:
49 | enabled: true
50 | ruff:
51 | enabled: true
52 | markdownlint:
53 | enabled: true
54 | github-checks:
55 | enabled: true
56 | timeout_ms: 180000
57 | languagetool:
58 | enabled: true
59 | enabled_rules: []
60 | disabled_rules: []
61 | enabled_categories: []
62 | disabled_categories: []
63 | enabled_only: false
64 | level: default
65 | biome:
66 | enabled: true
67 | hadolint:
68 | enabled: true
69 | swiftlint:
70 | enabled: true
71 | phpstan:
72 | enabled: true
73 | level: default
74 | golangci-lint:
75 | enabled: true
76 | yamllint:
77 | enabled: true
78 | gitleaks:
79 | enabled: true
80 | checkov:
81 | enabled: true
82 | detekt:
83 | enabled: true
84 | eslint:
85 | enabled: true
86 | rubocop:
87 | enabled: true
88 | buf:
89 | enabled: true
90 | regal:
91 | enabled: true
92 | actionlint:
93 | enabled: true
94 | pmd:
95 | enabled: true
96 | cppcheck:
97 | enabled: true
98 | semgrep:
99 | enabled: true
100 | circleci:
101 | enabled: true
102 | sqlfluff:
103 | enabled: true
104 | prismaLint:
105 | enabled: true
106 | oxc:
107 | enabled: true
108 | shopifyThemeCheck:
109 | enabled: true
110 | chat:
111 | auto_reply: true
112 | create_issues: true
113 | integrations:
114 | jira:
115 | usage: auto
116 | linear:
117 | usage: auto
118 | knowledge_base:
119 | opt_out: false
120 | web_search:
121 | enabled: true
122 | learnings:
123 | scope: auto
124 | issues:
125 | scope: auto
126 | jira:
127 | usage: auto
128 | project_keys: []
129 | linear:
130 | usage: auto
131 | team_keys: []
132 | pull_requests:
133 | scope: auto
134 | code_generation:
135 | docstrings:
136 | language: en-US
137 | path_instructions: []
138 |
--------------------------------------------------------------------------------
/examples/virus_antibody/README.md:
--------------------------------------------------------------------------------
1 | # Virus-Antibody Model
2 |
3 | This model is a simulation of immune reaction declined as a confrontation between antibody agents and virus agents. The global idea is to model how the immune system can struggle against new virus but is able to adapt over time and beat a same virus if it comes back. The results are quite interesting as the simulation can go both ways (virus win or antibodies win) with a little tweak in the base parameters.
4 |
5 |
6 | **It showcases :**
7 | - **Usage of memory in agents** : divided into a short term memory using a deque to easily add and remove memories in case of a new virus encounter, and a long term memory (here a simple list)
8 | - **Agent knowledge sharing** : the antibodies are able to share short term memory)
9 | - **Usage of weak referencing** to avoid coding errors (antibodies can store viruses in a `self.target` attribute)
10 | - Emergence of completely **different outcomes** with only small changes in parameters
11 |
12 |
13 | For example, with a given set of fixed parameters :
14 | | Virus mutation rate = 0.15 (antibodies win) | Virus mutation rate = 0.2 (viruses win) |
15 | |--------------------------------------------------|--------------------------------------------------|
16 | |  |  |
17 |
18 |
19 |
20 |
21 | ## How It Works
22 |
23 | 1. **Initialization**: The model initializes a population of viruses and antibodies in a continuous 2D space.
24 | 2. **Agent Behavior**:
25 | - Antibodies move randomly until they detect a virus within their sight range (becomes purple), than pursue the virus.
26 | - Antibodies pass on all the virus DNA in their short term memory to the nearest antibodies (cf. example)
27 | - Viruses move randomly and can duplicate or mutate.
28 | 3. **Engagement (antibody vs virus)**: When an antibody encounters a virus:
29 | - If the antibody has the virus's DNA in its memory, it destroys the virus.
30 | - Otherwise, the virus may defeat the antibody, causing it to lose health or become inactive temporarily.
31 | 4. **Duplication**: Antibodies and viruses can duplicate according to their duplication rate.
32 |
33 |
34 | > Example for memory transmission : Let's look at two antibodies A1 and A2
35 | > `A1.st_memory() = [ABC]` and `A1.lt_memory() = [ABC]`
36 | > `A2.st_memory() = [DEF]` and `A2.lt() = [DEF]`
37 | >
38 | > After A1 encounters A2,
39 | > `A1.st_memory() = [DEF]` and `A1.lt() = [ABC, DEF]`
40 | > `A2.st_memory() = [ABC]` and `A1.lt() = [DEF, ABC]`
41 | >
42 | > A1 and A2 'switched' short term memory but both have the two viruses DNA in their long term memory
43 |
44 | For further details, here is the full architecture of this model :
45 |
46 |
47 |

48 |
49 |
50 | ## Usage
51 |
52 | After cloning the repo and installing mesa on pip, run the application with :
53 | ```bash
54 | solara run app.py
55 | ```
56 |
57 | ## A couple more of interesting cases
58 |
59 | | An interesting tendency inversion | high duplication + high mutation = both grow (more viruses) | high duplication + low mutation = both grow (more antibodies) |
60 | |---|---|---|
61 | |
|
|
|
62 |
--------------------------------------------------------------------------------
/gis/agents_and_networks/src/visualization/utils.py:
--------------------------------------------------------------------------------
1 | import datetime
2 |
3 | import matplotlib.pyplot as plt
4 | import pandas as pd
5 | import seaborn as sns
6 | import solara
7 |
8 | from ..agent.building import Building
9 | from ..agent.commuter import Commuter
10 | from ..agent.geo_agents import Driveway, LakeAndRiver, Walkway
11 |
12 |
13 | def make_plot_clock(model):
14 | return solara.Markdown(f"**Day {model.day}, {model.hour:02d}:{model.minute:02d}**")
15 |
16 |
17 | def agent_draw(agent):
18 | portrayal = {}
19 | portrayal["color"] = "White"
20 | if isinstance(agent, Driveway):
21 | portrayal["color"] = "#D08004"
22 | elif isinstance(agent, Walkway):
23 | portrayal["color"] = "Brown"
24 | elif isinstance(agent, LakeAndRiver):
25 | portrayal["color"] = "#04D0CD"
26 | elif isinstance(agent, Building):
27 | portrayal["color"] = "Grey"
28 | # if agent.function is None:
29 | # portrayal["color"] = "Grey"
30 | # elif agent.function == 1.0:
31 | # portrayal["color"] = "Blue"
32 | # elif agent.function == 2.0:
33 | # portrayal["color"] = "Green"
34 | # else:
35 | # portrayal["color"] = "Grey"
36 | elif isinstance(agent, Commuter):
37 | if agent.status == "home":
38 | portrayal["color"] = "Green"
39 | elif agent.status == "work":
40 | portrayal["color"] = "Blue"
41 | elif agent.status == "transport":
42 | portrayal["color"] = "Red"
43 | else:
44 | portrayal["color"] = "Grey"
45 | portrayal["radius"] = "5"
46 | portrayal["fillOpacity"] = 1
47 | return portrayal
48 |
49 |
50 | def plot_commuter_status_count(model_vars_df: pd.DataFrame) -> None:
51 | commuter_status_df = model_vars_df.rename(
52 | columns=lambda x: x.replace("status_", "")
53 | )
54 | commuter_status_df["time"] = commuter_status_df["time"] / pd.Timedelta(minutes=1)
55 | commuter_status_df = commuter_status_df.melt(
56 | id_vars=["time"],
57 | value_vars=["home", "traveling", "work"],
58 | var_name="status",
59 | value_name="count",
60 | )
61 | sns.relplot(
62 | x="time",
63 | y="count",
64 | data=commuter_status_df,
65 | kind="line",
66 | hue="status",
67 | aspect=1.5,
68 | )
69 | plt.gca().xaxis.set_major_formatter(
70 | lambda x, pos: ":".join(str(datetime.timedelta(minutes=x)).split(":")[:2])
71 | )
72 | plt.xticks(rotation=90)
73 | plt.title("Number of commuters by status")
74 |
75 |
76 | def plot_num_friendships(model_vars_df: pd.DataFrame) -> None:
77 | friendship_df = model_vars_df.rename(columns=lambda x: x.replace("friendship_", ""))
78 | friendship_df["time"] = friendship_df["time"] / pd.Timedelta(minutes=1)
79 | friendship_df = friendship_df.melt(
80 | id_vars=["time"],
81 | value_vars=["home", "work"],
82 | var_name="friendship",
83 | value_name="count",
84 | )
85 | sns.relplot(
86 | x="time",
87 | y="count",
88 | data=friendship_df,
89 | kind="line",
90 | hue="friendship",
91 | aspect=1.5,
92 | )
93 | plt.gca().xaxis.set_major_formatter(
94 | lambda x, pos: ":".join(str(datetime.timedelta(minutes=x)).split(":")[:2])
95 | )
96 | plt.xticks(rotation=90)
97 | plt.title("Number of friendships")
98 |
--------------------------------------------------------------------------------
/examples/charts/charts/server.py:
--------------------------------------------------------------------------------
1 | import mesa
2 | from charts.agents import Person
3 | from charts.model import Charts
4 |
5 | """
6 | Citation:
7 | The following code was adapted from server.py at
8 | https://github.com/mesa/mesa/blob/main/examples/wolf_sheep/wolf_sheep/server.py
9 | Accessed on: November 2, 2017
10 | Author of original code: Taylor Mutch
11 | """
12 |
13 | # The colors here are taken from Matplotlib's tab10 palette
14 | # Green
15 | RICH_COLOR = "#2ca02c"
16 | # Red
17 | POOR_COLOR = "#d62728"
18 | # Blue
19 | MID_COLOR = "#1f77b4"
20 |
21 |
22 | def person_portrayal(agent):
23 | if agent is None:
24 | return
25 |
26 | portrayal = {}
27 |
28 | # update portrayal characteristics for each Person object
29 | if isinstance(agent, Person):
30 | portrayal["Shape"] = "circle"
31 | portrayal["r"] = 0.5
32 | portrayal["Layer"] = 0
33 | portrayal["Filled"] = "true"
34 |
35 | color = MID_COLOR
36 |
37 | # set agent color based on savings and loans
38 | if agent.savings > agent.model.rich_threshold:
39 | color = RICH_COLOR
40 | if agent.savings < 10 and agent.loans < 10:
41 | color = MID_COLOR
42 | if agent.loans > 10:
43 | color = POOR_COLOR
44 |
45 | portrayal["Color"] = color
46 |
47 | return portrayal
48 |
49 |
50 | # dictionary of user settable parameters - these map to the model __init__ parameters
51 | model_params = {
52 | "init_people": mesa.visualization.Slider(
53 | "People", 25, 1, 200, description="Initial Number of People"
54 | ),
55 | "rich_threshold": mesa.visualization.Slider(
56 | "Rich Threshold",
57 | 10,
58 | 1,
59 | 20,
60 | description="Upper End of Random Initial Wallet Amount",
61 | ),
62 | "reserve_percent": mesa.visualization.Slider(
63 | "Reserves",
64 | 50,
65 | 1,
66 | 100,
67 | description="Percent of deposits the bank has to hold in reserve",
68 | ),
69 | }
70 |
71 | # set the portrayal function and size of the canvas for visualization
72 | canvas_element = mesa.visualization.CanvasGrid(person_portrayal, 20, 20, 500, 500)
73 |
74 | # map data to chart in the ChartModule
75 | line_chart = mesa.visualization.ChartModule(
76 | [
77 | {"Label": "Rich", "Color": RICH_COLOR},
78 | {"Label": "Poor", "Color": POOR_COLOR},
79 | {"Label": "Middle Class", "Color": MID_COLOR},
80 | ]
81 | )
82 |
83 | model_bar = mesa.visualization.BarChartModule(
84 | [
85 | {"Label": "Rich", "Color": RICH_COLOR},
86 | {"Label": "Poor", "Color": POOR_COLOR},
87 | {"Label": "Middle Class", "Color": MID_COLOR},
88 | ]
89 | )
90 |
91 | agent_bar = mesa.visualization.BarChartModule(
92 | [{"Label": "Wealth", "Color": MID_COLOR}],
93 | scope="agent",
94 | sorting="ascending",
95 | sort_by="Wealth",
96 | )
97 |
98 | pie_chart = mesa.visualization.PieChartModule(
99 | [
100 | {"Label": "Rich", "Color": RICH_COLOR},
101 | {"Label": "Middle Class", "Color": MID_COLOR},
102 | {"Label": "Poor", "Color": POOR_COLOR},
103 | ]
104 | )
105 |
106 | # create instance of Mesa ModularServer
107 | server = mesa.visualization.ModularServer(
108 | Charts,
109 | [canvas_element, line_chart, model_bar, agent_bar, pie_chart],
110 | "Mesa Charts",
111 | model_params=model_params,
112 | )
113 |
--------------------------------------------------------------------------------
/gis/agents_and_networks/src/space/campus.py:
--------------------------------------------------------------------------------
1 | import random
2 | from collections import defaultdict
3 | from typing import DefaultDict
4 |
5 | import mesa
6 | import mesa_geo as mg
7 | from shapely.geometry import Point
8 |
9 | from ..agent.building import Building
10 | from ..agent.commuter import Commuter
11 |
12 |
13 | class Campus(mg.GeoSpace):
14 | homes: tuple[Building]
15 | works: tuple[Building]
16 | other_buildings: tuple[Building]
17 | home_counter: DefaultDict[mesa.space.FloatCoordinate, int]
18 | _buildings: dict[int, Building]
19 | _commuters_pos_map: DefaultDict[mesa.space.FloatCoordinate, set[Commuter]]
20 | _commuter_id_map: dict[int, Commuter]
21 |
22 | def __init__(self, crs: str) -> None:
23 | super().__init__(crs=crs)
24 | self.homes = ()
25 | self.works = ()
26 | self.other_buildings = ()
27 | self.home_counter = defaultdict(int)
28 | self._buildings = {}
29 | self._commuters_pos_map = defaultdict(set)
30 | self._commuter_id_map = {}
31 |
32 | def get_random_home(self) -> Building:
33 | return random.choice(self.homes)
34 |
35 | def get_random_work(self) -> Building:
36 | return random.choice(self.works)
37 |
38 | def get_building_by_id(self, unique_id: int) -> Building:
39 | return self._buildings[unique_id]
40 |
41 | def add_buildings(self, agents) -> None:
42 | super().add_agents(agents)
43 | homes, works, other_buildings = [], [], []
44 | for agent in agents:
45 | if isinstance(agent, Building):
46 | self._buildings[agent.unique_id] = agent
47 | if agent.function == 0.0:
48 | other_buildings.append(agent)
49 | elif agent.function == 1.0:
50 | works.append(agent)
51 | elif agent.function == 2.0:
52 | homes.append(agent)
53 | self.other_buildings = self.other_buildings + tuple(other_buildings)
54 | self.works = self.works + tuple(works)
55 | self.homes = self.homes + tuple(homes)
56 |
57 | def get_commuters_by_pos(
58 | self, float_pos: mesa.space.FloatCoordinate
59 | ) -> set[Commuter]:
60 | return self._commuters_pos_map[float_pos]
61 |
62 | def get_commuter_by_id(self, commuter_id: int) -> Commuter:
63 | return self._commuter_id_map[commuter_id]
64 |
65 | def add_commuter(self, agent: Commuter) -> None:
66 | super().add_agents([agent])
67 | self._commuters_pos_map[(agent.geometry.x, agent.geometry.y)].add(agent)
68 | self._commuter_id_map[agent.unique_id] = agent
69 |
70 | def update_home_counter(
71 | self,
72 | old_home_pos: mesa.space.FloatCoordinate | None,
73 | new_home_pos: mesa.space.FloatCoordinate,
74 | ) -> None:
75 | if old_home_pos is not None:
76 | self.home_counter[old_home_pos] -= 1
77 | self.home_counter[new_home_pos] += 1
78 |
79 | def move_commuter(
80 | self, commuter: Commuter, pos: mesa.space.FloatCoordinate
81 | ) -> None:
82 | self.__remove_commuter(commuter)
83 | commuter.geometry = Point(pos)
84 | self.add_commuter(commuter)
85 |
86 | def __remove_commuter(self, commuter: Commuter) -> None:
87 | super().remove_agent(commuter)
88 | del self._commuter_id_map[commuter.unique_id]
89 | self._commuters_pos_map[(commuter.geometry.x, commuter.geometry.y)].remove(
90 | commuter
91 | )
92 |
--------------------------------------------------------------------------------
/gis/population/population/model.py:
--------------------------------------------------------------------------------
1 | import math
2 | import random
3 | from pathlib import Path
4 |
5 | import mesa
6 | import mesa_geo as mg
7 | import numpy as np
8 | from shapely.geometry import Point
9 |
10 | from .space import UgandaArea
11 |
12 | script_directory = Path(__file__).resolve().parent
13 |
14 |
15 | class Person(mg.GeoAgent):
16 | MOBILITY_RANGE_X = 0.0
17 | MOBILITY_RANGE_Y = 0.0
18 |
19 | def __init__(self, model, geometry, crs, img_coord):
20 | super().__init__(model, geometry, crs)
21 | self.img_coord = img_coord
22 |
23 | def set_random_world_coord(self):
24 | world_coord_point = Point(
25 | self.model.space.population_layer.transform * self.img_coord
26 | )
27 | random_world_coord_x = world_coord_point.x + np.random.uniform(
28 | -self.MOBILITY_RANGE_X, self.MOBILITY_RANGE_X
29 | )
30 | random_world_coord_y = world_coord_point.y + np.random.uniform(
31 | -self.MOBILITY_RANGE_Y, self.MOBILITY_RANGE_Y
32 | )
33 | self.geometry = Point(random_world_coord_x, random_world_coord_y)
34 |
35 | def step(self):
36 | neighborhood = self.model.space.population_layer.get_neighborhood(
37 | self.img_coord, moore=True
38 | )
39 | found = False
40 | while neighborhood and not found:
41 | next_img_coord = random.choice(neighborhood)
42 | world_coord_point = Point(
43 | self.model.space.population_layer.transform * next_img_coord
44 | )
45 | if world_coord_point.within(self.model.space.lake):
46 | neighborhood.remove(next_img_coord)
47 | continue
48 | else:
49 | found = True
50 | self.img_coord = next_img_coord
51 | self.set_random_world_coord()
52 |
53 |
54 | class Population(mesa.Model):
55 | def __init__(
56 | self,
57 | population_gzip_file="../data/popu.asc.gz",
58 | lake_zip_file="../data/lake.zip",
59 | world_zip_file="../data/clip.zip",
60 | ):
61 | super().__init__()
62 | self.space = UgandaArea(crs="epsg:4326")
63 | self.space.load_data(
64 | script_directory / population_gzip_file,
65 | script_directory / lake_zip_file,
66 | script_directory / world_zip_file,
67 | model=self,
68 | )
69 | pixel_size_x, pixel_size_y = self.space.population_layer.resolution
70 | Person.MOBILITY_RANGE_X = pixel_size_x / 2.0
71 | Person.MOBILITY_RANGE_Y = pixel_size_y / 2.0
72 |
73 | self._create_agents()
74 |
75 | def _create_agents(self):
76 | num_agents = 0
77 | for cell in self.space.population_layer:
78 | popu_round = math.ceil(cell.population)
79 | if popu_round > 0:
80 | for _ in range(popu_round):
81 | num_agents += 1
82 | point = Point(self.space.population_layer.transform * cell.indices)
83 | if not point.within(self.space.lake):
84 | person = Person(
85 | model=self,
86 | crs=self.space.crs,
87 | geometry=point,
88 | img_coord=cell.indices,
89 | )
90 | person.set_random_world_coord()
91 | self.space.add_agents(person)
92 |
93 | def step(self):
94 | self.agents.shuffle_do("step")
95 |
--------------------------------------------------------------------------------
/gis/agents_and_networks/src/space/road_network.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import pickle
4 |
5 | import geopandas as gpd
6 | import mesa
7 | import momepy
8 | import networkx as nx
9 | import pyproj
10 | from sklearn.neighbors import KDTree
11 |
12 | from .utils import segmented
13 |
14 |
15 | class RoadNetwork:
16 | _nx_graph: nx.Graph
17 | _kd_tree: KDTree
18 | _crs: pyproj.CRS
19 |
20 | def __init__(self, lines: gpd.GeoSeries):
21 | segmented_lines = gpd.GeoDataFrame(geometry=segmented(lines))
22 | G = momepy.gdf_to_nx(segmented_lines, approach="primal", length="length") # noqa: N806
23 | self.nx_graph = G.subgraph(max(nx.connected_components(G), key=len))
24 | self.crs = lines.crs
25 |
26 | @property
27 | def nx_graph(self) -> nx.Graph:
28 | return self._nx_graph
29 |
30 | @nx_graph.setter
31 | def nx_graph(self, nx_graph) -> None:
32 | self._nx_graph = nx_graph
33 | self._kd_tree = KDTree(nx_graph.nodes)
34 |
35 | @property
36 | def crs(self) -> pyproj.CRS:
37 | return self._crs
38 |
39 | @crs.setter
40 | def crs(self, crs) -> None:
41 | self._crs = crs
42 |
43 | def get_nearest_node(
44 | self, float_pos: mesa.space.FloatCoordinate
45 | ) -> mesa.space.FloatCoordinate:
46 | node_index = self._kd_tree.query([float_pos], k=1, return_distance=False)
47 | node_pos = self._kd_tree.get_arrays()[0][node_index[0, 0]]
48 | return tuple(node_pos)
49 |
50 | def get_shortest_path(
51 | self, source: mesa.space.FloatCoordinate, target: mesa.space.FloatCoordinate
52 | ) -> list[mesa.space.FloatCoordinate]:
53 | from_node_pos = self.get_nearest_node(source)
54 | to_node_pos = self.get_nearest_node(target)
55 | # return nx.shortest_path(self.nx_graph, from_node_pos,
56 | # to_node_pos, method="dijkstra", weight="length")
57 | return nx.astar_path(self.nx_graph, from_node_pos, to_node_pos, weight="length")
58 |
59 |
60 | class CampusWalkway(RoadNetwork):
61 | campus: str
62 | _path_select_cache: dict[
63 | tuple[mesa.space.FloatCoordinate, mesa.space.FloatCoordinate],
64 | list[mesa.space.FloatCoordinate],
65 | ]
66 |
67 | def __init__(self, campus, lines, output_dir) -> None:
68 | super().__init__(lines)
69 | self.campus = campus
70 | self._path_cache_result = f"{output_dir}/{campus}_path_cache_result.pkl"
71 | try:
72 | with open(self._path_cache_result, "rb") as cached_result:
73 | self._path_select_cache = pickle.load(cached_result) # noqa: S301
74 | except FileNotFoundError:
75 | self._path_select_cache = {}
76 |
77 | def cache_path(
78 | self,
79 | source: mesa.space.FloatCoordinate,
80 | target: mesa.space.FloatCoordinate,
81 | path: list[mesa.space.FloatCoordinate],
82 | ) -> None:
83 | # print(f"caching path... current number of cached paths:
84 | # {len(self._path_select_cache)}")
85 | self._path_select_cache[(source, target)] = path
86 | self._path_select_cache[(target, source)] = list(reversed(path))
87 | with open(self._path_cache_result, "wb") as cached_result:
88 | pickle.dump(self._path_select_cache, cached_result)
89 |
90 | def get_cached_path(
91 | self, source: mesa.space.FloatCoordinate, target: mesa.space.FloatCoordinate
92 | ) -> list[mesa.space.FloatCoordinate] | None:
93 | return self._path_select_cache.get((source, target), None)
94 |
--------------------------------------------------------------------------------
/examples/warehouse/app.py:
--------------------------------------------------------------------------------
1 | import matplotlib.pyplot as plt
2 | import pandas as pd
3 | import solara
4 | from mesa.visualization import SolaraViz
5 | from mesa.visualization.utils import update_counter
6 | from warehouse.agents import InventoryAgent
7 | from warehouse.model import WarehouseModel
8 |
9 | # Constants
10 | LOADING_DOCKS = [(0, 0, 0), (0, 2, 0), (0, 4, 0), (0, 6, 0), (0, 8, 0)]
11 | AXIS_LIMITS = {"x": (0, 22), "y": (0, 20), "z": (0, 5)}
12 |
13 | model_params = {
14 | "seed": {
15 | "type": "InputText",
16 | "value": 42,
17 | "label": "Random Seed",
18 | },
19 | }
20 |
21 |
22 | def prepare_agent_data(model, agent_type, agent_label):
23 | """Prepare data for agents of a specific type.
24 |
25 | Args:
26 | model: The WarehouseModel instance.
27 | agent_type: The type of agent (e.g., "InventoryAgent", "RobotAgent").
28 | agent_label: The label for the agent type.
29 |
30 | Returns:
31 | A list of dictionaries containing agent coordinates and type.
32 | """
33 | return [
34 | {
35 | "x": agent.cell.coordinate[0],
36 | "y": agent.cell.coordinate[1],
37 | "z": agent.cell.coordinate[2],
38 | "type": agent_label,
39 | }
40 | for agent in model.agents_by_type[agent_type]
41 | ]
42 |
43 |
44 | @solara.component
45 | def plot_warehouse(model):
46 | """Visualize the warehouse model in a 3D scatter plot.
47 |
48 | Args:
49 | model: The WarehouseModel instance.
50 | """
51 | update_counter.get()
52 |
53 | # Prepare data for inventory and robot agents
54 | inventory_data = prepare_agent_data(model, InventoryAgent, "Inventory")
55 | robot_data = prepare_agent_data(model, type(model.RobotAgent), "Robot")
56 |
57 | # Combine data into a single DataFrame
58 | data = pd.DataFrame(inventory_data + robot_data)
59 |
60 | # Create Matplotlib 3D scatter plot
61 | fig = plt.figure(figsize=(8, 6))
62 | ax = fig.add_subplot(111, projection="3d")
63 |
64 | # Highlight loading dock cells
65 | for i, dock in enumerate(LOADING_DOCKS):
66 | ax.scatter(
67 | dock[0],
68 | dock[1],
69 | dock[2],
70 | c="yellow",
71 | label="Loading Dock"
72 | if i == 0
73 | else None, # Add label only to the first dock
74 | s=300,
75 | marker="o",
76 | )
77 |
78 | # Plot inventory agents
79 | inventory = data[data["type"] == "Inventory"]
80 | ax.scatter(
81 | inventory["x"],
82 | inventory["y"],
83 | inventory["z"],
84 | c="blue",
85 | label="Inventory",
86 | s=100,
87 | marker="s",
88 | )
89 |
90 | # Plot robot agents
91 | robots = data[data["type"] == "Robot"]
92 | ax.scatter(robots["x"], robots["y"], robots["z"], c="red", label="Robot", s=200)
93 |
94 | # Set labels, title, and legend
95 | ax.set_xlabel("X")
96 | ax.set_ylabel("Y")
97 | ax.set_zlabel("Z")
98 | ax.set_title("Warehouse Visualization")
99 | ax.legend()
100 |
101 | # Configure plot appearance
102 | ax.grid(False)
103 | ax.set_xlim(*AXIS_LIMITS["x"])
104 | ax.set_ylim(*AXIS_LIMITS["y"])
105 | ax.set_zlim(*AXIS_LIMITS["z"])
106 | ax.axis("off")
107 |
108 | # Render the plot in Solara
109 | solara.FigureMatplotlib(fig)
110 |
111 |
112 | # Create initial model instance
113 | model = WarehouseModel()
114 |
115 | # Create the SolaraViz page
116 | page = SolaraViz(
117 | model,
118 | components=[plot_warehouse],
119 | model_params=model_params,
120 | name="Pseudo-Warehouse Model",
121 | )
122 |
123 | page # noqa
124 |
--------------------------------------------------------------------------------
/examples/virus_antibody/app.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import weakref
4 |
5 | from agents import AntibodyAgent, VirusAgent
6 | from matplotlib.markers import MarkerStyle
7 | from model import VirusAntibodyModel
8 |
9 | sys.path.insert(0, os.path.abspath("../../../mesa"))
10 |
11 | from mesa.experimental.devs import ABMSimulator
12 | from mesa.visualization import (
13 | Slider,
14 | SolaraViz,
15 | make_plot_component,
16 | make_space_component,
17 | )
18 |
19 | # Style and portrayals
20 | MARKER_CACHE = {}
21 | MARKER_CACHE["antibody"] = MarkerStyle("o")
22 | MARKER_CACHE["virus"] = MarkerStyle("*")
23 |
24 |
25 | def agent_portrayal(agent):
26 | """Portray an agent for visualization."""
27 | portrayal = {}
28 |
29 | if isinstance(agent, AntibodyAgent):
30 | portrayal["marker"] = MARKER_CACHE["antibody"]
31 | portrayal["size"] = 30
32 |
33 | if isinstance(agent.target, weakref.ReferenceType):
34 | target_obj = agent.target() # dereference the weakref
35 | else:
36 | target_obj = agent.target
37 |
38 | if target_obj == agent:
39 | # gray if ko
40 | portrayal["color"] = "gray"
41 | portrayal["layer"] = 2
42 |
43 | elif target_obj is None:
44 | # Blue if moving
45 | portrayal["color"] = "blue"
46 | portrayal["layer"] = 1
47 |
48 | else:
49 | # Purple if aiming for virus
50 | portrayal["color"] = "purple"
51 | portrayal["layer"] = 1
52 |
53 | elif isinstance(agent, VirusAgent):
54 | portrayal["marker"] = MARKER_CACHE["virus"]
55 | portrayal["size"] = 50
56 | portrayal["color"] = "red"
57 | portrayal["filled"] = True
58 | portrayal["layer"] = 0
59 |
60 | return portrayal
61 |
62 |
63 | # Setup model parameters for the visualization interface
64 | simulator = ABMSimulator()
65 | model = VirusAntibodyModel()
66 |
67 | model_params = {
68 | "seed": {
69 | "type": "InputText",
70 | "value": 42,
71 | "label": "Random Seed",
72 | },
73 | "initial_antibody": Slider(
74 | label="Number of antibodies",
75 | value=20,
76 | min=1,
77 | max=50,
78 | step=1,
79 | ),
80 | "antibody_duplication_rate": Slider(
81 | label="Rate of duplication for antibodies",
82 | value=0.01,
83 | min=0,
84 | max=0.05,
85 | step=0.001,
86 | ),
87 | "initial_viruses": Slider(
88 | label="Number of viruses",
89 | value=20,
90 | min=1,
91 | max=50,
92 | step=1,
93 | ),
94 | "virus_duplication_rate": Slider(
95 | label="Rate of duplication for viruses",
96 | value=0.01,
97 | min=0,
98 | max=0.05,
99 | step=0.001,
100 | ),
101 | "virus_mutation_rate": Slider(
102 | label="Rate of mutation for viruses",
103 | value=0.05,
104 | min=0,
105 | max=0.3,
106 | step=0.01,
107 | ),
108 | }
109 |
110 |
111 | # Visualization and plots
112 |
113 |
114 | def post_process_lines(ax):
115 | ax.legend(loc="center left", bbox_to_anchor=(1, 0.9))
116 |
117 |
118 | agents_lineplot_component = make_plot_component(
119 | {"Antibodies": "tab:blue", "Viruses": "tab:red"},
120 | post_process=post_process_lines,
121 | )
122 |
123 | agent_portrayal_component = make_space_component(
124 | agent_portrayal=agent_portrayal, backend="matplotlib"
125 | )
126 |
127 | page = SolaraViz(
128 | model,
129 | components=[
130 | agent_portrayal_component,
131 | agents_lineplot_component,
132 | ],
133 | model_params=model_params,
134 | name="Virus/Antibody",
135 | )
136 |
137 | page # noqa
138 |
--------------------------------------------------------------------------------
/gis/agents_and_networks/src/space/utils.py:
--------------------------------------------------------------------------------
1 | import geopandas as gpd
2 | import mesa
3 | import numpy as np
4 | import pyproj
5 | from shapely.geometry import LineString, MultiLineString
6 | from shapely.ops import transform
7 |
8 |
9 | def get_coord_matrix(
10 | x_min: float, x_max: float, y_min: float, y_max: float
11 | ) -> np.ndarray:
12 | return np.array(
13 | [
14 | [x_min, y_min, 1.0],
15 | [x_min, y_max, 1.0],
16 | [x_max, y_min, 1.0],
17 | [x_max, y_max, 1.0],
18 | ]
19 | )
20 |
21 |
22 | def get_affine_transform(
23 | from_coord: np.ndarray, to_coord: np.ndarray
24 | ) -> tuple[float, float, float, float, float, float]:
25 | A, res, rank, s = np.linalg.lstsq(from_coord, to_coord, rcond=None) # noqa: N806
26 |
27 | np.testing.assert_array_almost_equal(res, np.zeros_like(res), decimal=15)
28 | np.testing.assert_array_almost_equal(A[:, 2], np.array([0.0, 0.0, 1.0]), decimal=15)
29 |
30 | # A.T = [[a, b, x_off],
31 | # [d, e, y_off],
32 | # [0, 0, 1 ]]
33 | # affine transform = [a, b, d, e, x_off, y_off]
34 | # For details, refer to https://geopandas.org/en/stable/docs/reference/api/geopandas.GeoSeries.affine_transform.html
35 | return A.T[0, 0], A.T[0, 1], A.T[1, 0], A.T[1, 1], A.T[0, 2], A.T[1, 2]
36 |
37 |
38 | def get_rounded_coordinate(
39 | float_coordinate: mesa.space.FloatCoordinate,
40 | ) -> mesa.space.Coordinate:
41 | return round(float_coordinate[0]), round(float_coordinate[1])
42 |
43 |
44 | def segmented(lines: gpd.GeoSeries) -> gpd.GeoSeries:
45 | def _segmented(linestring: LineString) -> list[LineString]:
46 | return [
47 | LineString((start_node, end_node))
48 | for start_node, end_node in zip(
49 | linestring.coords[:-1], linestring.coords[1:]
50 | )
51 | if start_node != end_node
52 | ]
53 |
54 | return gpd.GeoSeries([segment for line in lines for segment in _segmented(line)])
55 |
56 |
57 | # reference: https://gis.stackexchange.com/questions/367228/using-shapely-interpolate-to-evenly-re-sample-points-on-a-linestring-geodatafram
58 | def redistribute_vertices(geom, distance):
59 | if isinstance(geom, LineString):
60 | if (num_vert := round(geom.length / distance)) == 0:
61 | num_vert = 1
62 | return LineString(
63 | [
64 | geom.interpolate(float(n) / num_vert, normalized=True)
65 | for n in range(num_vert + 1)
66 | ]
67 | )
68 | elif isinstance(geom, MultiLineString):
69 | parts = [redistribute_vertices(part, distance) for part in geom]
70 | return type(geom)([p for p in parts if not p.is_empty])
71 | else:
72 | raise TypeError(
73 | f"Wrong type: {type(geom)}. Must be LineString or MultiLineString."
74 | )
75 |
76 |
77 | class UnitTransformer:
78 | _degree2meter: pyproj.Transformer
79 | _meter2degree: pyproj.Transformer
80 |
81 | def __init__(
82 | self, degree_crs: pyproj.CRS | None = None, meter_crs: pyproj.CRS | None = None
83 | ):
84 | if degree_crs is None:
85 | degree_crs = pyproj.CRS("EPSG:4326")
86 |
87 | if meter_crs is None:
88 | meter_crs = pyproj.CRS("EPSG:3857")
89 |
90 | self._degree2meter = pyproj.Transformer.from_crs(
91 | degree_crs, meter_crs, always_xy=True
92 | )
93 | self._meter2degree = pyproj.Transformer.from_crs(
94 | meter_crs, degree_crs, always_xy=True
95 | )
96 |
97 | def degree2meter(self, geom):
98 | return transform(self._degree2meter.transform, geom)
99 |
100 | def meter2degree(self, geom):
101 | return transform(self._meter2degree.transform, geom)
102 |
--------------------------------------------------------------------------------