diff --git a/.gitignore b/.gitignore index 995731ed2..db1ebead6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,87 +1,6 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] - -# C extensions -*.so - -# Distribution / packaging -.Python -env/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -*.egg-info/ -.installed.cfg -*.egg - -# ignore RL file - users download model on own -rl - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*,cover - -# Translations -*.mo -*.pot - -# Django stuff: -*.log - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter and iPython notebook checkpoints -*.ipynb_checkpoints -*.virtual_documents - -# Spyder app workspace config file -.spyderworkspace - -# PyCharm environment files -.idea/ - -# VSCode environment files -.vscode/ -*.code-workspace - -# Apple OSX -*.DS_Store - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Virtual environment +.venv +.venv313 +.venv313/ venv/ - -examples/caching_and_replay/my_cache_file_path.cache +__pycache__/ +*.pyc diff --git a/examples/charts/app.py b/examples/charts/app.py new file mode 100644 index 000000000..b3d05a5c1 --- /dev/null +++ b/examples/charts/app.py @@ -0,0 +1,60 @@ +import solara +from charts.agents import Person +from charts.model import Charts +from mesa.visualization import SolaraViz, make_plot_component, make_space_component +from mesa.visualization.components import AgentPortrayalStyle +from mesa.visualization.user_param import Slider + +# Colors - same palette as the original server.py (Matplotlib tab10) +RICH_COLOR = "#2ca02c" # green +POOR_COLOR = "#d62728" # red +MID_COLOR = "#1f77b4" # blue + + +def agent_portrayal(agent): + """ + Returns AgentPortrayalStyle (Mesa 3.x) instead of a dict. + Color encodes wealth class: + green -> savings > rich_threshold (rich) + red -> loans > 10 (poor) + blue -> everything else (middle class) + """ + if not isinstance(agent, Person): + return AgentPortrayalStyle() + + color = MID_COLOR + + if agent.savings > agent.model.rich_threshold: + color = RICH_COLOR + if agent.loans > 10: + color = POOR_COLOR + + return AgentPortrayalStyle(color=color, size=20, marker="o") + + +model_params = { + "init_people": Slider(label="People", value=25, min=1, max=200, step=1), + "rich_threshold": Slider(label="Rich Threshold", value=10, min=1, max=20, step=1), + "reserve_percent": Slider(label="Reserves (%)", value=50, min=1, max=100, step=1), +} + +SpaceGraph = make_space_component(agent_portrayal) + +WealthClassChart = make_plot_component( + {"Rich": RICH_COLOR, "Poor": POOR_COLOR, "Middle Class": MID_COLOR} +) + +MoneySupplyChart = make_plot_component( + {"Savings": "#9467bd", "Wallets": "#8c564b", "Money": "#17becf", "Loans": "#e377c2"} +) + +# Wrap the instance in solara.Reactive to prevent render loops. +# SolaraViz expects Model | solara.Reactive[Model]. +model = solara.Reactive(Charts()) + +page = SolaraViz( + model, + components=[SpaceGraph, WealthClassChart, MoneySupplyChart], + model_params=model_params, + name="Mesa Charts - Bank Reserves", +) diff --git a/examples/charts/charts/server.py b/examples/charts/charts/server.py deleted file mode 100644 index 3096f422d..000000000 --- a/examples/charts/charts/server.py +++ /dev/null @@ -1,112 +0,0 @@ -import mesa -from charts.agents import Person -from charts.model import Charts - -""" -Citation: -The following code was adapted from server.py at -https://github.com/mesa/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 -line_chart = mesa.visualization.ChartModule( - [ - {"Label": "Rich", "Color": RICH_COLOR}, - {"Label": "Poor", "Color": POOR_COLOR}, - {"Label": "Middle Class", "Color": MID_COLOR}, - ] -) - -model_bar = mesa.visualization.BarChartModule( - [ - {"Label": "Rich", "Color": RICH_COLOR}, - {"Label": "Poor", "Color": POOR_COLOR}, - {"Label": "Middle Class", "Color": MID_COLOR}, - ] -) - -agent_bar = mesa.visualization.BarChartModule( - [{"Label": "Wealth", "Color": MID_COLOR}], - scope="agent", - sorting="ascending", - sort_by="Wealth", -) - -pie_chart = mesa.visualization.PieChartModule( - [ - {"Label": "Rich", "Color": RICH_COLOR}, - {"Label": "Middle Class", "Color": MID_COLOR}, - {"Label": "Poor", "Color": POOR_COLOR}, - ] -) - -# create instance of Mesa ModularServer -server = mesa.visualization.ModularServer( - Charts, - [canvas_element, line_chart, model_bar, agent_bar, pie_chart], - "Mesa Charts", - model_params=model_params, -) diff --git a/examples/charts/run.py b/examples/charts/run.py index b767a516f..2cd703bb9 100644 --- a/examples/charts/run.py +++ b/examples/charts/run.py @@ -1,3 +1,6 @@ -from charts.server import server +import subprocess +import sys +from pathlib import Path -server.launch(open_browser=True) +app_path = Path(__file__).parent / "app.py" +subprocess.run([sys.executable, "-m", "solara", "run", str(app_path)], check=False) diff --git a/examples/hex_ant/app.py b/examples/hex_ant/app.py index 952f391f9..b2636f201 100644 --- a/examples/hex_ant/app.py +++ b/examples/hex_ant/app.py @@ -1,10 +1,9 @@ import matplotlib.pyplot as plt +from agent import AntState from matplotlib.colors import LinearSegmentedColormap, ListedColormap from mesa.visualization import SolaraViz, make_space_component from mesa.visualization.components import PropertyLayerStyle - -from .agent import AntState -from .model import AntForaging +from model import AntForaging plt.rcParams["figure.figsize"] = (10, 10) diff --git a/examples/hex_ant/model.py b/examples/hex_ant/model.py index 96998d22b..15e1b8a75 100644 --- a/examples/hex_ant/model.py +++ b/examples/hex_ant/model.py @@ -1,7 +1,10 @@ import mesa -from mesa.discrete_space import HexGrid -from .agent import Ant +try: + from .agent import Ant +except ImportError: + from agent import Ant +from mesa.discrete_space import HexGrid class AntForaging(mesa.Model): @@ -57,9 +60,9 @@ def _init_environment(self): # Create the Nest in the center center = (self.grid.width // 2, self.grid.height // 2) # Spike the 'home' pheromone at the nest so ants can find it initially - self.grid.pheromone_home[center] = 1.0 + self.grid.pheromone_home.data[center] = 1.0 # Mark the home location - self.grid.home[center] = 1 + self.grid.home.data[center] = 1 # Scatter some Food Sources # Create 3 big clusters of food diff --git a/examples/hex_snowflake/app.py b/examples/hex_snowflake/app.py new file mode 100644 index 000000000..4fcce108f --- /dev/null +++ b/examples/hex_snowflake/app.py @@ -0,0 +1,21 @@ +from cell import Cell +from mesa.visualization import SolaraViz, make_space_component +from model import HexSnowflake + + +def agent_portrayal(agent): + if agent is None: + return + + if isinstance(agent, Cell): + color = "tab:green" if agent.state == Cell.ALIVE else "tab:grey" + return {"color": color, "size": 20} + + +model = HexSnowflake() + +page = SolaraViz( + model, + components=[make_space_component(agent_portrayal=agent_portrayal)], + name="Hex Snowflake", +) diff --git a/examples/hex_snowflake/hex_snowflake/cell.py b/examples/hex_snowflake/cell.py similarity index 100% rename from examples/hex_snowflake/hex_snowflake/cell.py rename to examples/hex_snowflake/cell.py diff --git a/examples/hex_snowflake/hex_snowflake/portrayal.py b/examples/hex_snowflake/hex_snowflake/portrayal.py deleted file mode 100644 index 621baf79c..000000000 --- a/examples/hex_snowflake/hex_snowflake/portrayal.py +++ /dev/null @@ -1,17 +0,0 @@ -def portrayCell(cell): - """This function is registered with the visualization server to be called - each tick to indicate how to draw the cell in its current state. - :param cell: the cell in the simulation - :return: the portrayal dictionary. - """ - if cell is None: - raise AssertionError - return { - "Shape": "hex", - "r": 1, - "Filled": "true", - "Layer": 0, - "x": cell.x, - "y": cell.y, - "Color": "black" if cell.isAlive else "white", - } diff --git a/examples/hex_snowflake/hex_snowflake/server.py b/examples/hex_snowflake/hex_snowflake/server.py deleted file mode 100644 index f00f20c97..000000000 --- a/examples/hex_snowflake/hex_snowflake/server.py +++ /dev/null @@ -1,12 +0,0 @@ -import mesa -from hex_snowflake.model import HexSnowflake -from hex_snowflake.portrayal import portrayCell - -width, height = 50, 50 - -# Make a world that is 50x50, on a 500x500 display. -canvas_element = mesa.visualization.CanvasHexGrid(portrayCell, width, height, 500, 500) - -server = mesa.visualization.ModularServer( - HexSnowflake, [canvas_element], "Hex Snowflake", {"height": height, "width": width} -) diff --git a/examples/hex_snowflake/hex_snowflake/model.py b/examples/hex_snowflake/model.py similarity index 100% rename from examples/hex_snowflake/hex_snowflake/model.py rename to examples/hex_snowflake/model.py diff --git a/examples/hex_snowflake/run.py b/examples/hex_snowflake/run.py index 7a8226474..e69de29bb 100644 --- a/examples/hex_snowflake/run.py +++ b/examples/hex_snowflake/run.py @@ -1,3 +0,0 @@ -from hex_snowflake.server import server - -server.launch(open_browser=True) diff --git a/examples/shape_example/app.py b/examples/shape_example/app.py new file mode 100644 index 000000000..0300a9135 --- /dev/null +++ b/examples/shape_example/app.py @@ -0,0 +1,20 @@ +from mesa.visualization import SolaraViz, make_space_component +from model import ShapeExample, Walker + + +def agent_portrayal(agent): + if isinstance(agent, Walker): + return { + "color": "tab:green", + "size": 50, + "marker": "^", + } + + +model = ShapeExample() + +page = SolaraViz( + model, + components=[make_space_component(agent_portrayal=agent_portrayal)], + name="Shape Model Example", +) diff --git a/examples/shape_example/model.py b/examples/shape_example/model.py new file mode 100644 index 000000000..6fb1a9959 --- /dev/null +++ b/examples/shape_example/model.py @@ -0,0 +1,35 @@ +import mesa +from mesa.discrete_space import CellAgent, OrthogonalMooreGrid + + +class Walker(CellAgent): # ← CellAgent not mesa.Agent + def __init__(self, model, heading=(1, 0)): + super().__init__(model) + self.heading = heading + self.headings = ((1, 0), (0, 1), (-1, 0), (0, -1)) + + def step(self): + new_cell = self.random.choice(list(self.cell.neighborhood)) + if new_cell.is_empty: + self.cell = new_cell + self.heading = self.random.choice(self.headings) + + +class ShapeExample(mesa.Model): + def __init__(self, num_agents=5, width=20, height=10): + super().__init__() + self.num_agents = num_agents + self.headings = ((1, 0), (0, 1), (-1, 0), (0, -1)) + self.grid = OrthogonalMooreGrid((width, height), torus=True, random=self.random) + self.make_walker_agents() + self.running = True + + def make_walker_agents(self): + cells = self.random.sample(list(self.grid.all_cells), self.num_agents) + for cell in cells: + heading = self.random.choice(self.headings) + a = Walker(self, heading) + a.cell = cell + + def step(self): + self.agents.shuffle_do("step") diff --git a/examples/shape_example/run.py b/examples/shape_example/run.py index 8d0f6d5e0..e69de29bb 100644 --- a/examples/shape_example/run.py +++ b/examples/shape_example/run.py @@ -1,3 +0,0 @@ -from shape_example.server import server - -server.launch(open_browser=True) diff --git a/examples/shape_example/shape_example/model.py b/examples/shape_example/shape_example/model.py deleted file mode 100644 index 53e675d8c..000000000 --- a/examples/shape_example/shape_example/model.py +++ /dev/null @@ -1,34 +0,0 @@ -import mesa -from mesa.discrete_space import OrthogonalMooreGrid - - -class Walker(mesa.Agent): - def __init__(self, model, heading=(1, 0)): - super().__init__(model) - self.heading = heading - self.headings = {(1, 0), (0, 1), (-1, 0), (0, -1)} - - -class ShapeExample(mesa.Model): - def __init__(self, num_agents=2, width=20, height=10): - super().__init__() - self.num_agents = num_agents # num of agents - self.headings = ((1, 0), (0, 1), (-1, 0), (0, -1)) # tuples are fast - self.grid = OrthogonalMooreGrid((width, height), torus=True, random=self.random) - - self.make_walker_agents() - self.running = True - - def make_walker_agents(self): - for _ in range(self.num_agents): - x = self.random.randrange(self.grid.dimensions[0]) - y = self.random.randrange(self.grid.dimensions[1]) - cell = self.grid[(x, y)] - heading = self.random.choice(self.headings) - # heading = (1, 0) - if cell.is_empty: - a = Walker(self, heading) - a.cell = cell - - def step(self): - self.agents.shuffle_do("step") diff --git a/examples/shape_example/shape_example/server.py b/examples/shape_example/shape_example/server.py deleted file mode 100644 index 5d31f8886..000000000 --- a/examples/shape_example/shape_example/server.py +++ /dev/null @@ -1,43 +0,0 @@ -import mesa - -from .model import ShapeExample, Walker - - -def agent_draw(agent): - portrayal = None - if agent is None: - # Actually this if part is unnecessary, but still keeping it for - # aesthetics - pass - elif isinstance(agent, Walker): - print(f"Uid: {agent.unique_id}, Heading: {agent.heading}") - portrayal = { - "Shape": "arrowHead", - "Filled": "true", - "Layer": 2, - "Color": ["#00FF00", "#99FF99"], - "stroke_color": "#666666", - "heading_x": agent.heading[0], - "heading_y": agent.heading[1], - "text": agent.unique_id, - "text_color": "white", - "scale": 0.8, - } - return portrayal - - -width = 15 -height = 10 -num_agents = 2 -pixel_ratio = 50 -grid = mesa.visualization.CanvasGrid( - agent_draw, width, height, width * pixel_ratio, height * pixel_ratio -) -server = mesa.visualization.ModularServer( - ShapeExample, - [grid], - "Shape Model Example", - {"N": num_agents, "width": width, "height": height}, -) -server.max_steps = 0 -server.port = 8521