diff --git a/examples/boltzmann_wealth_model_network/README.md b/examples/boltzmann_wealth_model_network/README.md index cd3bcd8d..c382fac7 100644 --- a/examples/boltzmann_wealth_model_network/README.md +++ b/examples/boltzmann_wealth_model_network/README.md @@ -10,31 +10,29 @@ 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 -To install the dependencies use pip and the requirements.txt in this directory. e.g. +To install the dependencies use `pip` to install `mesa[rec]` -``` - $ pip install -r requirements.txt +```bash + $ pip install mesa[rec] ``` ## How to Run -To run the model interactively, run ``mesa runserver`` in this directory. e.g. +To run the model interactively, run ``solara run`` in this directory. e.g. -``` - $ mesa runserver +```bash + $ 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://localhost:8765/](http://localhost: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. +* ``model.py``: Contains creation of agents, the network, and management of agent execution. +* ``agents.py``: Contains logic for giving money, and moving on the network. +* ``app.py``: Contains the code for the interactive Solara visualization. ## Further Reading diff --git a/examples/boltzmann_wealth_model_network/app.py b/examples/boltzmann_wealth_model_network/app.py new file mode 100644 index 00000000..cd4dbabe --- /dev/null +++ b/examples/boltzmann_wealth_model_network/app.py @@ -0,0 +1,96 @@ +from boltzmann_wealth_model_network.model import BoltzmannWealthModelNetwork +from mesa.mesa_logging import INFO, log_to_stderr +from mesa.visualization import ( + SolaraViz, + make_plot_component, + make_space_component, +) + +log_to_stderr(INFO) + + +# Tells Solara how to draw each agent. +def agent_portrayal(agent): + return { + "color": agent.wealth, # using a colormap to convert wealth to color + "size": 50, + } + + +model_params = { + "seed": { + "type": "InputText", + "value": 42, + "label": "Random seed", + }, + "n": { + "type": "SliderInt", + "value": 7, + "label": "Number of agents", + "min": 2, + "max": 10, + "step": 1, + # "description": "Choose how many agents to include in the model", + }, + "num_nodes": { + "type": "SliderInt", + "value": 10, + "label": "Number of nodes", + "min": 3, + "max": 12, + "step": 1, + # "description": "Choose how many nodes to include in the model, with at least the same number of agents", + }, +} + + +def post_process(ax): + ax.get_figure().colorbar(ax.collections[0], label="wealth", ax=ax) + + +# Create initial model instance +money_model = BoltzmannWealthModelNetwork(n=7, num_nodes=10, seed=42) + +# Create visualization elements. The visualization elements are Solara +# components that receive the model instance as a "prop" and display it in a +# certain way. Under the hood these are just classes that receive the model +# instance. You can also author your own visualization elements, which can also +# be functions that receive the model instance and return a valid Solara +# component. + +SpaceGraph = make_space_component( + agent_portrayal, cmap="viridis", vmin=0, vmax=10, post_process=post_process +) +GiniPlot = make_plot_component("Gini") + +# Create the SolaraViz page. This will automatically create a server and display +# the visualization elements in a web browser. +# +# Display it using the following command in the example directory: +# solara run app.py +# It will automatically update and display any changes made to this file. + +page = SolaraViz( + money_model, + components=[SpaceGraph, GiniPlot], + model_params=model_params, + name="Boltzmann Wealth Model: Network", +) +page # noqa + + +# In a notebook environment, we can also display the visualization elements +# directly. +# +# SpaceGraph(model1) +# GiniPlot(model1) + +# The plots will be static. If you want to pick up model steps, +# you have to make the model reactive first +# +# reactive_model = solara.reactive(model1) +# SpaceGraph(reactive_model) + +# In a different notebook block: +# +# reactive_model.value.step() diff --git a/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/agents.py b/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/agents.py new file mode 100644 index 00000000..6553f2ef --- /dev/null +++ b/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/agents.py @@ -0,0 +1,36 @@ +from mesa.discrete_space import CellAgent + + +class MoneyAgent(CellAgent): + """An agent with fixed initial wealth. + + Each agent starts with 1 unit of wealth and can give 1 unit to other agents + if they occupy the same cell. + + Attributes: + wealth (int): The agent's current wealth (starts at 1) + """ + + def __init__(self, model): + """Create a new agent. + + Args: + model (Model): The model instance that contains the agent + """ + super().__init__(model) + self.wealth = 1 + + def give_money(self): + neighbors = [agent for agent in self.cell.neighborhood.agents if agent != 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/model.py b/examples/boltzmann_wealth_model_network/boltzmann_wealth_model_network/model.py index fa671ce3..62f69475 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,72 +1,45 @@ -import mesa import networkx as nx +from mesa import Model +from mesa.datacollection import DataCollector +from mesa.discrete_space import Network +from .agents import MoneyAgent -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 BoltzmannWealthModelNetwork(mesa.Model): +class BoltzmannWealthModelNetwork(Model): """A model with some number of agents.""" - def __init__(self, num_agents=7, num_nodes=10): - super().__init__() - self.num_agents = num_agents + def __init__(self, n=7, num_nodes=10, seed=None): + super().__init__(seed=seed) + + self.num_agents = n 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, capacity=1, random=self.random) - self.datacollector = mesa.DataCollector( - model_reporters={"Gini": compute_gini}, - agent_reporters={"Wealth": lambda _: _.wealth}, + # Set up data collection + self.datacollector = DataCollector( + model_reporters={"Gini": self.compute_gini}, + agent_reporters={"Wealth": "wealth"}, ) + # Create agents; add the agent to a random node + # TODO: change to MoneyAgent.create_agents(...) 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]) self.running = True self.datacollector.collect(self) 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() + self.agents.shuffle_do("step") # Activate all agents in random order + self.datacollector.collect(self) # collect data + + def compute_gini(self): + agent_wealths = [agent.wealth for agent in self.agents] + x = sorted(agent_wealths) + N = self.num_agents + B = sum(xi * (N - i) for i, xi in enumerate(x)) / (N * sum(x)) + return 1 + (1 / N) - 2 * B 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 deleted file mode 100644 index e9403e6c..00000000 --- a/examples/boltzmann_wealth_model_network/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -jupyter -matplotlib -mesa~=2.0 -numpy -networkx 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) diff --git a/gis/agents_and_networks/src/space/utils.py b/gis/agents_and_networks/src/space/utils.py index 2f458845..7608086b 100644 --- a/gis/agents_and_networks/src/space/utils.py +++ b/gis/agents_and_networks/src/space/utils.py @@ -57,7 +57,7 @@ def _segmented(linestring: LineString) -> list[LineString]: # reference: https://gis.stackexchange.com/questions/367228/using-shapely-interpolate-to-evenly-re-sample-points-on-a-linestring-geodatafram def redistribute_vertices(geom, distance): if isinstance(geom, LineString): - if (num_vert := int(round(geom.length / distance))) == 0: + if (num_vert := round(geom.length / distance)) == 0: num_vert = 1 return LineString( [ diff --git a/rl/Tutorials.ipynb b/rl/Tutorials.ipynb index 7e01a1d8..aeec0ea8 100644 --- a/rl/Tutorials.ipynb +++ b/rl/Tutorials.ipynb @@ -26,7 +26,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -52,14 +52,14 @@ ], "source": [ "# ### Step 1: Importing the Necessary Modules\n", - "# To begin, let’s import the required modules for the Epstein Civil Violence model:\n", + "# To begin, let's import the required modules for the Epstein Civil Violence model:\n", "\n", "from epstein_civil_violence.model import EpsteinCivilViolenceRL\n", "from epstein_civil_violence.server import run_model\n", "from epstein_civil_violence.train_config import config\n", "from train import train_model\n", "\n", - "# Here’s a breakdown of the modules:\n", + "# Here's a breakdown of the modules:\n", "# - `EpsteinCivilViolenceRL`: Contains the core model and environment.\n", "# - `run_model`: Configures and runs the model for inference.\n", "# - `config`: Defines the parameters for training the model.\n", @@ -93,7 +93,7 @@ "# ### Step 3: Running the Environment with Random Actions\n", "\n", "# To get a feel for how the environment operates, let's run it for a few steps using random actions.\n", - "# We’ll sample the action space for these actions:\n", + "# We'll sample the action space for these actions:\n", "\n", "for _ in range(10):\n", " action_dict = {}\n",