Skip to content

Update boltzmann example for mesa 3.1.5 #247

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
18 changes: 7 additions & 11 deletions examples/boltzmann_wealth_model_network/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@

This is the same Boltzmann Wealth Model, but with a network grid implementation.

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).
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/latest/tutorials/0_first_model.html).

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

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.

JavaScript library used in this example to render the network: [sigma.js](http://sigmajs.org/).

## Installation

Expand All @@ -22,25 +21,22 @@ To install the dependencies use pip and the requirements.txt in this directory.

## How to Run

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

```
$ mesa runserver
$ solara run app.py
```

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

## Files

* ``run.py``: Launches a model visualization server.
* ``model.py``: Contains the agent class, and the overall model class.
* ``server.py``: Defines classes for visualizing the model (network layout) in the browser via Mesa's modular server, and instantiates a visualization server.
* ``agent.py``: Defines the agent class.
* ``model.py``: Defines the model class.
* ``app.py`` : Defines the visualization and spins up a solara server.

## 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)
Expand Down
53 changes: 53 additions & 0 deletions examples/boltzmann_wealth_model_network/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from boltzmann_wealth_model_network.model import BoltzmannWealthModelNetwork
from mesa.visualization import (
Slider,
SolaraViz,
make_plot_component,
make_space_component,
)


def agent_portrayal(agent):
return {
"color": "red" if agent.wealth == 0 else "green",
"size": 30,
}


model_params = {
"num_agents": Slider(
label="Number of agents",
value=10,
min=5,
max=20,
step=1,
),
"num_nodes": Slider(
label="Number of nodes",
value=10,
min=5,
max=20,
step=1,
),
}
Comment on lines +17 to +32
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that you handled the case where the number of agents is more than the number of nodes in the code (as one node only supports one agent) by ignoring the given value of number of nodes, which I think is a good approach. I would prefer that you let the user know that you are doing this though, by butting some sort of warning when the user does set the number of nodes<number of agents. I am saying this as the visualization will be counter intuitive if the user hasn't read the code. For instance, I set the number of nodes to 8 here but the visualization shows 18
image

I would prefer it if the warning were given in the GUI, but if that's too hard I think a terminal message should do it

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes seems very natural! Just added a simple terminal message.
image



def post_process_lineplot(ax):
ax.set_ylim(ymin=0)
ax.set_xlim(xmin=0)


SpacePlot = make_space_component(agent_portrayal)
GiniPlot = make_plot_component("Gini", post_process=post_process_lineplot)

model = BoltzmannWealthModelNetwork()

page = SolaraViz(
model,
components=[
SpacePlot,
GiniPlot,
],
model_params=model_params,
name="Boltzmann_wealth_model_network",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from mesa.discrete_space import CellAgent


class MoneyAgent(CellAgent):
"""An agent with fixed initial wealth"""

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

def give_money(self):
neighbours = list(self.cell.neighborhood.agents)
if len(neighbours) > 0:
other = self.random.choice(neighbours)
other.wealth += 1
self.wealth -= 1

def step(self):
if self.wealth > 0:
self.give_money()
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import mesa
import networkx as nx
from mesa.discrete_space import Network
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be "mesa.discrete_space.network import Network"


from .agent import MoneyAgent


def compute_gini(model):
Expand All @@ -13,26 +16,31 @@ def compute_gini(model):
class BoltzmannWealthModelNetwork(mesa.Model):
"""A model with some number of agents."""

def __init__(self, num_agents=7, num_nodes=10):
def __init__(self, num_agents=10, num_nodes=10):
super().__init__()
self.num_agents = num_agents
self.num_nodes = num_nodes if num_nodes >= self.num_agents else self.num_agents
if self.num_agents > num_nodes:
self.num_nodes = self.num_agents
print("""
╔═══════════════════════════════════ Warning ════════════════════════════════════════╗
║ Number of agents > Number of nodes. ║
║ Since each node can hold only one agent, so num_nodes has been set to num_agents. ║
╚════════════════════════════════════════════════════════════════════════════════════╝
""")
else:
self.num_nodes = num_nodes
self.G = nx.erdos_renyi_graph(n=self.num_nodes, p=0.5)
self.grid = mesa.experimental.cell_space.Network(
self.G, random=self.random, capacity=1
)
self.grid = Network(self.G, random=self.random, capacity=1)

self.datacollector = mesa.DataCollector(
model_reporters={"Gini": compute_gini},
agent_reporters={"Wealth": lambda _: _.wealth},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that agent_reporters is not used explicitly in the code, but do you think we should just leave it in if the user wants to do post-simulation analysis?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I feel it should be the user's choice

)

list_of_random_nodes = self.random.sample(list(self.G), self.num_agents)

# Create agents
for position in list_of_random_nodes:
agent = MoneyAgent(self)

# Add the agent to a random node
agent.move_to(self.grid[position])

Expand All @@ -43,30 +51,3 @@ def step(self):
self.agents.shuffle_do("step")
# collect data
self.datacollector.collect(self)

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


class MoneyAgent(mesa.experimental.cell_space.CellAgent):
"""An agent with fixed initial wealth."""

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

def give_money(self):
neighbors = [agent for agent in self.cell.neighborhood.agents if not self]
if len(neighbors) > 0:
other = self.random.choice(neighbors)
other.wealth += 1
self.wealth -= 1

def step(self):
empty_neighbors = [cell for cell in self.cell.neighborhood if cell.is_empty]
if empty_neighbors:
self.cell = self.random.choice(empty_neighbors)

if self.wealth > 0:
self.give_money()

This file was deleted.

8 changes: 4 additions & 4 deletions examples/boltzmann_wealth_model_network/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
jupyter
matplotlib
mesa~=2.0
numpy
mesa
solara
networkx
matplotlib
altair
3 changes: 0 additions & 3 deletions examples/boltzmann_wealth_model_network/run.py

This file was deleted.

Loading