diff --git a/examples/boltzmann_wealth_model_network/README.md b/examples/boltzmann_wealth_model_network/README.md index cd3bcd8d..a5106471 100644 --- a/examples/boltzmann_wealth_model_network/README.md +++ b/examples/boltzmann_wealth_model_network/README.md @@ -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 @@ -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) diff --git a/examples/boltzmann_wealth_model_network/app.py b/examples/boltzmann_wealth_model_network/app.py new file mode 100644 index 00000000..9e569939 --- /dev/null +++ b/examples/boltzmann_wealth_model_network/app.py @@ -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, + ), +} + + +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", +) diff --git a/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/agent.py b/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/agent.py new file mode 100644 index 00000000..0278850b --- /dev/null +++ b/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/agent.py @@ -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() diff --git a/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/model.py b/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/model.py index fa671ce3..45c52227 100644 --- a/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/model.py +++ b/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/model.py @@ -1,5 +1,8 @@ import mesa import networkx as nx +from mesa.discrete_space import Network + +from .agent import MoneyAgent def compute_gini(model): @@ -13,18 +16,24 @@ 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}, ) list_of_random_nodes = self.random.sample(list(self.G), self.num_agents) @@ -32,7 +41,6 @@ def __init__(self, num_agents=7, num_nodes=10): # 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]) @@ -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() diff --git a/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/server.py b/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/server.py deleted file mode 100644 index 50a019ce..00000000 --- a/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/server.py +++ /dev/null @@ -1,60 +0,0 @@ -import mesa - -from .model import BoltzmannWealthModelNetwork - - -def network_portrayal(G): - # The model ensures there is 0 or 1 agent per node - - portrayal = {} - portrayal["nodes"] = [ - { - "id": node_id, - "size": 3 if agents else 1, - "color": "#CC0000" if not agents or agents[0].wealth == 0 else "#007959", - "label": ( - None - if not agents - else f"Agent:{agents[0].unique_id} Wealth:{agents[0].wealth}" - ), - } - for (node_id, agents) in G.nodes.data("agent") - ] - - portrayal["edges"] = [ - {"id": edge_id, "source": source, "target": target, "color": "#000000"} - for edge_id, (source, target) in enumerate(G.edges) - ] - - return portrayal - - -grid = mesa.visualization.NetworkModule(network_portrayal, 500, 500) -chart = mesa.visualization.ChartModule( - [{"Label": "Gini", "Color": "Black"}], data_collector_name="datacollector" -) - -model_params = { - "num_agents": mesa.visualization.Slider( - "Number of agents", - 7, - 2, - 10, - 1, - description="Choose how many agents to include in the model", - ), - "num_nodes": mesa.visualization.Slider( - "Number of nodes", - 10, - 3, - 12, - 1, - description="Choose how many nodes to include in the model, with at " - "least the same number of agents", - ), -} - -server = mesa.visualization.ModularServer( - BoltzmannWealthModelNetwork, [grid, chart], "Money Model", model_params -) -server.port = 8521 diff --git a/examples/boltzmann_wealth_model_network/requirements.txt b/examples/boltzmann_wealth_model_network/requirements.txt index e9403e6c..2fa9e43f 100644 --- a/examples/boltzmann_wealth_model_network/requirements.txt +++ b/examples/boltzmann_wealth_model_network/requirements.txt @@ -1,5 +1,5 @@ -jupyter -matplotlib -mesa~=2.0 -numpy +mesa +solara networkx +matplotlib +altair \ No newline at end of file diff --git a/examples/boltzmann_wealth_model_network/run.py b/examples/boltzmann_wealth_model_network/run.py deleted file mode 100644 index eb60c904..00000000 --- a/examples/boltzmann_wealth_model_network/run.py +++ /dev/null @@ -1,3 +0,0 @@ -from boltzmann_wealth_model_network.server import server - -server.launch(open_browser=True)