import mesa
import solara
from matplotlib.figure import Figure
from mesa.visualization.utils import update_counter
from mesa.visualization import (
SolaraViz,
make_plot_component,
make_space_component
)
def compute_gini(model):
agent_wealths = [agent.wealth for agent in model.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 MoneyAgent(mesa.Agent):
"""An agent with fixed initial wealth."""
def __init__(self, model):
"""initialize a MoneyAgent instance.
Args:
model: A model instance
"""
super().__init__(model)
self.wealth = 1
def move(self):
"""move to a random neighboring cell."""
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):
"""give money to another agent in the same gridcell."""
cellmates = self.model.grid.get_cell_list_contents([self.pos])
if len(cellmates) > 1:
other = self.random.choice(cellmates)
other.wealth += 1
self.wealth -= 1
def step(self):
"""do one step of the agent."""
self.move()
if self.wealth > 0:
self.give_money()
class MoneyModel(mesa.Model):
"""A model with some number of agents."""
def __init__(self, n=10, width=10, height=10, seed=None):
"""Initialize a MoneyModel instance.
Args:
N: The number of agents.
width: width of the grid.
height: Height of the grid.
"""
super().__init__(seed=seed)
self.num_agents = n
self.grid = mesa.space.MultiGrid(width, height, True)
# Create agents
agents = MoneyAgent.create_agents(model=self, n=n)
# Create x and y positions for agents
x = self.rng.integers(0, self.grid.width, size=(n,))
y = self.rng.integers(0, self.grid.height, size=(n,))
for a, i, j in zip(agents, x, y):
# Add the agent to a random grid cell
self.grid.place_agent(a, (i, j))
self.datacollector = mesa.DataCollector(
model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"}
)
self.datacollector.collect(self)
def step(self):
"""do one step of the model"""
self.agents.shuffle_do("step")
self.datacollector.collect(self)
def agent_portrayal(agent):
size = 10
color = "tab:red"
if agent.wealth > 0:
size = 50
color = "tab:blue"
return {"size": size, "color": color}
model_params = {
"n": {
"type": "SliderInt",
"value": 50,
"label": "Number of agents:",
"min": 10,
"max": 100,
"step": 1,
},
"width": 10,
"height": 10,
}
@solara.component
def Histogram(model):
update_counter.get() # This is required to update the counter
# Note: you must initialize a figure using this method instead of
# plt.figure(), for thread safety purpose
fig = Figure()
ax = fig.subplots()
wealth_vals = [agent.wealth for agent in model.agents]
# Note: you have to use Matplotlib's OOP API instead of plt.hist
# because plt.hist is not thread-safe.
ax.hist(wealth_vals, bins=10)
solara.FigureMatplotlib(fig)
# Create initial model instance
money_model = MoneyModel(n=50, width=10, height=10)
SpaceGraph = make_space_component(agent_portrayal)
GiniPlot = make_plot_component("Gini")
page = SolaraViz(
money_model,
components=[SpaceGraph, GiniPlot, Histogram],
model_params=model_params,
name="Boltzmann Wealth Model",
)
# This is required to render the visualization in the Jupyter notebook
page