Skip to content

Commit

Permalink
Add back original boltzmann and schelling
Browse files Browse the repository at this point in the history
  • Loading branch information
rht authored and tpike3 committed Jun 29, 2023
1 parent 8afbd42 commit 05857c7
Show file tree
Hide file tree
Showing 16 changed files with 976 additions and 1 deletion.
51 changes: 51 additions & 0 deletions examples/boltzmann_wealth_model/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Boltzmann Wealth Model (Tutorial)

## Summary

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), with the completed code.

If you want to go over the step-by-step tutorial, please go and run the [Jupyter Notebook](https://github.com/projectmesa/mesa/blob/main/docs/tutorials/intro_tutorial.ipynb). The code here runs the finalized code in the last cells directly.

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.

## How to Run

To follow the tutorial example, launch the Jupyter Notebook and run the code in ``Introduction to Mesa Tutorial Code.ipynb`` which you can find in the main mesa repo [here](https://github.com/projectmesa/mesa/blob/main/docs/tutorials/intro_tutorial.ipynb)

To launch the interactive server, as described in the [last section of the tutorial](https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html#adding-visualization), run:

```
$ python server.py
```

Make sure to install the requirements first:

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

If your browser doesn't open automatically, point it to [http://127.0.0.1:8521/](http://127.0.0.1:8521/). When the visualization loads, press Reset, then Run.


## Files

* ``boltzmann_wealth_model/model.py``: Final version of the model.
* ``boltzmann_wealth_model/server.py``: Code for the interactive visualization.
* ``run.py``: Launches the server.

## Optional

* ``boltzmann_wealth_model/app.py``: can be used to run the simulation via the streamlit interface.
* For this some additional packages like ``streamlit`` and ``altair`` needs to be installed.
* Once installed, the app can be opened in the browser using : ``streamlit run app.py``

## Further Reading

The full tutorial describing how the model is built can be found at:
https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html

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:

[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)

[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)
113 changes: 113 additions & 0 deletions examples/boltzmann_wealth_model/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import time

import altair as alt
import pandas as pd
import streamlit as st
from boltzmann_wealth_model.model import BoltzmannWealthModel

model = st.title("Boltzman Wealth Model")
num_agents = st.slider(
"Choose how many agents to include in the model",
min_value=1,
max_value=100,
value=50,
)
num_ticks = st.slider(
"Select number of Simulation Runs", min_value=1, max_value=100, value=50
)
height = st.slider("Select Grid Height", min_value=10, max_value=100, step=10, value=15)
width = st.slider("Select Grid Width", min_value=10, max_value=100, step=10, value=20)
model = BoltzmannWealthModel(num_agents, height, width)


status_text = st.empty()
run = st.button("Run Simulation")


if run:
tick = time.time()
step = 0
# init grid
df_grid = pd.DataFrame()
df_gini = pd.DataFrame({"step": [0], "gini": [-1]})
for x in range(width):
for y in range(height):
df_grid = pd.concat(
[df_grid, pd.DataFrame({"x": [x], "y": [y], "agent_count": 0})],
ignore_index=True,
)

heatmap = (
alt.Chart(df_grid)
.mark_point(size=100)
.encode(x="x", y="y", color=alt.Color("agent_count"))
.interactive()
.properties(width=800, height=600)
)

line = (
alt.Chart(df_gini)
.mark_line(point=True)
.encode(x="step", y="gini")
.properties(width=800, height=600)
)

# init progress bar
my_bar = st.progress(0, text="Simulation Progress") # progress
placeholder = st.empty()
st.subheader("Agent Grid")
chart = st.altair_chart(heatmap)
st.subheader("Gini Values")
line_chart = st.altair_chart(line)

color_scale = alt.Scale(
domain=[0, 1, 2, 3, 4], range=["red", "cyan", "white", "white", "blue"]
)
for i in range(num_ticks):
model.step()
my_bar.progress((i / num_ticks), text="Simulation progress")
placeholder.text("Step = %d" % i)
for cell in model.grid.coord_iter():
cell_content, x, y = cell
agent_count = len(cell_content)
selected_row = df_grid[(df_grid["x"] == x) & (df_grid["y"] == y)]
df_grid.loc[
selected_row.index, "agent_count"
] = agent_count # random.choice([1,2])

df_gini = pd.concat(
[
df_gini,
pd.DataFrame(
{"step": [i], "gini": [model.datacollector.model_vars["Gini"][i]]}
),
]
)
# st.table(df_grid)
heatmap = (
alt.Chart(df_grid)
.mark_circle(size=100)
.encode(x="x", y="y", color=alt.Color("agent_count", scale=color_scale))
.interactive()
.properties(width=800, height=600)
)
chart.altair_chart(heatmap)

line = (
alt.Chart(df_gini)
.mark_line(point=True)
.encode(x="step", y="gini")
.properties(width=800, height=600)
)
line_chart.altair_chart(line)

time.sleep(0.01)

tock = time.time()
st.success(f"Simulation completed in {tock - tick:.2f} secs")

# st.subheader('Agent Grid')
# fig = px.imshow(agent_counts,labels={'color':'Agent Count'})
# st.plotly_chart(fig)
# st.subheader('Gini value over sim ticks (Plotly)')
# chart = st.line_chart(model.datacollector.model_vars['Gini'])
Empty file.
76 changes: 76 additions & 0 deletions examples/boltzmann_wealth_model/boltzmann_wealth_model/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import mesa


def compute_gini(model):
agent_wealths = [agent.wealth for agent in model.schedule.agents]
x = sorted(agent_wealths)
N = model.num_agents
B = sum(xi * (N - i) for i, xi in enumerate(x)) / (N * sum(x))
return 1 + (1 / N) - 2 * B


class BoltzmannWealthModel(mesa.Model):
"""A simple model of an economy where agents exchange currency at random.
All the agents begin with one unit of currency, and each time step can give
a unit of currency to another agent. Note how, over time, this produces a
highly skewed distribution of wealth.
"""

def __init__(self, N=100, width=10, height=10):
self.num_agents = N
self.grid = mesa.space.MultiGrid(width, height, True)
self.schedule = mesa.time.RandomActivation(self)
self.datacollector = mesa.DataCollector(
model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"}
)
# Create agents
for i in range(self.num_agents):
a = MoneyAgent(i, self)
self.schedule.add(a)
# Add the agent to a random grid cell
x = self.random.randrange(self.grid.width)
y = self.random.randrange(self.grid.height)
self.grid.place_agent(a, (x, y))

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

def step(self):
self.schedule.step()
# collect data
self.datacollector.collect(self)

def run_model(self, n):
for i in range(n):
self.step()


class MoneyAgent(mesa.Agent):
"""An agent with fixed initial wealth."""

def __init__(self, unique_id, model):
super().__init__(unique_id, model)
self.wealth = 1

def move(self):
possible_steps = self.model.grid.get_neighborhood(
self.pos, moore=True, include_center=False
)
new_position = self.random.choice(possible_steps)
self.model.grid.move_agent(self, new_position)

def give_money(self):
cellmates = self.model.grid.get_cell_list_contents([self.pos])
cellmates.pop(
cellmates.index(self)
) # Ensure agent is not giving money to itself
if len(cellmates) > 0:
other = self.random.choice(cellmates)
other.wealth += 1
self.wealth -= 1

def step(self):
self.move()
if self.wealth > 0:
self.give_money()
40 changes: 40 additions & 0 deletions examples/boltzmann_wealth_model/boltzmann_wealth_model/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import mesa

from .model import BoltzmannWealthModel


def agent_portrayal(agent):
portrayal = {"Shape": "circle", "Filled": "true", "r": 0.5}

if agent.wealth > 0:
portrayal["Color"] = "red"
portrayal["Layer"] = 0
else:
portrayal["Color"] = "grey"
portrayal["Layer"] = 1
portrayal["r"] = 0.2
return portrayal


grid = mesa.visualization.CanvasGrid(agent_portrayal, 10, 10, 500, 500)
chart = mesa.visualization.ChartModule(
[{"Label": "Gini", "Color": "#0000FF"}], data_collector_name="datacollector"
)

model_params = {
"N": mesa.visualization.Slider(
"Number of agents",
100,
2,
200,
1,
description="Choose how many agents to include in the model",
),
"width": 10,
"height": 10,
}

server = mesa.visualization.ModularServer(
BoltzmannWealthModel, [grid, chart], "Money Model", model_params
)
server.port = 8521
1 change: 1 addition & 0 deletions examples/boltzmann_wealth_model/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mesa~=1.1
3 changes: 3 additions & 0 deletions examples/boltzmann_wealth_model/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from boltzmann_wealth_model.server import server

server.launch(open_browser=True)
49 changes: 49 additions & 0 deletions examples/schelling/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Schelling Segregation Model

## Summary

The Schelling segregation model is a classic agent-based model, demonstrating how even a mild preference for similar neighbors can lead to a much higher degree of segregation than we would intuitively expect. The model consists of agents on a square grid, where each grid cell can contain at most one agent. Agents come in two colors: red and blue. They are happy if a certain number of their eight possible neighbors are of the same color, and unhappy otherwise. Unhappy agents will pick a random empty cell to move to each step, until they are happy. The model keeps running until there are no unhappy agents.

By default, the number of similar neighbors the agents need to be happy is set to 3. That means the agents would be perfectly happy with a majority of their neighbors being of a different color (e.g. a Blue agent would be happy with five Red neighbors and three Blue ones). Despite this, the model consistently leads to a high degree of segregation, with most agents ending up with no neighbors of a different color.

## 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.

To view and run some example model analyses, launch the IPython Notebook and open ``analysis.ipynb``. Visualizing the analysis also requires [matplotlib](http://matplotlib.org/).

## How to Run without the GUI

To run the model with the grid displayed as an ASCII text, run `python run_ascii.py` in this directory.

## Files

* ``run.py``: Launches a model visualization server.
* ``run_ascii.py``: Run the model in text mode.
* ``schelling.py``: Contains the agent class, and the overall model class.
* ``server.py``: Defines classes for visualizing the model in the browser via Mesa's modular server, and instantiates a visualization server.
* ``analysis.ipynb``: Notebook demonstrating how to run experiments and parameter sweeps on the model.

## Further Reading

Schelling's original paper describing the model:

[Schelling, Thomas C. Dynamic Models of Segregation. Journal of Mathematical Sociology. 1971, Vol. 1, pp 143-186.](https://www.stat.berkeley.edu/~aldous/157/Papers/Schelling_Seg_Models.pdf)

An interactive, browser-based explanation and implementation:

[Parable of the Polygons](http://ncase.me/polygons/), by Vi Hart and Nicky Case.
Empty file added examples/schelling/__init__.py
Empty file.
Loading

0 comments on commit 05857c7

Please sign in to comment.