Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions examples/caching_and_replay/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Caching and Replaying Schelling

## Summary

This example applies caching on the Mesa [Schelling example](https://github.com/projectmesa/mesa-examples/tree/main/examples/Schelling).
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.

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`.
From the user's perspective, the new model behaves the same way as the original Schelling model, but additionally supports caching.

## Installation

To install the dependencies use pip and the requirements.txt in this directory. e.g.

```
$ pip install -r requirements.txt
```

## How to Run

To run the model interactively, run ``mesa runserver`` in this directory. e.g.

```
$ mesa runserver
```

Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run.

After running a regular simulation, you can **replay** your latest simulation run by first enabling the Replay switch and then pressing Reset.
Note that this **requires the previous simulation run to have finished** (e.g. all agents are happy, no more new steps are simulated).

## Files

* ``run.py``: Launches a model visualization server and uses `CacheableModelSchelling` as simulation model
* ``cacheablemodel.py``: Implements `CacheableModelSchelling` to make the original Schelling model cacheable
* ``model.py``: Taken from the original Mesa Schelling example
* ``server.py``: Taken from the original Mesa Schelling example

## Further Reading

* [Mesa-Replay library](https://github.com/Logende/mesa-replay)
* [More caching and replay examples](https://github.com/Logende/mesa-replay/tree/main/examples)
28 changes: 28 additions & 0 deletions examples/caching_and_replay/cacheablemodel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from model import Schelling
from mesa_replay import CacheableModel, CacheState


class CacheableSchelling(CacheableModel):
"""A wrapper around the original Schelling model to make the simulation cacheable and replay-able.
Uses CacheableModel from the Mesa-Replay library, which is a decorator that can be put around any regular mesa model
to make it "cacheable". From outside, a CacheableSchelling instance can be treated like any regular Mesa model.
The only difference is that the model will write the state of every simulation step to a cache file or when in
replay mode use a given cache file to replay that cached simulation run."""

def __init__(
self,
width=20,
height=20,
density=0.8,
minority_pc=0.2,
homophily=3,
# Note that this is an additional parameter we add to our model, which decides whether to simulate or replay
replay=False,
):
actual_model = Schelling(width, height, density, minority_pc, homophily)
cache_state = CacheState.REPLAY if replay else CacheState.RECORD
super().__init__(
actual_model,
cache_file_path="my_cache_file_path.cache",
cache_state=cache_state,
)
91 changes: 91 additions & 0 deletions examples/caching_and_replay/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""This file was copied over from the original Schelling mesa example."""

import mesa


class SchellingAgent(mesa.Agent):
"""
Schelling segregation agent
"""

def __init__(self, pos, model, agent_type):
"""
Create a new Schelling agent.

Args:
unique_id: Unique identifier for the agent.
x, y: Agent initial location.
agent_type: Indicator for the agent's type (minority=1, majority=0)
"""
super().__init__(pos, model)
self.pos = pos
self.type = agent_type

def step(self):
similar = 0
for neighbor in self.model.grid.iter_neighbors(self.pos, True):
if neighbor.type == self.type:
similar += 1

# If unhappy, move:
if similar < self.model.homophily:
self.model.grid.move_to_empty(self)
else:
self.model.happy += 1


class Schelling(mesa.Model):
"""
Model class for the Schelling segregation model.
"""

def __init__(self, width=20, height=20, density=0.8, minority_pc=0.2, homophily=3):
""" """

self.width = width
self.height = height
self.density = density
self.minority_pc = minority_pc
self.homophily = homophily

self.schedule = mesa.time.RandomActivation(self)
self.grid = mesa.space.SingleGrid(width, height, torus=True)

self.happy = 0
self.datacollector = mesa.DataCollector(
{"happy": "happy"}, # Model-level count of happy agents
# For testing purposes, agent's individual x and y
{"x": lambda a: a.pos[0], "y": lambda a: a.pos[1]},
)

# Set up agents
# We use a grid iterator that returns
# the coordinates of a cell as well as
# its contents. (coord_iter)
for cell in self.grid.coord_iter():
x = cell[1]
y = cell[2]
if self.random.random() < self.density:
if self.random.random() < self.minority_pc:
agent_type = 1
else:
agent_type = 0

agent = SchellingAgent((x, y), self, agent_type)
self.grid.place_agent(agent, (x, y))
self.schedule.add(agent)

self.running = True
self.datacollector.collect(self)

def step(self):
"""
Run one step of the model. If All agents are happy, halt the model.
"""
self.happy = 0 # Reset counter of happy agents
self.schedule.step()
# collect data
self.datacollector.collect(self)

if self.happy == self.schedule.get_agent_count():
self.running = False
2 changes: 2 additions & 0 deletions examples/caching_and_replay/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mesa
git+https://github.com/Logende/mesa-replay@main#egg=Mesa-Replay
22 changes: 22 additions & 0 deletions examples/caching_and_replay/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import mesa

from server import (
canvas_element,
get_happy_agents,
happy_chart,
model_params,
)
from cacheablemodel import CacheableSchelling

# As 'replay' is a simulation model parameter in this example, we need to make it available as such
model_params["replay"] = mesa.visualization.Checkbox("Replay last run?", False)

server = mesa.visualization.ModularServer(
# Note that Schelling was replaced by CacheableSchelling here
CacheableSchelling,
[canvas_element, get_happy_agents, happy_chart],
"Schelling",
model_params,
)

server.launch()
48 changes: 48 additions & 0 deletions examples/caching_and_replay/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""This file was copied over from the original Schelling mesa example."""

import mesa

from model import Schelling


def get_happy_agents(model):
"""
Display a text count of how many happy agents there are.
"""
return f"Happy agents: {model.happy}"


def schelling_draw(agent):
"""
Portrayal Method for canvas
"""
if agent is None:
return
portrayal = {"Shape": "circle", "r": 0.5, "Filled": "true", "Layer": 0}

if agent.type == 0:
portrayal["Color"] = ["#FF0000", "#FF9999"]
portrayal["stroke_color"] = "#00FF00"
else:
portrayal["Color"] = ["#0000FF", "#9999FF"]
portrayal["stroke_color"] = "#000000"
return portrayal


canvas_element = mesa.visualization.CanvasGrid(schelling_draw, 20, 20, 500, 500)
happy_chart = mesa.visualization.ChartModule([{"Label": "happy", "Color": "Black"}])

model_params = {
"height": 20,
"width": 20,
"density": mesa.visualization.Slider("Agent density", 0.6, 0.1, 1.0, 0.1),
"minority_pc": mesa.visualization.Slider("Fraction minority", 0.2, 0.00, 1.0, 0.05),
"homophily": mesa.visualization.Slider("Homophily", 2, 0, 8, 1),
}

server = mesa.visualization.ModularServer(
Schelling,
[canvas_element, get_happy_agents, happy_chart],
"Schelling",
model_params,
)