diff --git a/examples/bank_reserves/Readme.md b/examples/bank_reserves/Readme.md index 27570d20..3cae6f5f 100644 --- a/examples/bank_reserves/Readme.md +++ b/examples/bank_reserves/Readme.md @@ -2,14 +2,14 @@ ## Summary -A highly abstracted, simplified model of an economy, with only one type of agent and a single bank representing all banks in an economy. People (represented by circles) move randomly within the grid. If two or more people are on the same grid location, there is a 50% chance that they will trade with each other. If they trade, there is an equal chance of giving the other agent $5 or $2. A positive trade balance will be deposited in the bank as savings. If trading results in a negative balance, the agent will try to withdraw from its savings to cover the balance. If it does not have enough savings to cover the negative balance, it will take out a loan from the bank to cover the difference. The bank is required to keep a certain percentage of deposits as reserves. If run.py is used to run the model, then the percent of deposits the bank is required to retain is a user settable parameter. The amount the bank is able to loan at any given time is a function of the amount of deposits, its reserves, and its current total outstanding loan amount. +A highly abstracted, simplified model of an economy, with only one type of agent and a single bank representing all banks in an economy. People (represented by circles) move randomly within the grid. If two or more people are on the same grid location, there is a 50% chance that they will trade with each other. If they trade, there is an equal chance of giving the other agent $5 or $2. A positive trade balance will be deposited in the bank as savings. If trading results in a negative balance, the agent will try to withdraw from its savings to cover the balance. If it does not have enough savings to cover the negative balance, it will take out a loan from the bank to cover the difference. The bank is required to keep a certain percentage of deposits as reserves. If app.py is used to run the model, then the percent of deposits the bank is required to retain is a user settable parameter. The amount the bank is able to loan at any given time is a function of the amount of deposits, its reserves, and its current total outstanding loan amount. The model demonstrates the following Mesa features: - - MultiGrid for creating shareable space for agents + - CellAgent object inheritance + - OrthogonalMooreGrid for creating shareable space for agents - DataCollector for collecting data on individual model runs - Slider for adjusting initial model parameters - - ModularServer for visualization of agent interaction - - Agent object inheritance + - Solara for visualization of agent interaction - Using a BatchRunner to collect data on multiple combinations of model parameters ## Installation @@ -22,13 +22,13 @@ To install the dependencies use pip and the requirements.txt in this directory. ## Interactive Model Run -To run the model interactively, use `mesa runserver` in this directory: +To run the model interactively, use `solara` 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/), select the model parameters, press Reset, then Start. +Then open your browser to [http://127.0.0.1:8765/](http://127.0.0.1:8765/), select the model parameters, press Reset, then Start. ## Batch Run @@ -43,11 +43,9 @@ To update the parameters to test other parameter sweeps, edit the list of parame ## Files -* ``bank_reserves/random_walker.py``: This defines a class that inherits from the Mesa Agent class. The main purpose is to provide a method for agents to move randomly one cell at a time. -* ``bank_reserves/agents.py``: Defines the People and Bank classes. -* ``bank_reserves/model.py``: Defines the Bank Reserves model and the DataCollector functions. -* ``bank_reserves/server.py``: Sets up the interactive visualization server. -* ``run.py``: Launches a model visualization server. +* ``agents.py``: Defines the People and Bank classes. +* ``model.py``: Defines the Bank Reserves model and the DataCollector functions. +* ``app.py``: Sets up the interactive solara server for visualization. * ``batch_run.py``: Basically the same as model.py, but includes a Mesa BatchRunner. The result of the batch run will be a .csv file with the data from every step of every run. ## Further Reading diff --git a/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/__init__.py b/examples/bank_reserves/__init__.py similarity index 100% rename from examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/__init__.py rename to examples/bank_reserves/__init__.py diff --git a/examples/bank_reserves/bank_reserves/agents.py b/examples/bank_reserves/agents.py similarity index 100% rename from examples/bank_reserves/bank_reserves/agents.py rename to examples/bank_reserves/agents.py diff --git a/examples/bank_reserves/app.py b/examples/bank_reserves/app.py new file mode 100644 index 00000000..ab1950d2 --- /dev/null +++ b/examples/bank_reserves/app.py @@ -0,0 +1,71 @@ +from agents import Person +from mesa.visualization import ( + Slider, + SolaraViz, + make_plot_component, + make_space_component, +) +from model import BankReservesModel + +""" +Citation: +The following code was adapted from server.py at +https://github.com/projectmesa/mesa/blob/main/examples/wolf_sheep/wolf_sheep/server.py +Accessed on: November 2, 2017 +Author of original code: Taylor Mutch +""" + + +def person_portrayal(agent): + if agent is None: + return + + portrayal = {} + + # update portrayal characteristics for each Person object + if isinstance(agent, Person): + color = "tab:blue" + # set agent color based on savings and loans + if agent.savings > agent.model.rich_threshold: + color = "green" + if agent.savings < 10 and agent.loans < 10: + color = "tab:blue" + if agent.loans > 10: + color = "tab:red" + + portrayal["color"] = color + + return portrayal + + +model_params = { + "init_people": Slider( + label="People", + value=25, + min=1, + max=200, + ), + "rich_threshold": Slider( + label="Rich Threshold", + value=10, + min=1, + max=20, + ), + "reserve_percent": Slider( + label="Reserves", + value=50, + min=1, + max=100, + ), +} + +SpacePlot = make_space_component(person_portrayal) +CategoryPlot = make_plot_component(["Rich", "Poor", "Middle Class"]) +model = BankReservesModel() + +page = SolaraViz( + BankReservesModel(), + components=[SpacePlot, CategoryPlot], + model_params=model_params, + name="Bank Reserves", +) diff --git a/examples/bank_reserves/bank_reserves/server.py b/examples/bank_reserves/bank_reserves/server.py deleted file mode 100644 index 6fea0561..00000000 --- a/examples/bank_reserves/bank_reserves/server.py +++ /dev/null @@ -1,90 +0,0 @@ -import mesa - -from .agents import Person -from .model import BankReservesModel - -""" -Citation: -The following code was adapted from server.py at -https://github.com/projectmesa/mesa/blob/main/examples/wolf_sheep/wolf_sheep/server.py -Accessed on: November 2, 2017 -Author of original code: Taylor Mutch -""" - -# The colors here are taken from Matplotlib's tab10 palette -# Green -RICH_COLOR = "#2ca02c" -# Red -POOR_COLOR = "#d62728" -# Blue -MID_COLOR = "#1f77b4" - - -def person_portrayal(agent): - if agent is None: - return - - portrayal = {} - - # update portrayal characteristics for each Person object - if isinstance(agent, Person): - portrayal["Shape"] = "circle" - portrayal["r"] = 0.5 - portrayal["Layer"] = 0 - portrayal["Filled"] = "true" - - color = MID_COLOR - - # set agent color based on savings and loans - if agent.savings > agent.model.rich_threshold: - color = RICH_COLOR - if agent.savings < 10 and agent.loans < 10: - color = MID_COLOR - if agent.loans > 10: - color = POOR_COLOR - - portrayal["Color"] = color - - return portrayal - - -# dictionary of user settable parameters - these map to the model __init__ parameters -model_params = { - "init_people": mesa.visualization.Slider( - "People", 25, 1, 200, description="Initial Number of People" - ), - "rich_threshold": mesa.visualization.Slider( - "Rich Threshold", - 10, - 1, - 20, - description="Upper End of Random Initial Wallet Amount", - ), - "reserve_percent": mesa.visualization.Slider( - "Reserves", - 50, - 1, - 100, - description="Percent of deposits the bank has to hold in reserve", - ), -} - -# set the portrayal function and size of the canvas for visualization -canvas_element = mesa.visualization.CanvasGrid(person_portrayal, 20, 20, 500, 500) - -# map data to chart in the ChartModule -chart_element = mesa.visualization.ChartModule( - [ - {"Label": "Rich", "Color": RICH_COLOR}, - {"Label": "Poor", "Color": POOR_COLOR}, - {"Label": "Middle Class", "Color": MID_COLOR}, - ] -) - -# create instance of Mesa ModularServer -server = mesa.visualization.ModularServer( - BankReservesModel, - [canvas_element, chart_element], - "Bank Reserves Model", - model_params=model_params, -) diff --git a/examples/bank_reserves/batch_run.py b/examples/bank_reserves/batch_run.py index 2903fd59..d0c7ce45 100644 --- a/examples/bank_reserves/batch_run.py +++ b/examples/bank_reserves/batch_run.py @@ -24,24 +24,23 @@ every step of every run. """ -import mesa import pandas as pd -from bank_reserves.model import BankReservesModel +from mesa.batchrunner import batch_run +from model import BankReservesModel def main(): # parameter lists for each parameter to be tested in batch run br_params = { + "width": 20, + "height": 20, "init_people": [25, 100], "rich_threshold": [5, 10], "reserve_percent": 5, } # The existing batch run logic here - data = mesa.batch_run( - BankReservesModel, - br_params, - ) + data = batch_run(model_cls=BankReservesModel, parameters=br_params) br_df = pd.DataFrame(data) br_df.to_csv("BankReservesModel_Data.csv") diff --git a/examples/bank_reserves/bank_reserves/model.py b/examples/bank_reserves/model.py similarity index 95% rename from examples/bank_reserves/bank_reserves/model.py rename to examples/bank_reserves/model.py index 2671980d..0070fa93 100644 --- a/examples/bank_reserves/bank_reserves/model.py +++ b/examples/bank_reserves/model.py @@ -12,10 +12,9 @@ import mesa import numpy as np +from agents import Bank, Person from mesa.experimental.cell_space import OrthogonalMooreGrid -from .agents import Bank, Person - """ If you want to perform a parameter sweep, call batch_run.py instead of run.py. For details see batch_run.py in the same directory as run.py. @@ -97,19 +96,14 @@ class BankReservesModel(mesa.Model): amount. """ - # grid height - grid_h = 20 - # grid width - grid_w = 20 - """init parameters "init_people", "rich_threshold", and "reserve_percent" are all set via Slider""" def __init__( self, - height=grid_h, - width=grid_w, - init_people=2, + height=20, + width=20, + init_people=5, rich_threshold=10, reserve_percent=50, ): @@ -158,7 +152,3 @@ def step(self): self.agents.shuffle_do("step") # collect data self.datacollector.collect(self) - - def run_model(self): - for i in range(self.run_time): - self.step() diff --git a/examples/bank_reserves/requirements.txt b/examples/bank_reserves/requirements.txt index d398c6ed..514c5cfd 100644 --- a/examples/bank_reserves/requirements.txt +++ b/examples/bank_reserves/requirements.txt @@ -1,4 +1,5 @@ -itertools -mesa~=2.0 -numpy -pandas +mesa==3.1.4 +solara +matplotlib +altair +networkx diff --git a/examples/bank_reserves/run.py b/examples/bank_reserves/run.py deleted file mode 100644 index 64a572ee..00000000 --- a/examples/bank_reserves/run.py +++ /dev/null @@ -1,3 +0,0 @@ -from bank_reserves.server import server - -server.launch(open_browser=True) diff --git a/examples/boltzmann_wealth_model_network/README.md b/examples/boltzmann_wealth_model_network/README.md index cd3bcd8d..577c3d5f 100644 --- a/examples/boltzmann_wealth_model_network/README.md +++ b/examples/boltzmann_wealth_model_network/README.md @@ -10,7 +10,6 @@ In this network implementation, agents must be located on a node, with a limit o 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,19 +21,19 @@ 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 diff --git a/examples/boltzmann_wealth_model_network/__init__.py b/examples/boltzmann_wealth_model_network/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/boltzmann_wealth_model_network/agent.py b/examples/boltzmann_wealth_model_network/agent.py new file mode 100644 index 00000000..f44339df --- /dev/null +++ b/examples/boltzmann_wealth_model_network/agent.py @@ -0,0 +1,20 @@ +from mesa.experimental.cell_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/app.py b/examples/boltzmann_wealth_model_network/app.py new file mode 100644 index 00000000..fadefc1b --- /dev/null +++ b/examples/boltzmann_wealth_model_network/app.py @@ -0,0 +1,50 @@ +from mesa.visualization import ( + Slider, + SolaraViz, + make_plot_component, + make_space_component, +) +from model import BoltzmannWealthModelNetwork + + +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=[GiniPlot, SpacePlot], + model_params=model_params, + name="Boltzmann_wealth_model_network", +) 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/boltzmann_wealth_model_network/model.py b/examples/boltzmann_wealth_model_network/model.py similarity index 53% rename from examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/model.py rename to examples/boltzmann_wealth_model_network/model.py index fa671ce3..d26c45df 100644 --- a/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/model.py +++ b/examples/boltzmann_wealth_model_network/model.py @@ -1,5 +1,8 @@ import mesa import networkx as nx +from mesa.experimental.cell_space import Network + +from .agent import MoneyAgent def compute_gini(model): @@ -13,18 +16,15 @@ 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 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 +32,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 +42,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/requirements.txt b/examples/boltzmann_wealth_model_network/requirements.txt index e9403e6c..64d4aebd 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==3.1.4 +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)