From e7d1feef6da363c4a49d504fae116903af1bc1d9 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Wed, 23 Oct 2024 15:34:23 +0200 Subject: [PATCH 01/74] start --- mesa/visualization/components/matplotlib.py | 2 ++ mesa/visualization/components/util.py | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 mesa/visualization/components/util.py diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index bea633d6c8b..be9e7640c22 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -37,6 +37,8 @@ def MakeSpaceMatplotlib(model): return MakeSpaceMatplotlib + + @solara.component def SpaceMatplotlib( model, diff --git a/mesa/visualization/components/util.py b/mesa/visualization/components/util.py new file mode 100644 index 00000000000..f3af21e1a27 --- /dev/null +++ b/mesa/visualization/components/util.py @@ -0,0 +1,16 @@ +from mesa.space import _Grid, ContinuousSpace +from mesa.experimental.cell_space import DiscreteSpace + + +def get_agents_olstyle_grid(space: _Grid): + for entry in space: + if entry: + yield entry + + +def get_agents_newstyle_grid(space: DiscreteSpace): + for agent in space.all_cells.agents: + yield agent + +def get_agent_continuous(space: ContinuousSpace): + pass From bbb0122a382df6f955491b070c0745284a970806 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Wed, 23 Oct 2024 17:28:09 +0200 Subject: [PATCH 02/74] ongoin --- mesa/space.py | 170 ++++++++++++++------------ mesa/visualization/components/util.py | 115 ++++++++++++++++- 2 files changed, 203 insertions(+), 82 deletions(-) diff --git a/mesa/space.py b/mesa/space.py index fc089b0f258..8dbf3b3d83e 100644 --- a/mesa/space.py +++ b/mesa/space.py @@ -128,7 +128,7 @@ def __init__(self, width: int, height: int, torus: bool) -> None: # Cutoff used inside self.move_to_empty. The parameters are fitted on Python # 3.11 and it was verified that they are roughly the same for 3.10. Refer to # the code in PR#1565 to check for their stability when a new release gets out. - self.cutoff_empties = 7.953 * self.num_cells**0.384 + self.cutoff_empties = 7.953 * self.num_cells ** 0.384 @staticmethod def default_val() -> None: @@ -151,12 +151,14 @@ def build_empties(self) -> None: self._empties_built = True @overload - def __getitem__(self, index: int | Sequence[Coordinate]) -> list[GridContent]: ... + def __getitem__(self, index: int | Sequence[Coordinate]) -> list[GridContent]: + ... @overload def __getitem__( - self, index: tuple[int | slice, int | slice] - ) -> GridContent | list[GridContent]: ... + self, index: tuple[int | slice, int | slice] + ) -> GridContent | list[GridContent]: + ... def __getitem__(self, index): """Access contents from the grid.""" @@ -202,11 +204,11 @@ def coord_iter(self) -> Iterator[tuple[GridContent, Coordinate]]: yield self._grid[row][col], (row, col) # agent, position def iter_neighborhood( - self, - pos: Coordinate, - moore: bool, - include_center: bool = False, - radius: int = 1, + self, + pos: Coordinate, + moore: bool, + include_center: bool = False, + radius: int = 1, ) -> Iterator[Coordinate]: """Return an iterator over cell coordinates that are in the neighborhood of a certain point. @@ -229,11 +231,11 @@ def iter_neighborhood( yield from self.get_neighborhood(pos, moore, include_center, radius) def get_neighborhood( - self, - pos: Coordinate, - moore: bool, - include_center: bool = False, - radius: int = 1, + self, + pos: Coordinate, + moore: bool, + include_center: bool = False, + radius: int = 1, ) -> Sequence[Coordinate]: """Return a list of cells that are in the neighborhood of a certain point. @@ -268,10 +270,10 @@ def get_neighborhood( # First we check if the neighborhood is inside the grid if ( - x >= radius - and self.width - x > radius - and y >= radius - and self.height - y > radius + x >= radius + and self.width - x > radius + and y >= radius + and self.height - y > radius ): # If the radius is smaller than the distance from the borders, we # can skip boundary checks. @@ -312,11 +314,11 @@ def get_neighborhood( return tuple(neighborhood.keys()) def iter_neighbors( - self, - pos: Coordinate, - moore: bool, - include_center: bool = False, - radius: int = 1, + self, + pos: Coordinate, + moore: bool, + include_center: bool = False, + radius: int = 1, ) -> Iterator[Agent]: """Return an iterator over neighbors to a certain point. @@ -342,11 +344,11 @@ def iter_neighbors( yield cell def get_neighbors( - self, - pos: Coordinate, - moore: bool, - include_center: bool = False, - radius: int = 1, + self, + pos: Coordinate, + moore: bool, + include_center: bool = False, + radius: int = 1, ) -> list[Agent]: """Return a list of neighbors to a certain point. @@ -384,7 +386,7 @@ def out_of_bounds(self, pos: Coordinate) -> bool: @accept_tuple_argument def iter_cell_list_contents( - self, cell_list: Iterable[Coordinate] + self, cell_list: Iterable[Coordinate] ) -> Iterator[Agent]: """Returns an iterator of the agents contained in the cells identified in `cell_list`; cells with empty content are excluded. @@ -412,9 +414,11 @@ def get_cell_list_contents(self, cell_list: Iterable[Coordinate]) -> list[Agent] """ return list(self.iter_cell_list_contents(cell_list)) - def place_agent(self, agent: Agent, pos: Coordinate) -> None: ... + def place_agent(self, agent: Agent, pos: Coordinate) -> None: + ... - def remove_agent(self, agent: Agent) -> None: ... + def remove_agent(self, agent: Agent) -> None: + ... def move_agent(self, agent: Agent, pos: Coordinate) -> None: """Move an agent from its current position to a new position. @@ -429,11 +433,11 @@ def move_agent(self, agent: Agent, pos: Coordinate) -> None: self.place_agent(agent, pos) def move_agent_to_one_of( - self, - agent: Agent, - pos: list[Coordinate], - selection: str = "random", - handle_empty: str | None = None, + self, + agent: Agent, + pos: list[Coordinate], + selection: str = "random", + handle_empty: str | None = None, ) -> None: """Move an agent to one of the given positions. @@ -493,7 +497,7 @@ def _distance_squared(self, pos1: Coordinate, pos2: Coordinate) -> float: if self.torus: dx = min(dx, self.width - dx) dy = min(dy, self.height - dy) - return dx**2 + dy**2 + return dx ** 2 + dy ** 2 def swap_pos(self, agent_a: Agent, agent_b: Agent) -> None: """Swap agents positions.""" @@ -552,8 +556,8 @@ def exists_empty_cells(self) -> bool: def is_single_argument_function(function): """Check if a function is a single argument function.""" return ( - inspect.isfunction(function) - and len(inspect.signature(function).parameters) == 1 + inspect.isfunction(function) + and len(inspect.signature(function).parameters) == 1 ) @@ -579,7 +583,7 @@ class PropertyLayer: propertylayer_experimental_warning_given = False def __init__( - self, name: str, width: int, height: int, default_value, dtype=np.float64 + self, name: str, width: int, height: int, default_value, dtype=np.float64 ): """Initializes a new PropertyLayer instance. @@ -607,7 +611,7 @@ def __init__( # Check that width and height are positive integers if (not isinstance(width, int) or width < 1) or ( - not isinstance(height, int) or height < 1 + not isinstance(height, int) or height < 1 ): raise ValueError( f"Width and height must be positive integers, got {width} and {height}." @@ -655,8 +659,8 @@ def set_cells(self, value, condition=None): condition_result = vectorized_condition(self.data) if ( - not isinstance(condition_result, np.ndarray) - or condition_result.shape != self.data.shape + not isinstance(condition_result, np.ndarray) + or condition_result.shape != self.data.shape ): raise ValueError( "Result of condition must be a NumPy array with the same shape as the grid." @@ -775,11 +779,11 @@ class _PropertyGrid(_Grid): """ def __init__( - self, - width: int, - height: int, - torus: bool, - property_layers: None | PropertyLayer | list[PropertyLayer] = None, + self, + width: int, + height: int, + torus: bool, + property_layers: None | PropertyLayer | list[PropertyLayer] = None, ): """Initializes a new _PropertyGrid instance with specified dimensions and optional property layers. @@ -846,7 +850,7 @@ def remove_property_layer(self, property_name: str): del self.properties[property_name] def get_neighborhood_mask( - self, pos: Coordinate, moore: bool, include_center: bool, radius: int + self, pos: Coordinate, moore: bool, include_center: bool, radius: int ) -> np.ndarray: """Generate a boolean mask representing the neighborhood. @@ -870,12 +874,12 @@ def get_neighborhood_mask( return mask def select_cells( - self, - conditions: dict | None = None, - extreme_values: dict | None = None, - masks: np.ndarray | list[np.ndarray] = None, - only_empty: bool = False, - return_list: bool = True, + self, + conditions: dict | None = None, + extreme_values: dict | None = None, + masks: np.ndarray | list[np.ndarray] = None, + only_empty: bool = False, + return_list: bool = True, ) -> list[Coordinate] | np.ndarray: """Select cells based on property conditions, extreme values, and/or masks, with an option to only select empty cells. @@ -1031,11 +1035,11 @@ def remove_agent(self, agent: Agent) -> None: agent.pos = None def iter_neighbors( # noqa: D102 - self, - pos: Coordinate, - moore: bool, - include_center: bool = False, - radius: int = 1, + self, + pos: Coordinate, + moore: bool, + include_center: bool = False, + radius: int = 1, ) -> Iterator[Agent]: return itertools.chain.from_iterable( super().iter_neighbors(pos, moore, include_center, radius) @@ -1043,7 +1047,7 @@ def iter_neighbors( # noqa: D102 @accept_tuple_argument def iter_cell_list_contents( - self, cell_list: Iterable[Coordinate] + self, cell_list: Iterable[Coordinate] ) -> Iterator[Agent]: """Returns an iterator of the agents contained in the cells identified in `cell_list`. @@ -1083,7 +1087,7 @@ def torus_adj_2d(self, pos: Coordinate) -> Coordinate: return pos[0] % self.width, pos[1] % self.height def get_neighborhood( - self, pos: Coordinate, include_center: bool = False, radius: int = 1 + self, pos: Coordinate, include_center: bool = False, radius: int = 1 ) -> list[Coordinate]: """Return a list of coordinates that are in the neighborhood of a certain point. @@ -1171,7 +1175,7 @@ def get_neighborhood( return neighborhood def iter_neighborhood( - self, pos: Coordinate, include_center: bool = False, radius: int = 1 + self, pos: Coordinate, include_center: bool = False, radius: int = 1 ) -> Iterator[Coordinate]: """Return an iterator over cell coordinates that are in the neighborhood of a certain point. @@ -1187,7 +1191,7 @@ def iter_neighborhood( yield from self.get_neighborhood(pos, include_center, radius) def iter_neighbors( - self, pos: Coordinate, include_center: bool = False, radius: int = 1 + self, pos: Coordinate, include_center: bool = False, radius: int = 1 ) -> Iterator[Agent]: """Return an iterator over neighbors to a certain point. @@ -1205,7 +1209,7 @@ def iter_neighbors( return self.iter_cell_list_contents(neighborhood) def get_neighbors( - self, pos: Coordinate, include_center: bool = False, radius: int = 1 + self, pos: Coordinate, include_center: bool = False, radius: int = 1 ) -> list[Agent]: """Return a list of neighbors to a certain point. @@ -1221,6 +1225,10 @@ def get_neighbors( """ return list(self.iter_neighbors(pos, include_center, radius)) + def __iter__(self) -> Iterator[GridContent]: + """Create an iterator returns the agents in the space""" + for entry in self._agent_to_index: + yield entry class HexSingleGrid(_HexGrid, SingleGrid): """Hexagonal SingleGrid: a SingleGrid where neighbors are computed according to a hexagonal tiling of the grid. @@ -1301,12 +1309,12 @@ class ContinuousSpace: """ def __init__( - self, - x_max: float, - y_max: float, - torus: bool, - x_min: float = 0, - y_min: float = 0, + self, + x_max: float, + y_max: float, + torus: bool, + x_min: float = 0, + y_min: float = 0, ) -> None: """Create a new continuous space. @@ -1390,7 +1398,7 @@ def remove_agent(self, agent: Agent) -> None: agent.pos = None def get_neighbors( - self, pos: FloatCoordinate, radius: float, include_center: bool = True + self, pos: FloatCoordinate, radius: float, include_center: bool = True ) -> list[Agent]: """Get all agents within a certain radius. @@ -1410,14 +1418,14 @@ def get_neighbors( deltas = np.minimum(deltas, self.size - deltas) dists = deltas[:, 0] ** 2 + deltas[:, 1] ** 2 - (idxs,) = np.where(dists <= radius**2) + (idxs,) = np.where(dists <= radius ** 2) neighbors = [ self._index_to_agent[x] for x in idxs if include_center or dists[x] > 0 ] return neighbors def get_heading( - self, pos_1: FloatCoordinate, pos_2: FloatCoordinate + self, pos_1: FloatCoordinate, pos_2: FloatCoordinate ) -> FloatCoordinate: """Get the heading vector between two points, accounting for toroidal space. @@ -1492,6 +1500,11 @@ def out_of_bounds(self, pos: FloatCoordinate) -> bool: x, y = pos return x < self.x_min or x >= self.x_max or y < self.y_min or y >= self.y_max + def __iter__(self) -> Iterator[GridContent]: + """Create an iterator returns the agents in the space""" + for entry in self._agent_to_index: + yield entry + class NetworkGrid: """Network Grid where each node contains zero or more agents.""" @@ -1518,7 +1531,7 @@ def place_agent(self, agent: Agent, node_id: int) -> None: agent.pos = node_id def get_neighborhood( - self, node_id: int, include_center: bool = False, radius: int = 1 + self, node_id: int, include_center: bool = False, radius: int = 1 ) -> list[int]: """Get all adjacent nodes within a certain radius. @@ -1544,7 +1557,7 @@ def get_neighborhood( return neighborhood def get_neighbors( - self, node_id: int, include_center: bool = False, radius: int = 1 + self, node_id: int, include_center: bool = False, radius: int = 1 ) -> list[Agent]: """Get all agents in adjacent nodes (within a certain radius). @@ -1624,3 +1637,8 @@ def iter_cell_list_contents(self, cell_list: list[int]) -> Iterator[Agent]: self.G.nodes[node_id]["agent"] for node_id in itertools.filterfalse(self.is_cell_empty, cell_list) ) + + def __iter__(self) -> Iterator[GridContent]: + """Create an iterator returns the agents in the space""" + for node_id in self.G.nodes: + yield self.g.nodes[node_id]["agent"] diff --git a/mesa/visualization/components/util.py b/mesa/visualization/components/util.py index f3af21e1a27..315b641affe 100644 --- a/mesa/visualization/components/util.py +++ b/mesa/visualization/components/util.py @@ -1,16 +1,119 @@ -from mesa.space import _Grid, ContinuousSpace +from typing import Callable + +from mesa.space import _Grid, ContinuousSpace, _HexGrid, NetworkGrid from mesa.experimental.cell_space import DiscreteSpace -def get_agents_olstyle_grid(space: _Grid): +__all__ = ["get_agent_continuous", + "get_agents_olstyle_grid", + "get_agents_newstyle_grid"] + +OldStyleSpace = _Grid | ContinuousSpace | _HexGrid | NetworkGrid +Space = OldStyleSpace | DiscreteSpace + +def get_agents_olstyle_space(space: OldStyleSpace): for entry in space: if entry: yield entry - -def get_agents_newstyle_grid(space: DiscreteSpace): +def get_agents_newstyle_space(space: DiscreteSpace): for agent in space.all_cells.agents: yield agent -def get_agent_continuous(space: ContinuousSpace): - pass + +def _get_oldstyle_agent_data(agents, agent_portrayal): + """Helper function to get agent data for visualization.""" + x, y, s, c, m = [], [], [], [], [] + for agent in agents: + data = agent_portrayal(agent) + x.append(pos[0] + 0.5) # Center the agent in the cell + y.append(pos[1] + 0.5) # Center the agent in the cell + default_size = (180 / max(space.width, space.height)) ** 2 + s.append(data.get("size", default_size)) + c.append(data.get("color", "tab:blue")) + m.append(data.get("shape", "o")) + return {"x": x, "y": y, "s": s, "c": c, "m": m} + + +# fixme: +# how we get the data depends on the space and I don't like it. +# can't we separate more cleanly the data and any post +# space specific refinements? +# or gather data depending on the space? +# fixme: +# what routes do we have? +# network (2 options) +# continuous (1 option) +# orthogonal grids (7 options?) +# voronoi (1 option) + + +def get_agent_data(space: Space, agent_portrayal: Callable): + # fixme should we cover networks here at all? + + match space: + case _Grid(): # matches also hexgrids + agents = get_agents_olstyle_space(space) + case ContinuousSpace(): + agents = get_agents_olstyle_space(space) + case NetworkGrid(): + agents = get_agents_olstyle_space(space) + case DiscreteSpace(): + agents = get_agents_newstyle_space(space) + case _: + raise NotImplementedError(f"Unknown space of type {type(space)}") + + x, y, s, c, m = [], [], [], [], [] + for agent in agents: + data = agent_portrayal(agent) + + if isinstance(space, DiscreteSpace): + x_i, y_i = agent.cell.coordinate + else: + x_i, y_i = agent.pos + + x.append(x_i) + y.append(y_i) + s.append(data.get("size", None)) # we use None here as a flag to fall back on default + c.append(data.get("color", "tab:blue")) + m.append(data.get("shape", "o")) + + + + + +if __name__ == '__main__': + from mesa import Agent, Model + from mesa.space import SingleGrid + from mesa.experimental.cell_space import OrthogonalMooreGrid, CellAgent + + model = Model() + space = SingleGrid(10, 10, True) + for _ in range(10): + agent = Agent(model) + pos = model.random.choice(list(space.empties)) + space.place_agent(agent, pos) + + agents = list(get_agents_olstyle_space(space)) + assert len(agents) == 10 + + + model = Model() + space = OrthogonalMooreGrid((10, 10), capacity=1, torus=True, random=model.random) + for _ in range(10): + agent = CellAgent(model) + cell = space.select_random_empty_cell() + agent.cell = cell + + agents = list(get_agents_newstyle_space(space)) + assert len(agents) == 10 + + model = Model() + space = ContinuousSpace(10, 10, True) + for _ in range(10): + agent = Agent(model) + pos = (agent.random.random()*space.width, agent.random.random()*space.height) + space.place_agent(agent, pos) + + agents = list(get_agents_olstyle_space(space)) + assert len(agents) == 10 \ No newline at end of file From 4197325ad0de6149a29a1eca2adb820b3e5d382c Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Fri, 25 Oct 2024 17:08:53 +0200 Subject: [PATCH 03/74] creating a mess --- mesa/examples/advanced/wolf_sheep/app.py | 28 +- mesa/visualization/components/matplotlib.py | 367 ++++++++++++-------- 2 files changed, 243 insertions(+), 152 deletions(-) diff --git a/mesa/examples/advanced/wolf_sheep/app.py b/mesa/examples/advanced/wolf_sheep/app.py index b5ac6e8bf47..ab581048337 100644 --- a/mesa/examples/advanced/wolf_sheep/app.py +++ b/mesa/examples/advanced/wolf_sheep/app.py @@ -1,3 +1,8 @@ +import sys +import os.path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../../'))) + + from mesa.examples.advanced.wolf_sheep.agents import GrassPatch, Sheep, Wolf from mesa.examples.advanced.wolf_sheep.model import WolfSheep from mesa.visualization import ( @@ -16,24 +21,25 @@ def wolf_sheep_portrayal(agent): return portrayal = { - "size": 25, - "shape": "s", # square marker + "s": 25, } if isinstance(agent, Wolf): - portrayal["color"] = WOLF_COLOR - portrayal["Layer"] = 3 + portrayal["c"] = "tab:orange" + portrayal["marker"] = "o" elif isinstance(agent, Sheep): - portrayal["color"] = SHEEP_COLOR - portrayal["Layer"] = 2 + portrayal["c"] = "tab:blue" + portrayal["zorder"] = 2 + portrayal["marker"] = "o" elif isinstance(agent, GrassPatch): if agent.fully_grown: - portrayal["color"] = "#00FF00" + portrayal["c"] = "tab:green" else: - portrayal["color"] = "#84e184" - # portrayal["shape"] = "rect" + portrayal["c"] = "tab:brown" + portrayal["marker"] = "s" # portrayal["Filled"] = "true" - portrayal["Layer"] = 1 + portrayal["zorder"] = 1 + portrayal["s"] = 75 return portrayal @@ -65,7 +71,7 @@ def wolf_sheep_portrayal(agent): space_component = make_space_matplotlib(wolf_sheep_portrayal) lineplot_component = make_plot_measure(["Wolves", "Sheep", "Grass"]) -model = WolfSheep() +model = WolfSheep(grass=True) page = SolaraViz( diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index be9e7640c22..1590ab9d613 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -10,11 +10,18 @@ from matplotlib.colors import LinearSegmentedColormap, Normalize, to_rgba from matplotlib.figure import Figure +from collections import defaultdict +from typing import Callable + import mesa -from mesa.experimental.cell_space import Grid, VoronoiGrid -from mesa.space import PropertyLayer +from mesa.experimental.cell_space import Grid, VoronoiGrid, OrthogonalMooreGrid, OrthogonalVonNeumannGrid, HexGrid +from mesa.space import PropertyLayer, SingleGrid, MultiGrid, HexSingleGrid, HexMultiGrid, ContinuousSpace, NetworkGrid from mesa.visualization.utils import update_counter +OrthogonalGrid = SingleGrid | MultiGrid | OrthogonalMooreGrid | OrthogonalVonNeumannGrid +HexGrid = HexSingleGrid | HexMultiGrid | HexGrid +Network = NetworkGrid | mesa.experimental.cell_space.Network + def make_space_matplotlib(agent_portrayal=None, propertylayer_portrayal=None): """Create a Matplotlib-based space visualization component. @@ -27,7 +34,6 @@ def make_space_matplotlib(agent_portrayal=None, propertylayer_portrayal=None): function: A function that creates a SpaceMatplotlib component """ if agent_portrayal is None: - def agent_portrayal(a): return {"id": a.unique_id} @@ -37,19 +43,16 @@ def MakeSpaceMatplotlib(model): return MakeSpaceMatplotlib - - @solara.component def SpaceMatplotlib( - model, - agent_portrayal, - propertylayer_portrayal, - dependencies: list[any] | None = None, + model, + agent_portrayal, + propertylayer_portrayal, + dependencies: list[any] | None = None, ): """Create a Matplotlib-based space visualization component.""" update_counter.get() - space_fig = Figure() - space_ax = space_fig.subplots() + space = getattr(model, "grid", None) if space is None: space = getattr(model, "space", None) @@ -57,22 +60,25 @@ def SpaceMatplotlib( # https://stackoverflow.com/questions/67524641/convert-multiple-isinstance-checks-to-structural-pattern-matching match space: case mesa.space._Grid(): - _draw_grid(space, space_ax, agent_portrayal, propertylayer_portrayal, model) - case mesa.space.ContinuousSpace(): - _draw_continuous_space(space, space_ax, agent_portrayal, model) + fig, ax = draw_orthogonal_grid(space, agent_portrayal, propertylayer_portrayal) + # case mesa.space.ContinuousSpace(): + # _draw_continuous_space(space, space_ax, agent_portrayal, model) case mesa.space.NetworkGrid(): - _draw_network_grid(space, space_ax, agent_portrayal) - case VoronoiGrid(): - _draw_voronoi(space, space_ax, agent_portrayal) - case Grid(): # matches OrthogonalMooreGrid, OrthogonalVonNeumannGrid, and Hexgrid - # fixme add a separate draw method for hexgrids in the future - _draw_discrete_space_grid(space, space_ax, agent_portrayal) - case None: - if propertylayer_portrayal: - draw_property_layers(space_ax, space, propertylayer_portrayal, model) + fig, ax = _draw_network_grid(space, agent_portrayal) + # case VoronoiGrid(): + # _draw_voronoi(space, space_ax, agent_portrayal) + case OrthogonalMooreGrid(): # matches OrthogonalMooreGrid, OrthogonalVonNeumannGrid, and Hexgrid + fig, ax = draw_orthogonal_grid(space, agent_portrayal, propertylayer_portrayal) + case OrthogonalVonNeumannGrid(): # matches OrthogonalMooreGrid, OrthogonalVonNeumannGrid, and Hexgrid + fig, ax = draw_orthogonal_grid(space, agent_portrayal, propertylayer_portrayal) + case mesa.experimental.cell_space.Network(): + fig, ax = _draw_network_grid(space, agent_portrayal) + # case None: + # if propertylayer_portrayal: + # draw_property_layers(space_ax, space, propertylayer_portrayal, model) solara.FigureMatplotlib( - space_fig, format="png", bbox_inches="tight", dependencies=dependencies + fig, format="png", bbox_inches="tight", dependencies=dependencies ) @@ -148,107 +154,222 @@ def draw_property_layers(ax, space, propertylayer_portrayal, model): ) -def _draw_grid(space, space_ax, agent_portrayal, propertylayer_portrayal, model): - if propertylayer_portrayal: - draw_property_layers(space_ax, space, propertylayer_portrayal, model) +def collect_agent_data(space: OrthogonalGrid | HexGrid | Network | ContinuousSpace, agent_portrayal: Callable): + """Collect the plotting data for all agents in the space. + + Args: + space: The space containing the Agents. + agent_portrayal: A callable that is called with the agent and returns a dict + loc: a boolean indicating whether to gather agent x, y data or not + + Notes: + agent portray dict is limited to s (size of marker), c (color of marker, and marker (marker style) + see `Matplotlib`_. - agent_data = _get_agent_data(space, agent_portrayal) - space_ax.set_xlim(0, space.width) - space_ax.set_ylim(0, space.height) - _split_and_scatter(agent_data, space_ax) + .. _Matplotlib: https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.scatter.html + + """ + default_size = (180 / max(space.width, space.height)) ** 2 + + arguments = dict(x=[], y=[], s=[], c=[], marker=[]) + for agent in space.agents: + portray = agent_portrayal(agent) + loc = agent.pos + if loc is None: + loc = agent.cell.coordinate + x, y = loc + arguments["x"].append(x) + arguments["y"].append(y) + + arguments['s'].append(portray.get("s", default_size)) + arguments['c'].append(portray.get("c", "tab:blue")) + arguments['marker'].append(portray.get("marker", "o")) + + return {k: np.asarray(v) for k, v in arguments.items()} + + +def draw_orthogonal_grid(space: OrthogonalGrid, agent_portrayal: Callable, propertylayer_portrayal: Callable): + arguments = collect_agent_data(space, agent_portrayal) + + fig, ax = plt.subplots() + ax.set_xlim(0, space.width) + ax.set_ylim(0, space.height) # Draw grid lines for x in range(space.width + 1): - space_ax.axvline(x, color="gray", linestyle=":") + ax.axvline(x, color="gray", linestyle=":") for y in range(space.height + 1): - space_ax.axhline(y, color="gray", linestyle=":") + ax.axhline(y, color="gray", linestyle=":") + x = arguments.pop('x') + 0.5 + y = arguments.pop('y') + 0.5 + marker = arguments.pop('marker') -def _get_agent_data(space, agent_portrayal): - """Helper function to get agent data for visualization.""" - x, y, s, c, m = [], [], [], [], [] - for agents, pos in space.coord_iter(): - if not agents: - continue - if not isinstance(agents, list): - agents = [agents] # noqa PLW2901 - for agent in agents: - data = agent_portrayal(agent) - x.append(pos[0] + 0.5) # Center the agent in the cell - y.append(pos[1] + 0.5) # Center the agent in the cell - default_size = (180 / max(space.width, space.height)) ** 2 - s.append(data.get("size", default_size)) - c.append(data.get("color", "tab:blue")) - m.append(data.get("shape", "o")) - return {"x": x, "y": y, "s": s, "c": c, "m": m} - - -def _split_and_scatter(portray_data, space_ax): - """Helper function to split and scatter agent data.""" - for marker in set(portray_data["m"]): - mask = [m == marker for m in portray_data["m"]] - space_ax.scatter( - [x for x, show in zip(portray_data["x"], mask) if show], - [y for y, show in zip(portray_data["y"], mask) if show], - s=[s for s, show in zip(portray_data["s"], mask) if show], - c=[c for c, show in zip(portray_data["c"], mask) if show], - marker=marker, - ) + for mark in np.unique(marker): + mask = marker == mark + ax.scatter(x[mask], y[mask], marker=mark, **{k: v[mask] for k, v in arguments.items()}) + + return fig, ax + + +# def draw_hex_grid(space: HexGrid, agent_portrayal: Callable, propertylayer_portrayal: Callable): +# ... +# +def collect_agent_data_for_networks(space, agent_portrayal): + arguments = defaultdict(list) + for agent in space.agents: + portray = agent_portrayal(agent) + for k, v in agent_portrayal.items(): + arguments[k].append(v) + + return arguments + +def draw_network(space: Network, agent_portrayal: Callable): + """Visualize a network space. + + Args: + space: the space to visualize + agent_portrayal: a callable that is called with the agent and returns a dict + + Notes: + this uses networkx.draw under the hood so agent portrayal fields should match those used there + i.e., node_side, node_color, node_shape, edge_color + + + """ + arguments = collect_agent_data_for_networks(agent_portrayal) + + fig, ax = plt.subplots() + graph = space.G + pos = nx.spring_layout(graph, seed=0) + nx.draw( + graph, + ax=ax, + pos=pos, + **arguments, + ) + return fig, ax + + +# def draw_continuous_space(space: ContinuousSpace, agent_portrayal: Callable): +# ... +# +# def draw_voroinoi_grid(space: VoronoiGrid, agent_portrayal: Callable, propertylayer_portrayal: Callable): +# ... + +# def _draw_grid(space, space_ax, agent_portrayal, propertylayer_portrayal, model): +# if propertylayer_portrayal: +# draw_property_layers(space_ax, space, propertylayer_portrayal, model) +# +# agent_data = _get_agent_data(space, agent_portrayal) +# +# space_ax.set_xlim(0, space.width) +# space_ax.set_ylim(0, space.height) +# _split_and_scatter(agent_data, space_ax) +# +# # Draw grid lines +# for x in range(space.width + 1): +# space_ax.axvline(x, color="gray", linestyle=":") +# for y in range(space.height + 1): +# space_ax.axhline(y, color="gray", linestyle=":") +# +# +# def _get_agent_data(space, agent_portrayal): +# """Helper function to get agent data for visualization.""" +# x, y, s, c, m = [], [], [], [], [] +# for agents, pos in space.coord_iter(): +# if not agents: +# continue +# if not isinstance(agents, list): +# agents = [agents] # noqa PLW2901 +# for agent in agents: +# data = agent_portrayal(agent) +# x.append(pos[0] + 0.5) # Center the agent in the cell +# y.append(pos[1] + 0.5) # Center the agent in the cell +# default_size = (180 / max(space.width, space.height)) ** 2 +# s.append(data.get("size", default_size)) +# c.append(data.get("color", "tab:blue")) +# m.append(data.get("shape", "o")) +# return {"x": x, "y": y, "s": s, "c": c, "m": m} +# +# +# def _split_and_scatter(portray_data, space_ax): +# """Helper function to split and scatter agent data.""" +# for marker in set(portray_data["m"]): +# mask = [m == marker for m in portray_data["m"]] +# space_ax.scatter( +# [x for x, show in zip(portray_data["x"], mask) if show], +# [y for y, show in zip(portray_data["y"], mask) if show], +# s=[s for s, show in zip(portray_data["s"], mask) if show], +# c=[c for c, show in zip(portray_data["c"], mask) if show], +# marker=marker, +# ) + +def collect_agent_data_for_networks(space, agent_portrayal): + arguments = defaultdict(list) + for agent in space.agents: + portray = agent_portrayal(agent) + for k, v in agent_portrayal.items(): + arguments[k].append(v) + + return arguments def _draw_network_grid(space, space_ax, agent_portrayal): + arguments = collect_agent_data_for_networks(agent_portrayal) + graph = space.G pos = nx.spring_layout(graph, seed=0) nx.draw( graph, ax=space_ax, pos=pos, - **agent_portrayal(graph), + **arguments, ) -def _draw_continuous_space(space, space_ax, agent_portrayal, model): - def portray(space): - x = [] - y = [] - s = [] # size - c = [] # color - m = [] # shape - for agent in space._agent_to_index: - data = agent_portrayal(agent) - _x, _y = agent.pos - x.append(_x) - y.append(_y) - - # This is matplotlib's default marker size - default_size = 20 - size = data.get("size", default_size) - s.append(size) - color = data.get("color", "tab:blue") - c.append(color) - mark = data.get("shape", "o") - m.append(mark) - return {"x": x, "y": y, "s": s, "c": c, "m": m} - - # Determine border style based on space.torus - border_style = "solid" if not space.torus else (0, (5, 10)) - - # Set the border of the plot - for spine in space_ax.spines.values(): - spine.set_linewidth(1.5) - spine.set_color("black") - spine.set_linestyle(border_style) - - width = space.x_max - space.x_min - x_padding = width / 20 - height = space.y_max - space.y_min - y_padding = height / 20 - space_ax.set_xlim(space.x_min - x_padding, space.x_max + x_padding) - space_ax.set_ylim(space.y_min - y_padding, space.y_max + y_padding) - - # Portray and scatter the agents in the space - _split_and_scatter(portray(space), space_ax) +# def _draw_continuous_space(space, space_ax, agent_portrayal, model): +# def portray(space): +# x = [] +# y = [] +# s = [] # size +# c = [] # color +# m = [] # shape +# for agent in space._agent_to_index: +# data = agent_portrayal(agent) +# _x, _y = agent.pos +# x.append(_x) +# y.append(_y) +# +# # This is matplotlib's default marker size +# default_size = 20 +# size = data.get("size", default_size) +# s.append(size) +# color = data.get("color", "tab:blue") +# c.append(color) +# mark = data.get("shape", "o") +# m.append(mark) +# return {"x": x, "y": y, "s": s, "c": c, "m": m} +# +# # Determine border style based on space.torus +# border_style = "solid" if not space.torus else (0, (5, 10)) +# +# # Set the border of the plot +# for spine in space_ax.spines.values(): +# spine.set_linewidth(1.5) +# spine.set_color("black") +# spine.set_linestyle(border_style) +# +# width = space.x_max - space.x_min +# x_padding = width / 20 +# height = space.y_max - space.y_min +# y_padding = height / 20 +# space_ax.set_xlim(space.x_min - x_padding, space.x_max + x_padding) +# space_ax.set_ylim(space.y_min - y_padding, space.y_max + y_padding) +# +# # Portray and scatter the agents in the space +# _split_and_scatter(portray(space), space_ax) def _draw_voronoi(space, space_ax, agent_portrayal): @@ -299,42 +420,6 @@ def portray(g): space_ax.plot(*zip(*polygon), color="black") # Plot polygon edges in black -def _draw_discrete_space_grid(space: Grid, space_ax, agent_portrayal): - if space._ndims != 2: - raise ValueError("Space must be 2D") - - def portray(g): - x = [] - y = [] - s = [] # size - c = [] # color - - for cell in g.all_cells: - for agent in cell.agents: - data = agent_portrayal(agent) - x.append(cell.coordinate[0]) - y.append(cell.coordinate[1]) - if "size" in data: - s.append(data["size"]) - if "color" in data: - c.append(data["color"]) - out = {"x": x, "y": y} - out["s"] = s - if len(c) > 0: - out["c"] = c - - return out - - space_ax.set_xlim(0, space.width) - space_ax.set_ylim(0, space.height) - - # Draw grid lines - for x in range(space.width + 1): - space_ax.axvline(x, color="gray", linestyle=":") - for y in range(space.height + 1): - space_ax.axhline(y, color="gray", linestyle=":") - - space_ax.scatter(**portray(space)) def make_plot_measure(measure: str | dict[str, str] | list[str] | tuple[str]): From 8e7b6c6f713cbed0d7962c88c7fefd6b8cd57ad9 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Sun, 27 Oct 2024 09:01:27 +0100 Subject: [PATCH 04/74] ongoing refactor --- mesa/visualization/components/matplotlib.py | 90 +++------------------ 1 file changed, 12 insertions(+), 78 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 1590ab9d613..b4d66f4087e 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -64,7 +64,7 @@ def SpaceMatplotlib( # case mesa.space.ContinuousSpace(): # _draw_continuous_space(space, space_ax, agent_portrayal, model) case mesa.space.NetworkGrid(): - fig, ax = _draw_network_grid(space, agent_portrayal) + fig, ax = draw_network(space, agent_portrayal) # case VoronoiGrid(): # _draw_voronoi(space, space_ax, agent_portrayal) case OrthogonalMooreGrid(): # matches OrthogonalMooreGrid, OrthogonalVonNeumannGrid, and Hexgrid @@ -72,10 +72,10 @@ def SpaceMatplotlib( case OrthogonalVonNeumannGrid(): # matches OrthogonalMooreGrid, OrthogonalVonNeumannGrid, and Hexgrid fig, ax = draw_orthogonal_grid(space, agent_portrayal, propertylayer_portrayal) case mesa.experimental.cell_space.Network(): - fig, ax = _draw_network_grid(space, agent_portrayal) - # case None: - # if propertylayer_portrayal: - # draw_property_layers(space_ax, space, propertylayer_portrayal, model) + fig, ax = draw_network(space, agent_portrayal) + case None: + if propertylayer_portrayal: + draw_property_layers(space_ax, space, propertylayer_portrayal, model) solara.FigureMatplotlib( fig, format="png", bbox_inches="tight", dependencies=dependencies @@ -193,17 +193,17 @@ def draw_orthogonal_grid(space: OrthogonalGrid, agent_portrayal: Callable, prope arguments = collect_agent_data(space, agent_portrayal) fig, ax = plt.subplots() - ax.set_xlim(0, space.width) - ax.set_ylim(0, space.height) + ax.set_xlim(-0.5, space.width-0.5) + ax.set_ylim(-0.5, space.height-0.5) # Draw grid lines - for x in range(space.width + 1): + for x in np.arange(-0.5, space.width-0.5, 1): ax.axvline(x, color="gray", linestyle=":") - for y in range(space.height + 1): + for y in np.arange(-0.5, space.height-0.5, 1): ax.axhline(y, color="gray", linestyle=":") - x = arguments.pop('x') + 0.5 - y = arguments.pop('y') + 0.5 + x = arguments.pop('x') + y = arguments.pop('y') marker = arguments.pop('marker') for mark in np.unique(marker): @@ -216,6 +216,7 @@ def draw_orthogonal_grid(space: OrthogonalGrid, agent_portrayal: Callable, prope # def draw_hex_grid(space: HexGrid, agent_portrayal: Callable, propertylayer_portrayal: Callable): # ... # + def collect_agent_data_for_networks(space, agent_portrayal): arguments = defaultdict(list) for agent in space.agents: @@ -259,74 +260,7 @@ def draw_network(space: Network, agent_portrayal: Callable): # def draw_voroinoi_grid(space: VoronoiGrid, agent_portrayal: Callable, propertylayer_portrayal: Callable): # ... -# def _draw_grid(space, space_ax, agent_portrayal, propertylayer_portrayal, model): -# if propertylayer_portrayal: -# draw_property_layers(space_ax, space, propertylayer_portrayal, model) -# -# agent_data = _get_agent_data(space, agent_portrayal) -# -# space_ax.set_xlim(0, space.width) -# space_ax.set_ylim(0, space.height) -# _split_and_scatter(agent_data, space_ax) -# -# # Draw grid lines -# for x in range(space.width + 1): -# space_ax.axvline(x, color="gray", linestyle=":") -# for y in range(space.height + 1): -# space_ax.axhline(y, color="gray", linestyle=":") -# -# -# def _get_agent_data(space, agent_portrayal): -# """Helper function to get agent data for visualization.""" -# x, y, s, c, m = [], [], [], [], [] -# for agents, pos in space.coord_iter(): -# if not agents: -# continue -# if not isinstance(agents, list): -# agents = [agents] # noqa PLW2901 -# for agent in agents: -# data = agent_portrayal(agent) -# x.append(pos[0] + 0.5) # Center the agent in the cell -# y.append(pos[1] + 0.5) # Center the agent in the cell -# default_size = (180 / max(space.width, space.height)) ** 2 -# s.append(data.get("size", default_size)) -# c.append(data.get("color", "tab:blue")) -# m.append(data.get("shape", "o")) -# return {"x": x, "y": y, "s": s, "c": c, "m": m} -# -# -# def _split_and_scatter(portray_data, space_ax): -# """Helper function to split and scatter agent data.""" -# for marker in set(portray_data["m"]): -# mask = [m == marker for m in portray_data["m"]] -# space_ax.scatter( -# [x for x, show in zip(portray_data["x"], mask) if show], -# [y for y, show in zip(portray_data["y"], mask) if show], -# s=[s for s, show in zip(portray_data["s"], mask) if show], -# c=[c for c, show in zip(portray_data["c"], mask) if show], -# marker=marker, -# ) -def collect_agent_data_for_networks(space, agent_portrayal): - arguments = defaultdict(list) - for agent in space.agents: - portray = agent_portrayal(agent) - for k, v in agent_portrayal.items(): - arguments[k].append(v) - - return arguments - -def _draw_network_grid(space, space_ax, agent_portrayal): - arguments = collect_agent_data_for_networks(agent_portrayal) - - graph = space.G - pos = nx.spring_layout(graph, seed=0) - nx.draw( - graph, - ax=space_ax, - pos=pos, - **arguments, - ) # def _draw_continuous_space(space, space_ax, agent_portrayal, model): From bdb23cbd10b864e53e51401c9df3667db900fa3e Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Sun, 27 Oct 2024 11:08:27 +0100 Subject: [PATCH 05/74] Update matplotlib.py --- mesa/visualization/components/matplotlib.py | 274 ++++++++++++-------- 1 file changed, 162 insertions(+), 112 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index b4d66f4087e..38400d10c32 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -1,6 +1,8 @@ """Matplotlib based solara components for visualization MESA spaces and plots.""" import warnings +from collections import defaultdict +from collections.abc import Callable import matplotlib.pyplot as plt import networkx as nx @@ -10,16 +12,25 @@ from matplotlib.colors import LinearSegmentedColormap, Normalize, to_rgba from matplotlib.figure import Figure -from collections import defaultdict -from typing import Callable - import mesa -from mesa.experimental.cell_space import Grid, VoronoiGrid, OrthogonalMooreGrid, OrthogonalVonNeumannGrid, HexGrid -from mesa.space import PropertyLayer, SingleGrid, MultiGrid, HexSingleGrid, HexMultiGrid, ContinuousSpace, NetworkGrid +from mesa.experimental.cell_space import ( + OrthogonalMooreGrid, + OrthogonalVonNeumannGrid, + VoronoiGrid, +) +from mesa.space import ( + ContinuousSpace, + HexMultiGrid, + HexSingleGrid, + MultiGrid, + NetworkGrid, + PropertyLayer, + SingleGrid, +) from mesa.visualization.utils import update_counter OrthogonalGrid = SingleGrid | MultiGrid | OrthogonalMooreGrid | OrthogonalVonNeumannGrid -HexGrid = HexSingleGrid | HexMultiGrid | HexGrid +HexGrid = HexSingleGrid | HexMultiGrid | mesa.experimental.cell_space.HexGrid Network = NetworkGrid | mesa.experimental.cell_space.Network @@ -60,22 +71,30 @@ def SpaceMatplotlib( # https://stackoverflow.com/questions/67524641/convert-multiple-isinstance-checks-to-structural-pattern-matching match space: case mesa.space._Grid(): - fig, ax = draw_orthogonal_grid(space, agent_portrayal, propertylayer_portrayal) - # case mesa.space.ContinuousSpace(): - # _draw_continuous_space(space, space_ax, agent_portrayal, model) + fig, ax = draw_orthogonal_grid(space, agent_portrayal, propertylayer_portrayal, model) + case OrthogonalMooreGrid(): + fig, ax = draw_orthogonal_grid(space, agent_portrayal, propertylayer_portrayal, model) + case OrthogonalVonNeumannGrid(): + fig, ax = draw_orthogonal_grid(space, agent_portrayal, propertylayer_portrayal, model) + case HexSingleGrid(): + fig, ax = draw_hex_grid(space, agent_portrayal, propertylayer_portrayal, model) + case HexSingleGrid(): + fig, ax = draw_hex_grid(space, agent_portrayal, propertylayer_portrayal, model) + case mesa.experimental.cell_space.HexGrid(): + fig, ax = draw_hex_grid(space, agent_portrayal, propertylayer_portrayal, model) + case mesa.space.ContinuousSpace(): + fig, ax = draw_continuous_space(space, agent_portrayal) case mesa.space.NetworkGrid(): fig, ax = draw_network(space, agent_portrayal) - # case VoronoiGrid(): - # _draw_voronoi(space, space_ax, agent_portrayal) - case OrthogonalMooreGrid(): # matches OrthogonalMooreGrid, OrthogonalVonNeumannGrid, and Hexgrid - fig, ax = draw_orthogonal_grid(space, agent_portrayal, propertylayer_portrayal) - case OrthogonalVonNeumannGrid(): # matches OrthogonalMooreGrid, OrthogonalVonNeumannGrid, and Hexgrid - fig, ax = draw_orthogonal_grid(space, agent_portrayal, propertylayer_portrayal) + case VoronoiGrid(): + fig, ax = draw_voroinoi_grid(space, agent_portrayal) case mesa.experimental.cell_space.Network(): fig, ax = draw_network(space, agent_portrayal) case None: + fig = Figure() + ax = fig.subplots(111) if propertylayer_portrayal: - draw_property_layers(space_ax, space, propertylayer_portrayal, model) + draw_property_layers(ax, space, propertylayer_portrayal, model) solara.FigureMatplotlib( fig, format="png", bbox_inches="tight", dependencies=dependencies @@ -172,7 +191,7 @@ def collect_agent_data(space: OrthogonalGrid | HexGrid | Network | ContinuousSpa """ default_size = (180 / max(space.width, space.height)) ** 2 - arguments = dict(x=[], y=[], s=[], c=[], marker=[]) + arguments = {"x":[], "y":[], "s":[], "c":[], "marker":[]} for agent in space.agents: portray = agent_portrayal(agent) loc = agent.pos @@ -182,17 +201,31 @@ def collect_agent_data(space: OrthogonalGrid | HexGrid | Network | ContinuousSpa arguments["x"].append(x) arguments["y"].append(y) - arguments['s'].append(portray.get("s", default_size)) - arguments['c'].append(portray.get("c", "tab:blue")) - arguments['marker'].append(portray.get("marker", "o")) + arguments["s"].append(portray.get("s", default_size)) + arguments["c"].append(portray.get("c", "tab:blue")) + arguments["marker"].append(portray.get("marker", "o")) return {k: np.asarray(v) for k, v in arguments.items()} -def draw_orthogonal_grid(space: OrthogonalGrid, agent_portrayal: Callable, propertylayer_portrayal: Callable): +def draw_orthogonal_grid(space: OrthogonalGrid, agent_portrayal: Callable, propertylayer_portrayal: Callable, model): + """Visualize a orthogonal grid.. + + Args: + space: the space to visualize + agent_portrayal: a callable that is called with the agent and returns a dict + propertylayer_portrayal: a callable that is called with the agent and returns a dict + model: a model instance + + Returns: + A Figure and Axes instance + + """ arguments = collect_agent_data(space, agent_portrayal) - fig, ax = plt.subplots() + fig = Figure() + ax = fig.add_subplot(111) + ax.set_xlim(-0.5, space.width-0.5) ax.set_ylim(-0.5, space.height-0.5) @@ -202,26 +235,54 @@ def draw_orthogonal_grid(space: OrthogonalGrid, agent_portrayal: Callable, prope for y in np.arange(-0.5, space.height-0.5, 1): ax.axhline(y, color="gray", linestyle=":") - x = arguments.pop('x') - y = arguments.pop('y') - marker = arguments.pop('marker') + _scatter(ax, arguments) - for mark in np.unique(marker): - mask = marker == mark - ax.scatter(x[mask], y[mask], marker=mark, **{k: v[mask] for k, v in arguments.items()}) + if propertylayer_portrayal: + draw_property_layers(ax, space, propertylayer_portrayal, model) return fig, ax -# def draw_hex_grid(space: HexGrid, agent_portrayal: Callable, propertylayer_portrayal: Callable): -# ... -# +def draw_hex_grid(space: HexGrid, agent_portrayal: Callable, propertylayer_portrayal: Callable, model): + """Visualize a hex grid. + + Args: + space: the space to visualize + agent_portrayal: a callable that is called with the agent and returns a dict + propertylayer_portrayal: a callable that is called with the agent and returns a dict + model: a model instance + + Returns: + A Figure and Axes instance + + """ + arguments = collect_agent_data(space, agent_portrayal) + + # give all odd rows an offset in the x direction + logical = np.mod(arguments["y"], 2) == 1 + arguments["x"] = arguments["x"][logical] + 1 + + fig = Figure() + ax = fig.add_subplot(111) + + _scatter(ax, arguments) + + if propertylayer_portrayal: + draw_property_layers(ax, space, propertylayer_portrayal, model) + return fig, ax def collect_agent_data_for_networks(space, agent_portrayal): + """Collect the plotting data for all agents in the network. + + Args: + space: the space to visualize + agent_portrayal: a callable that is called with the agent and returns a dict + + """ arguments = defaultdict(list) for agent in space.agents: portray = agent_portrayal(agent) - for k, v in agent_portrayal.items(): + for k, v in portray.items(): arguments[k].append(v) return arguments @@ -254,80 +315,57 @@ def draw_network(space: Network, agent_portrayal: Callable): return fig, ax -# def draw_continuous_space(space: ContinuousSpace, agent_portrayal: Callable): -# ... -# -# def draw_voroinoi_grid(space: VoronoiGrid, agent_portrayal: Callable, propertylayer_portrayal: Callable): -# ... - - - - -# def _draw_continuous_space(space, space_ax, agent_portrayal, model): -# def portray(space): -# x = [] -# y = [] -# s = [] # size -# c = [] # color -# m = [] # shape -# for agent in space._agent_to_index: -# data = agent_portrayal(agent) -# _x, _y = agent.pos -# x.append(_x) -# y.append(_y) -# -# # This is matplotlib's default marker size -# default_size = 20 -# size = data.get("size", default_size) -# s.append(size) -# color = data.get("color", "tab:blue") -# c.append(color) -# mark = data.get("shape", "o") -# m.append(mark) -# return {"x": x, "y": y, "s": s, "c": c, "m": m} -# -# # Determine border style based on space.torus -# border_style = "solid" if not space.torus else (0, (5, 10)) -# -# # Set the border of the plot -# for spine in space_ax.spines.values(): -# spine.set_linewidth(1.5) -# spine.set_color("black") -# spine.set_linestyle(border_style) -# -# width = space.x_max - space.x_min -# x_padding = width / 20 -# height = space.y_max - space.y_min -# y_padding = height / 20 -# space_ax.set_xlim(space.x_min - x_padding, space.x_max + x_padding) -# space_ax.set_ylim(space.y_min - y_padding, space.y_max + y_padding) -# -# # Portray and scatter the agents in the space -# _split_and_scatter(portray(space), space_ax) - - -def _draw_voronoi(space, space_ax, agent_portrayal): - def portray(g): - x = [] - y = [] - s = [] # size - c = [] # color - - for cell in g.all_cells: - for agent in cell.agents: - data = agent_portrayal(agent) - x.append(cell.coordinate[0]) - y.append(cell.coordinate[1]) - if "size" in data: - s.append(data["size"]) - if "color" in data: - c.append(data["color"]) - out = {"x": x, "y": y} - out["s"] = s - if len(c) > 0: - out["c"] = c - - return out +def draw_continuous_space(space: ContinuousSpace, agent_portrayal: Callable): + """Visualize a continuous space. + + Args: + space: the space to visualize + agent_portrayal: a callable that is called with the agent and returns a dict + + Returns: + A Figure and Axes instance + + """ + arguments = collect_agent_data(space, agent_portrayal) + + fig = Figure() + ax = fig.add_subplot(111) + + border_style = "solid" if not space.torus else (0, (5, 10)) + + # Set the border of the plot + for spine in ax.spines.values(): + spine.set_linewidth(1.5) + spine.set_color("black") + spine.set_linestyle(border_style) + + width = space.x_max - space.x_min + x_padding = width / 20 + height = space.y_max - space.y_min + y_padding = height / 20 + + ax.set_xlim(space.x_min - x_padding, space.x_max + x_padding) + ax.set_ylim(space.y_min - y_padding, space.y_max + y_padding) + + _scatter(ax, arguments) + return fig, ax + + +def draw_voroinoi_grid(space: VoronoiGrid, agent_portrayal: Callable): + """Visualize a voronoi grid. + + Args: + space: the space to visualize + agent_portrayal: a callable that is called with the agent and returns a dict + + Returns: + A Figure and Axes instance + + """ + arguments = collect_agent_data(space, agent_portrayal) + + fig = Figure() + ax = fig.add_subplot(111) x_list = [i[0] for i in space.centroids_coordinates] y_list = [i[1] for i in space.centroids_coordinates] @@ -340,20 +378,32 @@ def portray(g): x_padding = width / 20 height = y_max - y_min y_padding = height / 20 - space_ax.set_xlim(x_min - x_padding, x_max + x_padding) - space_ax.set_ylim(y_min - y_padding, y_max + y_padding) - space_ax.scatter(**portray(space)) + ax.set_xlim(x_min - x_padding, x_max + x_padding) + ax.set_ylim(y_min - y_padding, y_max + y_padding) + + _scatter(ax, arguments) + for cell in space.all_cells: polygon = cell.properties["polygon"] - space_ax.fill( + ax.fill( *zip(*polygon), alpha=min(1, cell.properties[space.cell_coloring_property]), - c="red", + c="red", zorder=0 ) # Plot filled polygon - space_ax.plot(*zip(*polygon), color="black") # Plot polygon edges in black + ax.plot(*zip(*polygon), color="black") # Plot polygon edges in black + return fig, ax + +def _scatter(ax, arguments): + x = arguments.pop("x") + y = arguments.pop("y") + marker = arguments.pop("marker") + + for mark in np.unique(marker): + mask = marker == mark + ax.scatter(x[mask], y[mask], marker=mark, **{k: v[mask] for k, v in arguments.items()}) def make_plot_measure(measure: str | dict[str, str] | list[str] | tuple[str]): From 9f543da57c4a745b8d050bafc1fec80e8531eb97 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 27 Oct 2024 10:25:58 +0000 Subject: [PATCH 06/74] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/examples/advanced/wolf_sheep/app.py | 7 +- mesa/space.py | 163 ++++++++++---------- mesa/visualization/components/matplotlib.py | 70 ++++++--- mesa/visualization/components/util.py | 37 +++-- 4 files changed, 152 insertions(+), 125 deletions(-) diff --git a/mesa/examples/advanced/wolf_sheep/app.py b/mesa/examples/advanced/wolf_sheep/app.py index ab581048337..46f577b4ba0 100644 --- a/mesa/examples/advanced/wolf_sheep/app.py +++ b/mesa/examples/advanced/wolf_sheep/app.py @@ -1,6 +1,9 @@ -import sys import os.path -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../../'))) +import sys + +sys.path.insert( + 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../")) +) from mesa.examples.advanced.wolf_sheep.agents import GrassPatch, Sheep, Wolf diff --git a/mesa/space.py b/mesa/space.py index 5881baa6934..49fb1c9b880 100644 --- a/mesa/space.py +++ b/mesa/space.py @@ -139,7 +139,7 @@ def __init__(self, width: int, height: int, torus: bool) -> None: # Cutoff used inside self.move_to_empty. The parameters are fitted on Python # 3.11 and it was verified that they are roughly the same for 3.10. Refer to # the code in PR#1565 to check for their stability when a new release gets out. - self.cutoff_empties = 7.953 * self.num_cells ** 0.384 + self.cutoff_empties = 7.953 * self.num_cells**0.384 @staticmethod def default_val() -> None: @@ -162,8 +162,7 @@ def build_empties(self) -> None: self._empties_built = True @overload - def __getitem__(self, index: int | Sequence[Coordinate]) -> list[GridContent]: - ... + def __getitem__(self, index: int | Sequence[Coordinate]) -> list[GridContent]: ... @property def agents(self) -> AgentSet: @@ -187,9 +186,8 @@ def agents(self) -> AgentSet: @overload def __getitem__( - self, index: tuple[int | slice, int | slice] - ) -> GridContent | list[GridContent]: - ... + self, index: tuple[int | slice, int | slice] + ) -> GridContent | list[GridContent]: ... def __getitem__(self, index): """Access contents from the grid.""" @@ -235,11 +233,11 @@ def coord_iter(self) -> Iterator[tuple[GridContent, Coordinate]]: yield self._grid[row][col], (row, col) # agent, position def iter_neighborhood( - self, - pos: Coordinate, - moore: bool, - include_center: bool = False, - radius: int = 1, + self, + pos: Coordinate, + moore: bool, + include_center: bool = False, + radius: int = 1, ) -> Iterator[Coordinate]: """Return an iterator over cell coordinates that are in the neighborhood of a certain point. @@ -262,11 +260,11 @@ def iter_neighborhood( yield from self.get_neighborhood(pos, moore, include_center, radius) def get_neighborhood( - self, - pos: Coordinate, - moore: bool, - include_center: bool = False, - radius: int = 1, + self, + pos: Coordinate, + moore: bool, + include_center: bool = False, + radius: int = 1, ) -> Sequence[Coordinate]: """Return a list of cells that are in the neighborhood of a certain point. @@ -301,10 +299,10 @@ def get_neighborhood( # First we check if the neighborhood is inside the grid if ( - x >= radius - and self.width - x > radius - and y >= radius - and self.height - y > radius + x >= radius + and self.width - x > radius + and y >= radius + and self.height - y > radius ): # If the radius is smaller than the distance from the borders, we # can skip boundary checks. @@ -345,11 +343,11 @@ def get_neighborhood( return tuple(neighborhood.keys()) def iter_neighbors( - self, - pos: Coordinate, - moore: bool, - include_center: bool = False, - radius: int = 1, + self, + pos: Coordinate, + moore: bool, + include_center: bool = False, + radius: int = 1, ) -> Iterator[Agent]: """Return an iterator over neighbors to a certain point. @@ -375,11 +373,11 @@ def iter_neighbors( yield cell def get_neighbors( - self, - pos: Coordinate, - moore: bool, - include_center: bool = False, - radius: int = 1, + self, + pos: Coordinate, + moore: bool, + include_center: bool = False, + radius: int = 1, ) -> list[Agent]: """Return a list of neighbors to a certain point. @@ -417,7 +415,7 @@ def out_of_bounds(self, pos: Coordinate) -> bool: @accept_tuple_argument def iter_cell_list_contents( - self, cell_list: Iterable[Coordinate] + self, cell_list: Iterable[Coordinate] ) -> Iterator[Agent]: """Returns an iterator of the agents contained in the cells identified in `cell_list`; cells with empty content are excluded. @@ -445,11 +443,9 @@ def get_cell_list_contents(self, cell_list: Iterable[Coordinate]) -> list[Agent] """ return list(self.iter_cell_list_contents(cell_list)) - def place_agent(self, agent: Agent, pos: Coordinate) -> None: - ... + def place_agent(self, agent: Agent, pos: Coordinate) -> None: ... - def remove_agent(self, agent: Agent) -> None: - ... + def remove_agent(self, agent: Agent) -> None: ... def move_agent(self, agent: Agent, pos: Coordinate) -> None: """Move an agent from its current position to a new position. @@ -464,11 +460,11 @@ def move_agent(self, agent: Agent, pos: Coordinate) -> None: self.place_agent(agent, pos) def move_agent_to_one_of( - self, - agent: Agent, - pos: list[Coordinate], - selection: str = "random", - handle_empty: str | None = None, + self, + agent: Agent, + pos: list[Coordinate], + selection: str = "random", + handle_empty: str | None = None, ) -> None: """Move an agent to one of the given positions. @@ -528,7 +524,7 @@ def _distance_squared(self, pos1: Coordinate, pos2: Coordinate) -> float: if self.torus: dx = min(dx, self.width - dx) dy = min(dy, self.height - dy) - return dx ** 2 + dy ** 2 + return dx**2 + dy**2 def swap_pos(self, agent_a: Agent, agent_b: Agent) -> None: """Swap agents positions.""" @@ -587,8 +583,8 @@ def exists_empty_cells(self) -> bool: def is_single_argument_function(function): """Check if a function is a single argument function.""" return ( - inspect.isfunction(function) - and len(inspect.signature(function).parameters) == 1 + inspect.isfunction(function) + and len(inspect.signature(function).parameters) == 1 ) @@ -614,7 +610,7 @@ class PropertyLayer: propertylayer_experimental_warning_given = False def __init__( - self, name: str, width: int, height: int, default_value, dtype=np.float64 + self, name: str, width: int, height: int, default_value, dtype=np.float64 ): """Initializes a new PropertyLayer instance. @@ -642,7 +638,7 @@ def __init__( # Check that width and height are positive integers if (not isinstance(width, int) or width < 1) or ( - not isinstance(height, int) or height < 1 + not isinstance(height, int) or height < 1 ): raise ValueError( f"Width and height must be positive integers, got {width} and {height}." @@ -690,8 +686,8 @@ def set_cells(self, value, condition=None): condition_result = vectorized_condition(self.data) if ( - not isinstance(condition_result, np.ndarray) - or condition_result.shape != self.data.shape + not isinstance(condition_result, np.ndarray) + or condition_result.shape != self.data.shape ): raise ValueError( "Result of condition must be a NumPy array with the same shape as the grid." @@ -810,11 +806,11 @@ class _PropertyGrid(_Grid): """ def __init__( - self, - width: int, - height: int, - torus: bool, - property_layers: None | PropertyLayer | list[PropertyLayer] = None, + self, + width: int, + height: int, + torus: bool, + property_layers: None | PropertyLayer | list[PropertyLayer] = None, ): """Initializes a new _PropertyGrid instance with specified dimensions and optional property layers. @@ -881,7 +877,7 @@ def remove_property_layer(self, property_name: str): del self.properties[property_name] def get_neighborhood_mask( - self, pos: Coordinate, moore: bool, include_center: bool, radius: int + self, pos: Coordinate, moore: bool, include_center: bool, radius: int ) -> np.ndarray: """Generate a boolean mask representing the neighborhood. @@ -905,12 +901,12 @@ def get_neighborhood_mask( return mask def select_cells( - self, - conditions: dict | None = None, - extreme_values: dict | None = None, - masks: np.ndarray | list[np.ndarray] = None, - only_empty: bool = False, - return_list: bool = True, + self, + conditions: dict | None = None, + extreme_values: dict | None = None, + masks: np.ndarray | list[np.ndarray] = None, + only_empty: bool = False, + return_list: bool = True, ) -> list[Coordinate] | np.ndarray: """Select cells based on property conditions, extreme values, and/or masks, with an option to only select empty cells. @@ -1066,11 +1062,11 @@ def remove_agent(self, agent: Agent) -> None: agent.pos = None def iter_neighbors( # noqa: D102 - self, - pos: Coordinate, - moore: bool, - include_center: bool = False, - radius: int = 1, + self, + pos: Coordinate, + moore: bool, + include_center: bool = False, + radius: int = 1, ) -> Iterator[Agent]: return itertools.chain.from_iterable( super().iter_neighbors(pos, moore, include_center, radius) @@ -1078,7 +1074,7 @@ def iter_neighbors( # noqa: D102 @accept_tuple_argument def iter_cell_list_contents( - self, cell_list: Iterable[Coordinate] + self, cell_list: Iterable[Coordinate] ) -> Iterator[Agent]: """Returns an iterator of the agents contained in the cells identified in `cell_list`. @@ -1118,7 +1114,7 @@ def torus_adj_2d(self, pos: Coordinate) -> Coordinate: return pos[0] % self.width, pos[1] % self.height def get_neighborhood( - self, pos: Coordinate, include_center: bool = False, radius: int = 1 + self, pos: Coordinate, include_center: bool = False, radius: int = 1 ) -> list[Coordinate]: """Return a list of coordinates that are in the neighborhood of a certain point. @@ -1206,7 +1202,7 @@ def get_neighborhood( return neighborhood def iter_neighborhood( - self, pos: Coordinate, include_center: bool = False, radius: int = 1 + self, pos: Coordinate, include_center: bool = False, radius: int = 1 ) -> Iterator[Coordinate]: """Return an iterator over cell coordinates that are in the neighborhood of a certain point. @@ -1222,7 +1218,7 @@ def iter_neighborhood( yield from self.get_neighborhood(pos, include_center, radius) def iter_neighbors( - self, pos: Coordinate, include_center: bool = False, radius: int = 1 + self, pos: Coordinate, include_center: bool = False, radius: int = 1 ) -> Iterator[Agent]: """Return an iterator over neighbors to a certain point. @@ -1240,7 +1236,7 @@ def iter_neighbors( return self.iter_cell_list_contents(neighborhood) def get_neighbors( - self, pos: Coordinate, include_center: bool = False, radius: int = 1 + self, pos: Coordinate, include_center: bool = False, radius: int = 1 ) -> list[Agent]: """Return a list of neighbors to a certain point. @@ -1258,8 +1254,8 @@ def get_neighbors( def __iter__(self) -> Iterator[GridContent]: """Create an iterator returns the agents in the space""" - for entry in self._agent_to_index: - yield entry + yield from self._agent_to_index + class HexSingleGrid(_HexGrid, SingleGrid): """Hexagonal SingleGrid: a SingleGrid where neighbors are computed according to a hexagonal tiling of the grid. @@ -1340,12 +1336,12 @@ class ContinuousSpace: """ def __init__( - self, - x_max: float, - y_max: float, - torus: bool, - x_min: float = 0, - y_min: float = 0, + self, + x_max: float, + y_max: float, + torus: bool, + x_min: float = 0, + y_min: float = 0, ) -> None: """Create a new continuous space. @@ -1442,7 +1438,7 @@ def remove_agent(self, agent: Agent) -> None: agent.pos = None def get_neighbors( - self, pos: FloatCoordinate, radius: float, include_center: bool = True + self, pos: FloatCoordinate, radius: float, include_center: bool = True ) -> list[Agent]: """Get all agents within a certain radius. @@ -1462,14 +1458,14 @@ def get_neighbors( deltas = np.minimum(deltas, self.size - deltas) dists = deltas[:, 0] ** 2 + deltas[:, 1] ** 2 - (idxs,) = np.where(dists <= radius ** 2) + (idxs,) = np.where(dists <= radius**2) neighbors = [ self._index_to_agent[x] for x in idxs if include_center or dists[x] > 0 ] return neighbors def get_heading( - self, pos_1: FloatCoordinate, pos_2: FloatCoordinate + self, pos_1: FloatCoordinate, pos_2: FloatCoordinate ) -> FloatCoordinate: """Get the heading vector between two points, accounting for toroidal space. @@ -1546,8 +1542,7 @@ def out_of_bounds(self, pos: FloatCoordinate) -> bool: def __iter__(self) -> Iterator[GridContent]: """Create an iterator returns the agents in the space""" - for entry in self._agent_to_index: - yield entry + yield from self._agent_to_index class NetworkGrid: @@ -1596,7 +1591,7 @@ def place_agent(self, agent: Agent, node_id: int) -> None: agent.pos = node_id def get_neighborhood( - self, node_id: int, include_center: bool = False, radius: int = 1 + self, node_id: int, include_center: bool = False, radius: int = 1 ) -> list[int]: """Get all adjacent nodes within a certain radius. @@ -1622,7 +1617,7 @@ def get_neighborhood( return neighborhood def get_neighbors( - self, node_id: int, include_center: bool = False, radius: int = 1 + self, node_id: int, include_center: bool = False, radius: int = 1 ) -> list[Agent]: """Get all agents in adjacent nodes (within a certain radius). diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 38400d10c32..bba07d420bd 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -45,6 +45,7 @@ def make_space_matplotlib(agent_portrayal=None, propertylayer_portrayal=None): function: A function that creates a SpaceMatplotlib component """ if agent_portrayal is None: + def agent_portrayal(a): return {"id": a.unique_id} @@ -56,10 +57,10 @@ def MakeSpaceMatplotlib(model): @solara.component def SpaceMatplotlib( - model, - agent_portrayal, - propertylayer_portrayal, - dependencies: list[any] | None = None, + model, + agent_portrayal, + propertylayer_portrayal, + dependencies: list[any] | None = None, ): """Create a Matplotlib-based space visualization component.""" update_counter.get() @@ -71,17 +72,29 @@ def SpaceMatplotlib( # https://stackoverflow.com/questions/67524641/convert-multiple-isinstance-checks-to-structural-pattern-matching match space: case mesa.space._Grid(): - fig, ax = draw_orthogonal_grid(space, agent_portrayal, propertylayer_portrayal, model) + fig, ax = draw_orthogonal_grid( + space, agent_portrayal, propertylayer_portrayal, model + ) case OrthogonalMooreGrid(): - fig, ax = draw_orthogonal_grid(space, agent_portrayal, propertylayer_portrayal, model) + fig, ax = draw_orthogonal_grid( + space, agent_portrayal, propertylayer_portrayal, model + ) case OrthogonalVonNeumannGrid(): - fig, ax = draw_orthogonal_grid(space, agent_portrayal, propertylayer_portrayal, model) + fig, ax = draw_orthogonal_grid( + space, agent_portrayal, propertylayer_portrayal, model + ) case HexSingleGrid(): - fig, ax = draw_hex_grid(space, agent_portrayal, propertylayer_portrayal, model) + fig, ax = draw_hex_grid( + space, agent_portrayal, propertylayer_portrayal, model + ) case HexSingleGrid(): - fig, ax = draw_hex_grid(space, agent_portrayal, propertylayer_portrayal, model) + fig, ax = draw_hex_grid( + space, agent_portrayal, propertylayer_portrayal, model + ) case mesa.experimental.cell_space.HexGrid(): - fig, ax = draw_hex_grid(space, agent_portrayal, propertylayer_portrayal, model) + fig, ax = draw_hex_grid( + space, agent_portrayal, propertylayer_portrayal, model + ) case mesa.space.ContinuousSpace(): fig, ax = draw_continuous_space(space, agent_portrayal) case mesa.space.NetworkGrid(): @@ -173,7 +186,10 @@ def draw_property_layers(ax, space, propertylayer_portrayal, model): ) -def collect_agent_data(space: OrthogonalGrid | HexGrid | Network | ContinuousSpace, agent_portrayal: Callable): +def collect_agent_data( + space: OrthogonalGrid | HexGrid | Network | ContinuousSpace, + agent_portrayal: Callable, +): """Collect the plotting data for all agents in the space. Args: @@ -191,7 +207,7 @@ def collect_agent_data(space: OrthogonalGrid | HexGrid | Network | ContinuousSpa """ default_size = (180 / max(space.width, space.height)) ** 2 - arguments = {"x":[], "y":[], "s":[], "c":[], "marker":[]} + arguments = {"x": [], "y": [], "s": [], "c": [], "marker": []} for agent in space.agents: portray = agent_portrayal(agent) loc = agent.pos @@ -208,7 +224,12 @@ def collect_agent_data(space: OrthogonalGrid | HexGrid | Network | ContinuousSpa return {k: np.asarray(v) for k, v in arguments.items()} -def draw_orthogonal_grid(space: OrthogonalGrid, agent_portrayal: Callable, propertylayer_portrayal: Callable, model): +def draw_orthogonal_grid( + space: OrthogonalGrid, + agent_portrayal: Callable, + propertylayer_portrayal: Callable, + model, +): """Visualize a orthogonal grid.. Args: @@ -226,13 +247,13 @@ def draw_orthogonal_grid(space: OrthogonalGrid, agent_portrayal: Callable, prope fig = Figure() ax = fig.add_subplot(111) - ax.set_xlim(-0.5, space.width-0.5) - ax.set_ylim(-0.5, space.height-0.5) + ax.set_xlim(-0.5, space.width - 0.5) + ax.set_ylim(-0.5, space.height - 0.5) # Draw grid lines - for x in np.arange(-0.5, space.width-0.5, 1): + for x in np.arange(-0.5, space.width - 0.5, 1): ax.axvline(x, color="gray", linestyle=":") - for y in np.arange(-0.5, space.height-0.5, 1): + for y in np.arange(-0.5, space.height - 0.5, 1): ax.axhline(y, color="gray", linestyle=":") _scatter(ax, arguments) @@ -243,7 +264,9 @@ def draw_orthogonal_grid(space: OrthogonalGrid, agent_portrayal: Callable, prope return fig, ax -def draw_hex_grid(space: HexGrid, agent_portrayal: Callable, propertylayer_portrayal: Callable, model): +def draw_hex_grid( + space: HexGrid, agent_portrayal: Callable, propertylayer_portrayal: Callable, model +): """Visualize a hex grid. Args: @@ -271,6 +294,7 @@ def draw_hex_grid(space: HexGrid, agent_portrayal: Callable, propertylayer_portr draw_property_layers(ax, space, propertylayer_portrayal, model) return fig, ax + def collect_agent_data_for_networks(space, agent_portrayal): """Collect the plotting data for all agents in the network. @@ -287,6 +311,7 @@ def collect_agent_data_for_networks(space, agent_portrayal): return arguments + def draw_network(space: Network, agent_portrayal: Callable): """Visualize a network space. @@ -383,19 +408,18 @@ def draw_voroinoi_grid(space: VoronoiGrid, agent_portrayal: Callable): _scatter(ax, arguments) - for cell in space.all_cells: polygon = cell.properties["polygon"] ax.fill( *zip(*polygon), alpha=min(1, cell.properties[space.cell_coloring_property]), - c="red", zorder=0 + c="red", + zorder=0, ) # Plot filled polygon ax.plot(*zip(*polygon), color="black") # Plot polygon edges in black return fig, ax - def _scatter(ax, arguments): x = arguments.pop("x") y = arguments.pop("y") @@ -403,7 +427,9 @@ def _scatter(ax, arguments): for mark in np.unique(marker): mask = marker == mark - ax.scatter(x[mask], y[mask], marker=mark, **{k: v[mask] for k, v in arguments.items()}) + ax.scatter( + x[mask], y[mask], marker=mark, **{k: v[mask] for k, v in arguments.items()} + ) def make_plot_measure(measure: str | dict[str, str] | list[str] | tuple[str]): diff --git a/mesa/visualization/components/util.py b/mesa/visualization/components/util.py index 315b641affe..ae3461903b1 100644 --- a/mesa/visualization/components/util.py +++ b/mesa/visualization/components/util.py @@ -1,24 +1,26 @@ -from typing import Callable +from collections.abc import Callable -from mesa.space import _Grid, ContinuousSpace, _HexGrid, NetworkGrid from mesa.experimental.cell_space import DiscreteSpace +from mesa.space import ContinuousSpace, NetworkGrid, _Grid, _HexGrid - -__all__ = ["get_agent_continuous", - "get_agents_olstyle_grid", - "get_agents_newstyle_grid"] +__all__ = [ + "get_agent_continuous", + "get_agents_olstyle_grid", + "get_agents_newstyle_grid", +] OldStyleSpace = _Grid | ContinuousSpace | _HexGrid | NetworkGrid Space = OldStyleSpace | DiscreteSpace + def get_agents_olstyle_space(space: OldStyleSpace): for entry in space: if entry: yield entry + def get_agents_newstyle_space(space: DiscreteSpace): - for agent in space.all_cells.agents: - yield agent + yield from space.all_cells.agents def _get_oldstyle_agent_data(agents, agent_portrayal): @@ -74,18 +76,17 @@ def get_agent_data(space: Space, agent_portrayal: Callable): x.append(x_i) y.append(y_i) - s.append(data.get("size", None)) # we use None here as a flag to fall back on default + s.append( + data.get("size", None) + ) # we use None here as a flag to fall back on default c.append(data.get("color", "tab:blue")) m.append(data.get("shape", "o")) - - - -if __name__ == '__main__': +if __name__ == "__main__": from mesa import Agent, Model + from mesa.experimental.cell_space import CellAgent, OrthogonalMooreGrid from mesa.space import SingleGrid - from mesa.experimental.cell_space import OrthogonalMooreGrid, CellAgent model = Model() space = SingleGrid(10, 10, True) @@ -97,7 +98,6 @@ def get_agent_data(space: Space, agent_portrayal: Callable): agents = list(get_agents_olstyle_space(space)) assert len(agents) == 10 - model = Model() space = OrthogonalMooreGrid((10, 10), capacity=1, torus=True, random=model.random) for _ in range(10): @@ -112,8 +112,11 @@ def get_agent_data(space: Space, agent_portrayal: Callable): space = ContinuousSpace(10, 10, True) for _ in range(10): agent = Agent(model) - pos = (agent.random.random()*space.width, agent.random.random()*space.height) + pos = ( + agent.random.random() * space.width, + agent.random.random() * space.height, + ) space.place_agent(agent, pos) agents = list(get_agents_olstyle_space(space)) - assert len(agents) == 10 \ No newline at end of file + assert len(agents) == 10 From 355ab0dd182651256c18cc4c8945c1c16b3e6e18 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Sun, 27 Oct 2024 11:26:46 +0100 Subject: [PATCH 07/74] Update matplotlib.py --- mesa/visualization/components/matplotlib.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index bba07d420bd..cf37f036d58 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -1,5 +1,7 @@ """Matplotlib based solara components for visualization MESA spaces and plots.""" + + import warnings from collections import defaultdict from collections.abc import Callable From 4436072159281cb51fab033a404450fafc1831b9 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Sun, 27 Oct 2024 11:27:26 +0100 Subject: [PATCH 08/74] undo space changes this has become outdated with adding space.agents --- mesa/space.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/mesa/space.py b/mesa/space.py index 49fb1c9b880..df501b8639c 100644 --- a/mesa/space.py +++ b/mesa/space.py @@ -1252,10 +1252,6 @@ def get_neighbors( """ return list(self.iter_neighbors(pos, include_center, radius)) - def __iter__(self) -> Iterator[GridContent]: - """Create an iterator returns the agents in the space""" - yield from self._agent_to_index - class HexSingleGrid(_HexGrid, SingleGrid): """Hexagonal SingleGrid: a SingleGrid where neighbors are computed according to a hexagonal tiling of the grid. @@ -1540,10 +1536,6 @@ def out_of_bounds(self, pos: FloatCoordinate) -> bool: x, y = pos return x < self.x_min or x >= self.x_max or y < self.y_min or y >= self.y_max - def __iter__(self) -> Iterator[GridContent]: - """Create an iterator returns the agents in the space""" - yield from self._agent_to_index - class NetworkGrid: """Network Grid where each node contains zero or more agents.""" @@ -1697,8 +1689,3 @@ def iter_cell_list_contents(self, cell_list: list[int]) -> Iterator[Agent]: self.G.nodes[node_id]["agent"] for node_id in itertools.filterfalse(self.is_cell_empty, cell_list) ) - - def __iter__(self) -> Iterator[GridContent]: - """Create an iterator returns the agents in the space""" - for node_id in self.G.nodes: - yield self.g.nodes[node_id]["agent"] From 022cfdcdb470014406cff7d2d4d2ba5bd71dcfbc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 27 Oct 2024 10:27:50 +0000 Subject: [PATCH 09/74] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/visualization/components/matplotlib.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index cf37f036d58..bba07d420bd 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -1,7 +1,5 @@ """Matplotlib based solara components for visualization MESA spaces and plots.""" - - import warnings from collections import defaultdict from collections.abc import Callable From aad49e2efad5e13d05e14a03adc0826b99419bce Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Sun, 27 Oct 2024 18:02:37 +0100 Subject: [PATCH 10/74] Delete util.py --- mesa/visualization/components/util.py | 122 -------------------------- 1 file changed, 122 deletions(-) delete mode 100644 mesa/visualization/components/util.py diff --git a/mesa/visualization/components/util.py b/mesa/visualization/components/util.py deleted file mode 100644 index ae3461903b1..00000000000 --- a/mesa/visualization/components/util.py +++ /dev/null @@ -1,122 +0,0 @@ -from collections.abc import Callable - -from mesa.experimental.cell_space import DiscreteSpace -from mesa.space import ContinuousSpace, NetworkGrid, _Grid, _HexGrid - -__all__ = [ - "get_agent_continuous", - "get_agents_olstyle_grid", - "get_agents_newstyle_grid", -] - -OldStyleSpace = _Grid | ContinuousSpace | _HexGrid | NetworkGrid -Space = OldStyleSpace | DiscreteSpace - - -def get_agents_olstyle_space(space: OldStyleSpace): - for entry in space: - if entry: - yield entry - - -def get_agents_newstyle_space(space: DiscreteSpace): - yield from space.all_cells.agents - - -def _get_oldstyle_agent_data(agents, agent_portrayal): - """Helper function to get agent data for visualization.""" - x, y, s, c, m = [], [], [], [], [] - for agent in agents: - data = agent_portrayal(agent) - x.append(pos[0] + 0.5) # Center the agent in the cell - y.append(pos[1] + 0.5) # Center the agent in the cell - default_size = (180 / max(space.width, space.height)) ** 2 - s.append(data.get("size", default_size)) - c.append(data.get("color", "tab:blue")) - m.append(data.get("shape", "o")) - return {"x": x, "y": y, "s": s, "c": c, "m": m} - - -# fixme: -# how we get the data depends on the space and I don't like it. -# can't we separate more cleanly the data and any post -# space specific refinements? -# or gather data depending on the space? -# fixme: -# what routes do we have? -# network (2 options) -# continuous (1 option) -# orthogonal grids (7 options?) -# voronoi (1 option) - - -def get_agent_data(space: Space, agent_portrayal: Callable): - # fixme should we cover networks here at all? - - match space: - case _Grid(): # matches also hexgrids - agents = get_agents_olstyle_space(space) - case ContinuousSpace(): - agents = get_agents_olstyle_space(space) - case NetworkGrid(): - agents = get_agents_olstyle_space(space) - case DiscreteSpace(): - agents = get_agents_newstyle_space(space) - case _: - raise NotImplementedError(f"Unknown space of type {type(space)}") - - x, y, s, c, m = [], [], [], [], [] - for agent in agents: - data = agent_portrayal(agent) - - if isinstance(space, DiscreteSpace): - x_i, y_i = agent.cell.coordinate - else: - x_i, y_i = agent.pos - - x.append(x_i) - y.append(y_i) - s.append( - data.get("size", None) - ) # we use None here as a flag to fall back on default - c.append(data.get("color", "tab:blue")) - m.append(data.get("shape", "o")) - - -if __name__ == "__main__": - from mesa import Agent, Model - from mesa.experimental.cell_space import CellAgent, OrthogonalMooreGrid - from mesa.space import SingleGrid - - model = Model() - space = SingleGrid(10, 10, True) - for _ in range(10): - agent = Agent(model) - pos = model.random.choice(list(space.empties)) - space.place_agent(agent, pos) - - agents = list(get_agents_olstyle_space(space)) - assert len(agents) == 10 - - model = Model() - space = OrthogonalMooreGrid((10, 10), capacity=1, torus=True, random=model.random) - for _ in range(10): - agent = CellAgent(model) - cell = space.select_random_empty_cell() - agent.cell = cell - - agents = list(get_agents_newstyle_space(space)) - assert len(agents) == 10 - - model = Model() - space = ContinuousSpace(10, 10, True) - for _ in range(10): - agent = Agent(model) - pos = ( - agent.random.random() * space.width, - agent.random.random() * space.height, - ) - space.place_agent(agent, pos) - - agents = list(get_agents_olstyle_space(space)) - assert len(agents) == 10 From 5c2ddcbaebc6844c36a6f52ebbd41c57b340637e Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Sun, 27 Oct 2024 18:03:00 +0100 Subject: [PATCH 11/74] add width and height property to voroinoi --- mesa/experimental/cell_space/voronoi.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/mesa/experimental/cell_space/voronoi.py b/mesa/experimental/cell_space/voronoi.py index 2cde122cbb6..7239ff0cc72 100644 --- a/mesa/experimental/cell_space/voronoi.py +++ b/mesa/experimental/cell_space/voronoi.py @@ -208,6 +208,20 @@ def __init__( self._connect_cells() self._build_cell_polygons() + @property + def width(self): + x_list = [i[0] for i in self.centroids_coordinates] + x_max = max(x_list) + x_min = min(x_list) + return x_max - x_min + + @property + def height(self): + y_list = [i[1] for i in self.centroids_coordinates] + y_max = max(y_list) + y_min = min(y_list) + return y_max - y_min + def _connect_cells(self) -> None: """Connect cells to neighbors based on given centroids and using Delaunay Triangulation.""" self.triangulation = Delaunay() From 327b5398419f619b369329cb59c7ab2c0434ee43 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Sun, 27 Oct 2024 18:03:08 +0100 Subject: [PATCH 12/74] add tests --- mesa/visualization/components/matplotlib.py | 13 +- tests/test_components_matplotlib.py | 132 ++++++++++++++++++++ 2 files changed, 140 insertions(+), 5 deletions(-) create mode 100644 tests/test_components_matplotlib.py diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index bba07d420bd..d9002845be8 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -227,7 +227,7 @@ def collect_agent_data( def draw_orthogonal_grid( space: OrthogonalGrid, agent_portrayal: Callable, - propertylayer_portrayal: Callable, + propertylayer_portrayal: Callable | None, model, ): """Visualize a orthogonal grid.. @@ -283,7 +283,7 @@ def draw_hex_grid( # give all odd rows an offset in the x direction logical = np.mod(arguments["y"], 2) == 1 - arguments["x"] = arguments["x"][logical] + 1 + arguments["x"][logical] += + 1 fig = Figure() ax = fig.add_subplot(111) @@ -321,13 +321,16 @@ def draw_network(space: Network, agent_portrayal: Callable): Notes: this uses networkx.draw under the hood so agent portrayal fields should match those used there - i.e., node_side, node_color, node_shape, edge_color + i.e., node_size and node_color. """ - arguments = collect_agent_data_for_networks(agent_portrayal) + # FIXME this plotting is not correct. The x,y coordinates reflect the nodes in the network + # not the location of the agents on those nodes + arguments = collect_agent_data_for_networks(space, agent_portrayal) - fig, ax = plt.subplots() + fig = Figure() + ax = fig.add_subplot(111) graph = space.G pos = nx.spring_layout(graph, seed=0) nx.draw( diff --git a/tests/test_components_matplotlib.py b/tests/test_components_matplotlib.py new file mode 100644 index 00000000000..591fa3b1d5b --- /dev/null +++ b/tests/test_components_matplotlib.py @@ -0,0 +1,132 @@ +"""tests for matplotlib components.""" + +from mesa import Agent, Model +from mesa.experimental.cell_space import ( + CellAgent, + HexGrid, + OrthogonalMooreGrid, + VoronoiGrid, + Network +) +from mesa.space import ContinuousSpace, HexSingleGrid, SingleGrid, NetworkGrid +from mesa.visualization.components.matplotlib import ( + draw_continuous_space, + draw_hex_grid, + draw_orthogonal_grid, + draw_voroinoi_grid, + draw_network +) + + +def agent_portrayal(agent): + """Simple portrayal of an agent. + + Args: + agent (Agent): The agent to portray + + """ + return {"s": 10, + "c": "tab:blue", + "marker": "s" if (agent.unique_id % 2) == 0 else "o"} +def test_draw_hex_grid(): + """Test drawing hexgrids.""" + model = Model(seed=42) + grid = HexSingleGrid(10, 10, torus=True) + for _ in range(10): + agent = Agent(model) + grid.move_to_empty(agent) + draw_hex_grid(grid, agent_portrayal, None, model) + + + model = Model(seed=42) + grid = HexGrid((10, 10), torus=True, random=model.random, capacity=1) + for _ in range(10): + agent = CellAgent(model) + agent.cell = grid.select_random_empty_cell() + + draw_hex_grid(grid, agent_portrayal, None, model) + +def test_draw_voroinoi_grid(): + """Test drawing voroinoi grids.""" + model = Model(seed=42) + + coordinates = model.rng.random((100, 2)) * 10 + + grid = VoronoiGrid(coordinates.tolist(), random=model.random, capacity=1) + for _ in range(10): + agent = CellAgent(model) + agent.cell = grid.select_random_empty_cell() + + draw_voroinoi_grid(grid, agent_portrayal) + +def test_draw_orthogonal_grid(): + """Test drawing orthogonal grids.""" + model = Model(seed=42) + grid = SingleGrid(10, 10, torus=True) + for _ in range(10): + agent = Agent(model) + grid.move_to_empty(agent) + draw_orthogonal_grid(grid, agent_portrayal, None, model) + + + model = Model(seed=42) + grid = OrthogonalMooreGrid((10, 10), torus=True, random=model.random, capacity=1) + for _ in range(10): + agent = CellAgent(model) + agent.cell = grid.select_random_empty_cell() + + draw_orthogonal_grid(grid, agent_portrayal, None, model) + +def test_draw_continuous_space(): + """Test drawing continuous space.""" + model = Model(seed=42) + space = ContinuousSpace(10, 10, torus=True) + for _ in range(10): + x = model.random.random() * 10 + y = model.random.random() * 10 + agent = Agent(model) + space.place_agent(agent, (x, y)) + draw_continuous_space(space, agent_portrayal) + +def test_draw_network(): + """Test drawing network.""" + + def agent_portrayal(agent): + """Simple portrayal of an agent. + + Args: + agent (Agent): The agent to portray + + """ + return {"node_size": 10, + "node_color": "tab:blue"} + + import networkx as nx + + n = 10 + m = 20 + seed = 42 + graph = nx.gnm_random_graph(n, m, seed=seed) # noqa: N806 + + + model = Model(seed=42) + grid = NetworkGrid(graph) + for _ in range(10): + agent = Agent(model) + pos = agent.random.randint(0, len(graph.nodes)-1) + grid.place_agent(agent, pos) + draw_network(grid, agent_portrayal) + + + model = Model(seed=42) + grid = Network(graph, random=model.random, capacity=1) + for _ in range(10): + agent = CellAgent(model) + agent.cell = grid.select_random_empty_cell() + + draw_network(grid, agent_portrayal) + + +def test_draw_property_layers(): + """Test drawing property layers.""" + pass From d8a409ab69aeab5a2975a639be3673ff03741fce Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 27 Oct 2024 17:03:16 +0000 Subject: [PATCH 13/74] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/visualization/components/matplotlib.py | 2 +- tests/test_components_matplotlib.py | 32 +++++++++++---------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index d9002845be8..83b5d4ee2e8 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -283,7 +283,7 @@ def draw_hex_grid( # give all odd rows an offset in the x direction logical = np.mod(arguments["y"], 2) == 1 - arguments["x"][logical] += + 1 + arguments["x"][logical] += +1 fig = Figure() ax = fig.add_subplot(111) diff --git a/tests/test_components_matplotlib.py b/tests/test_components_matplotlib.py index 591fa3b1d5b..8284e48423f 100644 --- a/tests/test_components_matplotlib.py +++ b/tests/test_components_matplotlib.py @@ -4,17 +4,17 @@ from mesa.experimental.cell_space import ( CellAgent, HexGrid, + Network, OrthogonalMooreGrid, VoronoiGrid, - Network ) -from mesa.space import ContinuousSpace, HexSingleGrid, SingleGrid, NetworkGrid +from mesa.space import ContinuousSpace, HexSingleGrid, NetworkGrid, SingleGrid from mesa.visualization.components.matplotlib import ( draw_continuous_space, draw_hex_grid, + draw_network, draw_orthogonal_grid, draw_voroinoi_grid, - draw_network ) @@ -25,9 +25,13 @@ def agent_portrayal(agent): agent (Agent): The agent to portray """ - return {"s": 10, - "c": "tab:blue", - "marker": "s" if (agent.unique_id % 2) == 0 else "o"} + return { + "s": 10, + "c": "tab:blue", + "marker": "s" if (agent.unique_id % 2) == 0 else "o", + } + + def test_draw_hex_grid(): """Test drawing hexgrids.""" model = Model(seed=42) @@ -37,7 +41,6 @@ def test_draw_hex_grid(): grid.move_to_empty(agent) draw_hex_grid(grid, agent_portrayal, None, model) - model = Model(seed=42) grid = HexGrid((10, 10), torus=True, random=model.random, capacity=1) for _ in range(10): @@ -46,6 +49,7 @@ def test_draw_hex_grid(): draw_hex_grid(grid, agent_portrayal, None, model) + def test_draw_voroinoi_grid(): """Test drawing voroinoi grids.""" model = Model(seed=42) @@ -59,6 +63,7 @@ def test_draw_voroinoi_grid(): draw_voroinoi_grid(grid, agent_portrayal) + def test_draw_orthogonal_grid(): """Test drawing orthogonal grids.""" model = Model(seed=42) @@ -68,7 +73,6 @@ def test_draw_orthogonal_grid(): grid.move_to_empty(agent) draw_orthogonal_grid(grid, agent_portrayal, None, model) - model = Model(seed=42) grid = OrthogonalMooreGrid((10, 10), torus=True, random=model.random, capacity=1) for _ in range(10): @@ -77,6 +81,7 @@ def test_draw_orthogonal_grid(): draw_orthogonal_grid(grid, agent_portrayal, None, model) + def test_draw_continuous_space(): """Test drawing continuous space.""" model = Model(seed=42) @@ -88,6 +93,7 @@ def test_draw_continuous_space(): space.place_agent(agent, (x, y)) draw_continuous_space(space, agent_portrayal) + def test_draw_network(): """Test drawing network.""" @@ -98,26 +104,23 @@ def agent_portrayal(agent): agent (Agent): The agent to portray """ - return {"node_size": 10, - "node_color": "tab:blue"} + return {"node_size": 10, "node_color": "tab:blue"} import networkx as nx n = 10 m = 20 seed = 42 - graph = nx.gnm_random_graph(n, m, seed=seed) # noqa: N806 - + graph = nx.gnm_random_graph(n, m, seed=seed) model = Model(seed=42) grid = NetworkGrid(graph) for _ in range(10): agent = Agent(model) - pos = agent.random.randint(0, len(graph.nodes)-1) + pos = agent.random.randint(0, len(graph.nodes) - 1) grid.place_agent(agent, pos) draw_network(grid, agent_portrayal) - model = Model(seed=42) grid = Network(graph, random=model.random, capacity=1) for _ in range(10): @@ -129,4 +132,3 @@ def agent_portrayal(agent): def test_draw_property_layers(): """Test drawing property layers.""" - pass From c3ca9d7ec6009b6284afb48f9d0ffc60b8184de2 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Sun, 27 Oct 2024 20:27:50 +0100 Subject: [PATCH 14/74] add mesh to draw_hexgrid --- mesa/visualization/components/matplotlib.py | 31 ++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 83b5d4ee2e8..4b6f3f7fa44 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -1,6 +1,7 @@ """Matplotlib based solara components for visualization MESA spaces and plots.""" import warnings +import itertools from collections import defaultdict from collections.abc import Callable @@ -8,6 +9,12 @@ import networkx as nx import numpy as np import solara + +from matplotlib.collections import PatchCollection +from matplotlib.patches import RegularPolygon +import math + + from matplotlib.cm import ScalarMappable from matplotlib.colors import LinearSegmentedColormap, Normalize, to_rgba from matplotlib.figure import Figure @@ -282,14 +289,36 @@ def draw_hex_grid( arguments = collect_agent_data(space, agent_portrayal) # give all odd rows an offset in the x direction + offset = math.sqrt(0.75) + logical = np.mod(arguments["y"], 2) == 1 - arguments["x"][logical] += +1 + arguments["y"] = arguments["y"].astype(float) * offset + arguments["x"] = arguments["x"].astype(float) + arguments["x"][logical] += 0.5 fig = Figure() ax = fig.add_subplot(111) + ax.set_xlim(-1, space.width+0.5) + ax.set_ylim(-offset, space.height*offset) _scatter(ax, arguments) + def setup_hexmesh(width, height, ): + """Helper function for creating the hexmaesh.""" + # fixme: this should be done once, rather than in each update + + patches = [] + for x, y in itertools.product(range(width), range(height)): + if y % 2 == 1: + x += 0.5 + y *= offset + hex = RegularPolygon((x, y), numVertices=6, radius=math.sqrt(1 / 3), + orientation=np.radians(120)) + patches.append(hex) + mesh = PatchCollection(patches, edgecolor='k', facecolor=(1, 1, 1, 0), linestyle='dotted', lw=1) + return mesh + ax.add_collection(setup_hexmesh(space.width, space.height, )) + if propertylayer_portrayal: draw_property_layers(ax, space, propertylayer_portrayal, model) return fig, ax From 519670fb5b71fcb75e5299cd97eb900f917c73c0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 27 Oct 2024 19:28:20 +0000 Subject: [PATCH 15/74] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/visualization/components/matplotlib.py | 40 +++++++++++++-------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 4b6f3f7fa44..64f0e49b205 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -1,7 +1,8 @@ """Matplotlib based solara components for visualization MESA spaces and plots.""" -import warnings import itertools +import math +import warnings from collections import defaultdict from collections.abc import Callable @@ -9,15 +10,11 @@ import networkx as nx import numpy as np import solara - -from matplotlib.collections import PatchCollection -from matplotlib.patches import RegularPolygon -import math - - from matplotlib.cm import ScalarMappable +from matplotlib.collections import PatchCollection from matplotlib.colors import LinearSegmentedColormap, Normalize, to_rgba from matplotlib.figure import Figure +from matplotlib.patches import RegularPolygon import mesa from mesa.experimental.cell_space import ( @@ -298,12 +295,15 @@ def draw_hex_grid( fig = Figure() ax = fig.add_subplot(111) - ax.set_xlim(-1, space.width+0.5) - ax.set_ylim(-offset, space.height*offset) + ax.set_xlim(-1, space.width + 0.5) + ax.set_ylim(-offset, space.height * offset) _scatter(ax, arguments) - def setup_hexmesh(width, height, ): + def setup_hexmesh( + width, + height, + ): """Helper function for creating the hexmaesh.""" # fixme: this should be done once, rather than in each update @@ -312,12 +312,24 @@ def setup_hexmesh(width, height, ): if y % 2 == 1: x += 0.5 y *= offset - hex = RegularPolygon((x, y), numVertices=6, radius=math.sqrt(1 / 3), - orientation=np.radians(120)) + hex = RegularPolygon( + (x, y), + numVertices=6, + radius=math.sqrt(1 / 3), + orientation=np.radians(120), + ) patches.append(hex) - mesh = PatchCollection(patches, edgecolor='k', facecolor=(1, 1, 1, 0), linestyle='dotted', lw=1) + mesh = PatchCollection( + patches, edgecolor="k", facecolor=(1, 1, 1, 0), linestyle="dotted", lw=1 + ) return mesh - ax.add_collection(setup_hexmesh(space.width, space.height, )) + + ax.add_collection( + setup_hexmesh( + space.width, + space.height, + ) + ) if propertylayer_portrayal: draw_property_layers(ax, space, propertylayer_portrayal, model) From 80f10bfd47f3d4370b4895c3443330466dbf8962 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Mon, 28 Oct 2024 07:55:48 +0100 Subject: [PATCH 16/74] remove properties again --- mesa/experimental/cell_space/voronoi.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/mesa/experimental/cell_space/voronoi.py b/mesa/experimental/cell_space/voronoi.py index 7239ff0cc72..01281bf8563 100644 --- a/mesa/experimental/cell_space/voronoi.py +++ b/mesa/experimental/cell_space/voronoi.py @@ -208,19 +208,6 @@ def __init__( self._connect_cells() self._build_cell_polygons() - @property - def width(self): - x_list = [i[0] for i in self.centroids_coordinates] - x_max = max(x_list) - x_min = min(x_list) - return x_max - x_min - - @property - def height(self): - y_list = [i[1] for i in self.centroids_coordinates] - y_max = max(y_list) - y_min = min(y_list) - return y_max - y_min def _connect_cells(self) -> None: """Connect cells to neighbors based on given centroids and using Delaunay Triangulation.""" From a30b14296e06f80899fd28695b55d5a846d5c29e Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Mon, 28 Oct 2024 07:55:55 +0100 Subject: [PATCH 17/74] ongoing work --- mesa/visualization/components/matplotlib.py | 138 +++++++++++--------- 1 file changed, 74 insertions(+), 64 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 64f0e49b205..344393fc472 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -10,6 +10,12 @@ import networkx as nx import numpy as np import solara + +from matplotlib.collections import PatchCollection +from matplotlib.patches import RegularPolygon +import math + + from matplotlib.cm import ScalarMappable from matplotlib.collections import PatchCollection from matplotlib.colors import LinearSegmentedColormap, Normalize, to_rgba @@ -49,7 +55,6 @@ def make_space_matplotlib(agent_portrayal=None, propertylayer_portrayal=None): function: A function that creates a SpaceMatplotlib component """ if agent_portrayal is None: - def agent_portrayal(a): return {"id": a.unique_id} @@ -61,10 +66,10 @@ def MakeSpaceMatplotlib(model): @solara.component def SpaceMatplotlib( - model, - agent_portrayal, - propertylayer_portrayal, - dependencies: list[any] | None = None, + model, + agent_portrayal, + propertylayer_portrayal, + dependencies: list[any] | None = None, ): """Create a Matplotlib-based space visualization component.""" update_counter.get() @@ -191,8 +196,11 @@ def draw_property_layers(ax, space, propertylayer_portrayal, model): def collect_agent_data( - space: OrthogonalGrid | HexGrid | Network | ContinuousSpace, - agent_portrayal: Callable, + space: OrthogonalGrid | HexGrid | Network | ContinuousSpace, + agent_portrayal: Callable, + c_default="tab:blue", + marker_default="o", + s_default=25 ): """Collect the plotting data for all agents in the space. @@ -200,6 +208,9 @@ def collect_agent_data( space: The space containing the Agents. agent_portrayal: A callable that is called with the agent and returns a dict loc: a boolean indicating whether to gather agent x, y data or not + c_default: default color + marker_default: default marker + s_default: default size Notes: agent portray dict is limited to s (size of marker), c (color of marker, and marker (marker style) @@ -209,30 +220,26 @@ def collect_agent_data( .. _Matplotlib: https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.scatter.html """ - default_size = (180 / max(space.width, space.height)) ** 2 - - arguments = {"x": [], "y": [], "s": [], "c": [], "marker": []} + arguments = {"loc": [], "s": [], "c": [], "marker": []} for agent in space.agents: portray = agent_portrayal(agent) loc = agent.pos if loc is None: loc = agent.cell.coordinate - x, y = loc - arguments["x"].append(x) - arguments["y"].append(y) - arguments["s"].append(portray.get("s", default_size)) - arguments["c"].append(portray.get("c", "tab:blue")) - arguments["marker"].append(portray.get("marker", "o")) + arguments['loc'] = loc + arguments["s"].append(portray.get("s", s_default)) + arguments["c"].append(portray.get("c", c_default)) + arguments["marker"].append(portray.get("marker", marker_default)) return {k: np.asarray(v) for k, v in arguments.items()} def draw_orthogonal_grid( - space: OrthogonalGrid, - agent_portrayal: Callable, - propertylayer_portrayal: Callable | None, - model, + space: OrthogonalGrid, + agent_portrayal: Callable, + propertylayer_portrayal: Callable | None, + model, ): """Visualize a orthogonal grid.. @@ -246,7 +253,8 @@ def draw_orthogonal_grid( A Figure and Axes instance """ - arguments = collect_agent_data(space, agent_portrayal) + s_default = (180 / max(space.width, space.height)) ** 2 + arguments = collect_agent_data(space, agent_portrayal, s_default=s_default) fig = Figure() ax = fig.add_subplot(111) @@ -269,7 +277,7 @@ def draw_orthogonal_grid( def draw_hex_grid( - space: HexGrid, agent_portrayal: Callable, propertylayer_portrayal: Callable, model + space: HexGrid, agent_portrayal: Callable, propertylayer_portrayal: Callable, model ): """Visualize a hex grid. @@ -283,7 +291,8 @@ def draw_hex_grid( A Figure and Axes instance """ - arguments = collect_agent_data(space, agent_portrayal) + s_default = (180 / max(space.width, space.height)) ** 2 + arguments = collect_agent_data(space, agent_portrayal, s_default=s_default) # give all odd rows an offset in the x direction offset = math.sqrt(0.75) @@ -306,6 +315,7 @@ def setup_hexmesh( ): """Helper function for creating the hexmaesh.""" # fixme: this should be done once, rather than in each update + # fixme check coordinate system in hexgrid (see https://www.redblobgames.com/grids/hexagons/#coordinates-offset) patches = [] for x, y in itertools.product(range(width), range(height)): @@ -336,23 +346,6 @@ def setup_hexmesh( return fig, ax -def collect_agent_data_for_networks(space, agent_portrayal): - """Collect the plotting data for all agents in the network. - - Args: - space: the space to visualize - agent_portrayal: a callable that is called with the agent and returns a dict - - """ - arguments = defaultdict(list) - for agent in space.agents: - portray = agent_portrayal(agent) - for k, v in portray.items(): - arguments[k].append(v) - - return arguments - - def draw_network(space: Network, agent_portrayal: Callable): """Visualize a network space. @@ -366,20 +359,32 @@ def draw_network(space: Network, agent_portrayal: Callable): """ - # FIXME this plotting is not correct. The x,y coordinates reflect the nodes in the network - # not the location of the agents on those nodes - arguments = collect_agent_data_for_networks(space, agent_portrayal) + graph = space.G + pos = nx.spring_layout(graph, seed=0) + x, y = list(zip(*pos.values())) + xmin, xmax = min(x), max(x) + ymin, ymax = min(y), max(y) + + width = xmax - xmin + height = ymax - ymin + + s_default = (180 / max(width, height)) ** 2 + arguments = collect_agent_data(space, agent_portrayal, s_default=s_default) + + + # fixme check if this code works + # it won't work in the current format because pos is a dict + arguments['loc'] = pos[arguments['loc']] fig = Figure() ax = fig.add_subplot(111) - graph = space.G - pos = nx.spring_layout(graph, seed=0) - nx.draw( - graph, - ax=ax, - pos=pos, - **arguments, - ) + ax.set_axis_off() + ax.set_xlim(xmin=xmin, xmax=xmax) + ax.set_ylim(ymin=ymin, ymax=ymax) + + _scatter(ax, arguments) + + nx.draw_networkx_edges(graph, pos, ax=ax) return fig, ax @@ -395,7 +400,13 @@ def draw_continuous_space(space: ContinuousSpace, agent_portrayal: Callable): A Figure and Axes instance """ - arguments = collect_agent_data(space, agent_portrayal) + width = space.x_max - space.x_min + x_padding = width / 20 + height = space.y_max - space.y_min + y_padding = height / 20 + + s_default = (180 / max(width, height)) ** 2 + arguments = collect_agent_data(space, agent_portrayal, s_default=s_default) fig = Figure() ax = fig.add_subplot(111) @@ -408,11 +419,6 @@ def draw_continuous_space(space: ContinuousSpace, agent_portrayal: Callable): spine.set_color("black") spine.set_linestyle(border_style) - width = space.x_max - space.x_min - x_padding = width / 20 - height = space.y_max - space.y_min - y_padding = height / 20 - ax.set_xlim(space.x_min - x_padding, space.x_max + x_padding) ax.set_ylim(space.y_min - y_padding, space.y_max + y_padding) @@ -431,11 +437,6 @@ def draw_voroinoi_grid(space: VoronoiGrid, agent_portrayal: Callable): A Figure and Axes instance """ - arguments = collect_agent_data(space, agent_portrayal) - - fig = Figure() - ax = fig.add_subplot(111) - x_list = [i[0] for i in space.centroids_coordinates] y_list = [i[1] for i in space.centroids_coordinates] x_max = max(x_list) @@ -447,6 +448,13 @@ def draw_voroinoi_grid(space: VoronoiGrid, agent_portrayal: Callable): x_padding = width / 20 height = y_max - y_min y_padding = height / 20 + + s_default = (180 / max(width, height)) ** 2 + arguments = collect_agent_data(space, agent_portrayal, s_default=s_default) + + fig = Figure() + ax = fig.add_subplot(111) + ax.set_xlim(x_min - x_padding, x_max + x_padding) ax.set_ylim(y_min - y_padding, y_max + y_padding) @@ -465,8 +473,10 @@ def draw_voroinoi_grid(space: VoronoiGrid, agent_portrayal: Callable): def _scatter(ax, arguments): - x = arguments.pop("x") - y = arguments.pop("y") + loc = arguments.pop['loc'] + + x = loc[:, 0] + y = loc[:, 1] marker = arguments.pop("marker") for mark in np.unique(marker): From f54f52b621ee4339901137673482927605b3d832 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 06:56:51 +0000 Subject: [PATCH 18/74] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/experimental/cell_space/voronoi.py | 1 - mesa/visualization/components/matplotlib.py | 43 +++++++++------------ 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/mesa/experimental/cell_space/voronoi.py b/mesa/experimental/cell_space/voronoi.py index 01281bf8563..2cde122cbb6 100644 --- a/mesa/experimental/cell_space/voronoi.py +++ b/mesa/experimental/cell_space/voronoi.py @@ -208,7 +208,6 @@ def __init__( self._connect_cells() self._build_cell_polygons() - def _connect_cells(self) -> None: """Connect cells to neighbors based on given centroids and using Delaunay Triangulation.""" self.triangulation = Delaunay() diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 344393fc472..10fde0a1e2e 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -3,19 +3,12 @@ import itertools import math import warnings -from collections import defaultdict from collections.abc import Callable import matplotlib.pyplot as plt import networkx as nx import numpy as np import solara - -from matplotlib.collections import PatchCollection -from matplotlib.patches import RegularPolygon -import math - - from matplotlib.cm import ScalarMappable from matplotlib.collections import PatchCollection from matplotlib.colors import LinearSegmentedColormap, Normalize, to_rgba @@ -55,6 +48,7 @@ def make_space_matplotlib(agent_portrayal=None, propertylayer_portrayal=None): function: A function that creates a SpaceMatplotlib component """ if agent_portrayal is None: + def agent_portrayal(a): return {"id": a.unique_id} @@ -66,10 +60,10 @@ def MakeSpaceMatplotlib(model): @solara.component def SpaceMatplotlib( - model, - agent_portrayal, - propertylayer_portrayal, - dependencies: list[any] | None = None, + model, + agent_portrayal, + propertylayer_portrayal, + dependencies: list[any] | None = None, ): """Create a Matplotlib-based space visualization component.""" update_counter.get() @@ -196,11 +190,11 @@ def draw_property_layers(ax, space, propertylayer_portrayal, model): def collect_agent_data( - space: OrthogonalGrid | HexGrid | Network | ContinuousSpace, - agent_portrayal: Callable, - c_default="tab:blue", - marker_default="o", - s_default=25 + space: OrthogonalGrid | HexGrid | Network | ContinuousSpace, + agent_portrayal: Callable, + c_default="tab:blue", + marker_default="o", + s_default=25, ): """Collect the plotting data for all agents in the space. @@ -227,7 +221,7 @@ def collect_agent_data( if loc is None: loc = agent.cell.coordinate - arguments['loc'] = loc + arguments["loc"] = loc arguments["s"].append(portray.get("s", s_default)) arguments["c"].append(portray.get("c", c_default)) arguments["marker"].append(portray.get("marker", marker_default)) @@ -236,10 +230,10 @@ def collect_agent_data( def draw_orthogonal_grid( - space: OrthogonalGrid, - agent_portrayal: Callable, - propertylayer_portrayal: Callable | None, - model, + space: OrthogonalGrid, + agent_portrayal: Callable, + propertylayer_portrayal: Callable | None, + model, ): """Visualize a orthogonal grid.. @@ -277,7 +271,7 @@ def draw_orthogonal_grid( def draw_hex_grid( - space: HexGrid, agent_portrayal: Callable, propertylayer_portrayal: Callable, model + space: HexGrid, agent_portrayal: Callable, propertylayer_portrayal: Callable, model ): """Visualize a hex grid. @@ -371,10 +365,9 @@ def draw_network(space: Network, agent_portrayal: Callable): s_default = (180 / max(width, height)) ** 2 arguments = collect_agent_data(space, agent_portrayal, s_default=s_default) - # fixme check if this code works # it won't work in the current format because pos is a dict - arguments['loc'] = pos[arguments['loc']] + arguments["loc"] = pos[arguments["loc"]] fig = Figure() ax = fig.add_subplot(111) @@ -473,7 +466,7 @@ def draw_voroinoi_grid(space: VoronoiGrid, agent_portrayal: Callable): def _scatter(ax, arguments): - loc = arguments.pop['loc'] + loc = arguments.pop["loc"] x = loc[:, 0] y = loc[:, 1] From 295b819e01f1a35dcae1f89f2fccf90dc4fa5b8c Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Mon, 28 Oct 2024 08:12:14 +0100 Subject: [PATCH 19/74] ruff fix and update to plot --- mesa/visualization/components/matplotlib.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 10fde0a1e2e..efd20edeb1d 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -289,6 +289,8 @@ def draw_hex_grid( arguments = collect_agent_data(space, agent_portrayal, s_default=s_default) # give all odd rows an offset in the x direction + # give all rows an offset in the y direction + # numbers here are based on a distance of 1 between centers of hexes offset = math.sqrt(0.75) logical = np.mod(arguments["y"], 2) == 1 @@ -314,8 +316,8 @@ def setup_hexmesh( patches = [] for x, y in itertools.product(range(width), range(height)): if y % 2 == 1: - x += 0.5 - y *= offset + x += 0.5 # noqa: PLW2901 + y *= offset # noqa: PLW2901 hex = RegularPolygon( (x, y), numVertices=6, @@ -466,7 +468,7 @@ def draw_voroinoi_grid(space: VoronoiGrid, agent_portrayal: Callable): def _scatter(ax, arguments): - loc = arguments.pop["loc"] + loc = arguments.pop("loc") x = loc[:, 0] y = loc[:, 1] From b9ecc228790bfdb1eca5e2ddfe87f83c031516d7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 07:12:30 +0000 Subject: [PATCH 20/74] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/visualization/components/matplotlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index efd20edeb1d..cb2a68fc5e0 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -317,7 +317,7 @@ def setup_hexmesh( for x, y in itertools.product(range(width), range(height)): if y % 2 == 1: x += 0.5 # noqa: PLW2901 - y *= offset # noqa: PLW2901 + y *= offset # noqa: PLW2901 hex = RegularPolygon( (x, y), numVertices=6, From 101aad66204927283684dbc3ab64e5c9310da98f Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Mon, 28 Oct 2024 08:38:28 +0100 Subject: [PATCH 21/74] make tests work again only network still needs fixing --- mesa/visualization/components/matplotlib.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index cb2a68fc5e0..e1e978660e5 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -221,7 +221,7 @@ def collect_agent_data( if loc is None: loc = agent.cell.coordinate - arguments["loc"] = loc + arguments["loc"].append(loc) arguments["s"].append(portray.get("s", s_default)) arguments["c"].append(portray.get("c", c_default)) arguments["marker"].append(portray.get("marker", marker_default)) @@ -293,10 +293,11 @@ def draw_hex_grid( # numbers here are based on a distance of 1 between centers of hexes offset = math.sqrt(0.75) - logical = np.mod(arguments["y"], 2) == 1 - arguments["y"] = arguments["y"].astype(float) * offset - arguments["x"] = arguments["x"].astype(float) - arguments["x"][logical] += 0.5 + loc = arguments['loc'].astype(float) + + logical = np.mod(loc[:, 1], 2) == 1 + loc[:, 1] *= offset + loc[:, 0][logical] += 0.5 fig = Figure() ax = fig.add_subplot(111) From 9477f66654d5ac0e5d723cc1443528c5d3cd8b86 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 07:38:49 +0000 Subject: [PATCH 22/74] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/visualization/components/matplotlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index e1e978660e5..aac1d2f6314 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -293,7 +293,7 @@ def draw_hex_grid( # numbers here are based on a distance of 1 between centers of hexes offset = math.sqrt(0.75) - loc = arguments['loc'].astype(float) + loc = arguments["loc"].astype(float) logical = np.mod(loc[:, 1], 2) == 1 loc[:, 1] *= offset From 9904d256ea8b986d325b2ac61735bba7c16fd57f Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Mon, 28 Oct 2024 09:01:28 +0100 Subject: [PATCH 23/74] fix networks --- mesa/visualization/components/matplotlib.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index aac1d2f6314..10577be8436 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -368,8 +368,9 @@ def draw_network(space: Network, agent_portrayal: Callable): s_default = (180 / max(width, height)) ** 2 arguments = collect_agent_data(space, agent_portrayal, s_default=s_default) - # fixme check if this code works - # it won't work in the current format because pos is a dict + # this assumes that nodes are identified by an integer + # which is true for default nx graphs but might user changeable + pos = np.asarray(list(pos.values())) arguments["loc"] = pos[arguments["loc"]] fig = Figure() From 15fa02a9a38294cb3d823714b775793f51e08389 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Mon, 28 Oct 2024 09:10:35 +0100 Subject: [PATCH 24/74] correctly update x,y for hexgrids --- mesa/visualization/components/matplotlib.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 10577be8436..e9ca7ecf826 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -293,11 +293,19 @@ def draw_hex_grid( # numbers here are based on a distance of 1 between centers of hexes offset = math.sqrt(0.75) + + # logical = np.mod(arguments["y"], 2) == 1 + # arguments["y"] = arguments["y"].astype(float) * offset + # arguments["x"] = arguments["x"].astype(float) + # arguments["x"][logical] += 0.5 + loc = arguments["loc"].astype(float) logical = np.mod(loc[:, 1], 2) == 1 - loc[:, 1] *= offset loc[:, 0][logical] += 0.5 + loc[:, 1] *= offset + arguments["loc"] = loc + fig = Figure() ax = fig.add_subplot(111) From dbeb9224f575ac4f96dbe8bb127eaab1a3fba3bc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 08:10:51 +0000 Subject: [PATCH 25/74] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/visualization/components/matplotlib.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index e9ca7ecf826..d6003c0ff43 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -293,7 +293,6 @@ def draw_hex_grid( # numbers here are based on a distance of 1 between centers of hexes offset = math.sqrt(0.75) - # logical = np.mod(arguments["y"], 2) == 1 # arguments["y"] = arguments["y"].astype(float) * offset # arguments["x"] = arguments["x"].astype(float) @@ -306,7 +305,6 @@ def draw_hex_grid( loc[:, 1] *= offset arguments["loc"] = loc - fig = Figure() ax = fig.add_subplot(111) ax.set_xlim(-1, space.width + 0.5) From 669aa0bfaffd6ded5601d43567fe87e0e082102a Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Mon, 28 Oct 2024 12:12:53 +0100 Subject: [PATCH 26/74] correction for how hexgrids are wired --- mesa/visualization/components/matplotlib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index d6003c0ff43..cf092fc4e11 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -300,7 +300,7 @@ def draw_hex_grid( loc = arguments["loc"].astype(float) - logical = np.mod(loc[:, 1], 2) == 1 + logical = np.mod(loc[:, 1], 2) == 0 loc[:, 0][logical] += 0.5 loc[:, 1] *= offset arguments["loc"] = loc @@ -322,7 +322,7 @@ def setup_hexmesh( patches = [] for x, y in itertools.product(range(width), range(height)): - if y % 2 == 1: + if y % 2 == 0: x += 0.5 # noqa: PLW2901 y *= offset # noqa: PLW2901 hex = RegularPolygon( From 1bbe3795ac4dbd4a60b95b19961a9b9b70773072 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Mon, 28 Oct 2024 15:44:40 +0100 Subject: [PATCH 27/74] simplification of case match structure --- mesa/visualization/components/matplotlib.py | 82 +++++++-------------- 1 file changed, 26 insertions(+), 56 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index cf092fc4e11..72920d0e077 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -72,52 +72,35 @@ def SpaceMatplotlib( if space is None: space = getattr(model, "space", None) + fig = Figure() + ax = fig.subplots(111) + # https://stackoverflow.com/questions/67524641/convert-multiple-isinstance-checks-to-structural-pattern-matching match space: - case mesa.space._Grid(): - fig, ax = draw_orthogonal_grid( - space, agent_portrayal, propertylayer_portrayal, model - ) - case OrthogonalMooreGrid(): - fig, ax = draw_orthogonal_grid( - space, agent_portrayal, propertylayer_portrayal, model - ) - case OrthogonalVonNeumannGrid(): - fig, ax = draw_orthogonal_grid( - space, agent_portrayal, propertylayer_portrayal, model - ) - case HexSingleGrid(): - fig, ax = draw_hex_grid( - space, agent_portrayal, propertylayer_portrayal, model + case mesa.space._Grid() | OrthogonalMooreGrid() | OrthogonalVonNeumannGrid(): + draw_orthogonal_grid( + space, agent_portrayal, propertylayer_portrayal, model, ax ) - case HexSingleGrid(): - fig, ax = draw_hex_grid( - space, agent_portrayal, propertylayer_portrayal, model - ) - case mesa.experimental.cell_space.HexGrid(): - fig, ax = draw_hex_grid( - space, agent_portrayal, propertylayer_portrayal, model + case HexSingleGrid() | HexSingleGrid() | mesa.experimental.cell_space.HexGrid(): + draw_hex_grid( + space, agent_portrayal, propertylayer_portrayal, model, ax ) + case mesa.space.NetworkGrid() | mesa.experimental.cell_space.Network(): + draw_network(space, agent_portrayal, ax) case mesa.space.ContinuousSpace(): - fig, ax = draw_continuous_space(space, agent_portrayal) - case mesa.space.NetworkGrid(): - fig, ax = draw_network(space, agent_portrayal) + draw_continuous_space(space, agent_portrayal, ax) case VoronoiGrid(): - fig, ax = draw_voroinoi_grid(space, agent_portrayal) - case mesa.experimental.cell_space.Network(): - fig, ax = draw_network(space, agent_portrayal) + draw_voroinoi_grid(space, agent_portrayal, ax) case None: - fig = Figure() - ax = fig.subplots(111) if propertylayer_portrayal: - draw_property_layers(ax, space, propertylayer_portrayal, model) + draw_property_layers(space, propertylayer_portrayal, model, ax) solara.FigureMatplotlib( fig, format="png", bbox_inches="tight", dependencies=dependencies ) -def draw_property_layers(ax, space, propertylayer_portrayal, model): +def draw_property_layers(space, propertylayer_portrayal, model, ax): """Draw PropertyLayers on the given axes. Args: @@ -234,14 +217,16 @@ def draw_orthogonal_grid( agent_portrayal: Callable, propertylayer_portrayal: Callable | None, model, + ax ): - """Visualize a orthogonal grid.. + """Visualize a orthogonal grid. Args: space: the space to visualize agent_portrayal: a callable that is called with the agent and returns a dict propertylayer_portrayal: a callable that is called with the agent and returns a dict model: a model instance + ax: a Matplotlib Axes instance Returns: A Figure and Axes instance @@ -250,9 +235,6 @@ def draw_orthogonal_grid( s_default = (180 / max(space.width, space.height)) ** 2 arguments = collect_agent_data(space, agent_portrayal, s_default=s_default) - fig = Figure() - ax = fig.add_subplot(111) - ax.set_xlim(-0.5, space.width - 0.5) ax.set_ylim(-0.5, space.height - 0.5) @@ -267,11 +249,9 @@ def draw_orthogonal_grid( if propertylayer_portrayal: draw_property_layers(ax, space, propertylayer_portrayal, model) - return fig, ax - def draw_hex_grid( - space: HexGrid, agent_portrayal: Callable, propertylayer_portrayal: Callable, model + space: HexGrid, agent_portrayal: Callable, propertylayer_portrayal: Callable, model, ax ): """Visualize a hex grid. @@ -280,6 +260,7 @@ def draw_hex_grid( agent_portrayal: a callable that is called with the agent and returns a dict propertylayer_portrayal: a callable that is called with the agent and returns a dict model: a model instance + ax: a Matplotlib Axes instance Returns: A Figure and Axes instance @@ -305,8 +286,6 @@ def draw_hex_grid( loc[:, 1] *= offset arguments["loc"] = loc - fig = Figure() - ax = fig.add_subplot(111) ax.set_xlim(-1, space.width + 0.5) ax.set_ylim(-offset, space.height * offset) @@ -346,15 +325,15 @@ def setup_hexmesh( if propertylayer_portrayal: draw_property_layers(ax, space, propertylayer_portrayal, model) - return fig, ax -def draw_network(space: Network, agent_portrayal: Callable): +def draw_network(space: Network, agent_portrayal: Callable, ax): """Visualize a network space. Args: space: the space to visualize agent_portrayal: a callable that is called with the agent and returns a dict + ax: a Matplotlib Axes instance Notes: this uses networkx.draw under the hood so agent portrayal fields should match those used there @@ -379,25 +358,22 @@ def draw_network(space: Network, agent_portrayal: Callable): pos = np.asarray(list(pos.values())) arguments["loc"] = pos[arguments["loc"]] - fig = Figure() ax = fig.add_subplot(111) ax.set_axis_off() ax.set_xlim(xmin=xmin, xmax=xmax) ax.set_ylim(ymin=ymin, ymax=ymax) _scatter(ax, arguments) - nx.draw_networkx_edges(graph, pos, ax=ax) - return fig, ax - -def draw_continuous_space(space: ContinuousSpace, agent_portrayal: Callable): +def draw_continuous_space(space: ContinuousSpace, agent_portrayal: Callable, ax): """Visualize a continuous space. Args: space: the space to visualize agent_portrayal: a callable that is called with the agent and returns a dict + ax: a Matplotlib Axes instance Returns: A Figure and Axes instance @@ -411,8 +387,6 @@ def draw_continuous_space(space: ContinuousSpace, agent_portrayal: Callable): s_default = (180 / max(width, height)) ** 2 arguments = collect_agent_data(space, agent_portrayal, s_default=s_default) - fig = Figure() - ax = fig.add_subplot(111) border_style = "solid" if not space.torus else (0, (5, 10)) @@ -426,15 +400,15 @@ def draw_continuous_space(space: ContinuousSpace, agent_portrayal: Callable): ax.set_ylim(space.y_min - y_padding, space.y_max + y_padding) _scatter(ax, arguments) - return fig, ax -def draw_voroinoi_grid(space: VoronoiGrid, agent_portrayal: Callable): +def draw_voroinoi_grid(space: VoronoiGrid, agent_portrayal: Callable, ax): """Visualize a voronoi grid. Args: space: the space to visualize agent_portrayal: a callable that is called with the agent and returns a dict + ax: a Matplotlib Axes instance Returns: A Figure and Axes instance @@ -455,9 +429,6 @@ def draw_voroinoi_grid(space: VoronoiGrid, agent_portrayal: Callable): s_default = (180 / max(width, height)) ** 2 arguments = collect_agent_data(space, agent_portrayal, s_default=s_default) - fig = Figure() - ax = fig.add_subplot(111) - ax.set_xlim(x_min - x_padding, x_max + x_padding) ax.set_ylim(y_min - y_padding, y_max + y_padding) @@ -472,7 +443,6 @@ def draw_voroinoi_grid(space: VoronoiGrid, agent_portrayal: Callable): zorder=0, ) # Plot filled polygon ax.plot(*zip(*polygon), color="black") # Plot polygon edges in black - return fig, ax def _scatter(ax, arguments): From fc63cf4cd8909b95048fb9a91efd939deef76537 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Mon, 28 Oct 2024 15:44:59 +0100 Subject: [PATCH 28/74] update to tests because draw functions take an axes instance as argument --- tests/test_components_matplotlib.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/tests/test_components_matplotlib.py b/tests/test_components_matplotlib.py index 8284e48423f..d630f59812c 100644 --- a/tests/test_components_matplotlib.py +++ b/tests/test_components_matplotlib.py @@ -16,6 +16,7 @@ draw_orthogonal_grid, draw_voroinoi_grid, ) +import matplotlib.pyplot as plt def agent_portrayal(agent): @@ -39,7 +40,9 @@ def test_draw_hex_grid(): for _ in range(10): agent = Agent(model) grid.move_to_empty(agent) - draw_hex_grid(grid, agent_portrayal, None, model) + + fig, ax = plt.subplots() + draw_hex_grid(grid, agent_portrayal, None, model, ax) model = Model(seed=42) grid = HexGrid((10, 10), torus=True, random=model.random, capacity=1) @@ -47,7 +50,8 @@ def test_draw_hex_grid(): agent = CellAgent(model) agent.cell = grid.select_random_empty_cell() - draw_hex_grid(grid, agent_portrayal, None, model) + fig, ax = plt.subplots() + draw_hex_grid(grid, agent_portrayal, None, model, ax) def test_draw_voroinoi_grid(): @@ -61,7 +65,8 @@ def test_draw_voroinoi_grid(): agent = CellAgent(model) agent.cell = grid.select_random_empty_cell() - draw_voroinoi_grid(grid, agent_portrayal) + fig, ax = plt.subplots() + draw_voroinoi_grid(grid, agent_portrayal, ax) def test_draw_orthogonal_grid(): @@ -71,7 +76,9 @@ def test_draw_orthogonal_grid(): for _ in range(10): agent = Agent(model) grid.move_to_empty(agent) - draw_orthogonal_grid(grid, agent_portrayal, None, model) + + fig, ax = plt.subplots() + draw_orthogonal_grid(grid, agent_portrayal, None, model, ax) model = Model(seed=42) grid = OrthogonalMooreGrid((10, 10), torus=True, random=model.random, capacity=1) @@ -79,7 +86,8 @@ def test_draw_orthogonal_grid(): agent = CellAgent(model) agent.cell = grid.select_random_empty_cell() - draw_orthogonal_grid(grid, agent_portrayal, None, model) + fig, ax = plt.subplots() + draw_orthogonal_grid(grid, agent_portrayal, None, model, ax) def test_draw_continuous_space(): @@ -91,7 +99,9 @@ def test_draw_continuous_space(): y = model.random.random() * 10 agent = Agent(model) space.place_agent(agent, (x, y)) - draw_continuous_space(space, agent_portrayal) + + fig, ax = plt.subplots() + draw_continuous_space(space, agent_portrayal, ax) def test_draw_network(): @@ -119,7 +129,9 @@ def agent_portrayal(agent): agent = Agent(model) pos = agent.random.randint(0, len(graph.nodes) - 1) grid.place_agent(agent, pos) - draw_network(grid, agent_portrayal) + + fig, ax = plt.subplots() + draw_network(grid, agent_portrayal, ax) model = Model(seed=42) grid = Network(graph, random=model.random, capacity=1) @@ -127,7 +139,8 @@ def agent_portrayal(agent): agent = CellAgent(model) agent.cell = grid.select_random_empty_cell() - draw_network(grid, agent_portrayal) + fig, ax = plt.subplots() + draw_network(grid, agent_portrayal, ax) def test_draw_property_layers(): From 8a558167e02afea32afacf172c19042f75c30d62 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 14:45:10 +0000 Subject: [PATCH 29/74] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/visualization/components/matplotlib.py | 13 +++++++------ tests/test_components_matplotlib.py | 3 ++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 72920d0e077..3e2e074840c 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -82,9 +82,7 @@ def SpaceMatplotlib( space, agent_portrayal, propertylayer_portrayal, model, ax ) case HexSingleGrid() | HexSingleGrid() | mesa.experimental.cell_space.HexGrid(): - draw_hex_grid( - space, agent_portrayal, propertylayer_portrayal, model, ax - ) + draw_hex_grid(space, agent_portrayal, propertylayer_portrayal, model, ax) case mesa.space.NetworkGrid() | mesa.experimental.cell_space.Network(): draw_network(space, agent_portrayal, ax) case mesa.space.ContinuousSpace(): @@ -217,7 +215,7 @@ def draw_orthogonal_grid( agent_portrayal: Callable, propertylayer_portrayal: Callable | None, model, - ax + ax, ): """Visualize a orthogonal grid. @@ -251,7 +249,11 @@ def draw_orthogonal_grid( def draw_hex_grid( - space: HexGrid, agent_portrayal: Callable, propertylayer_portrayal: Callable, model, ax + space: HexGrid, + agent_portrayal: Callable, + propertylayer_portrayal: Callable, + model, + ax, ): """Visualize a hex grid. @@ -387,7 +389,6 @@ def draw_continuous_space(space: ContinuousSpace, agent_portrayal: Callable, ax) s_default = (180 / max(width, height)) ** 2 arguments = collect_agent_data(space, agent_portrayal, s_default=s_default) - border_style = "solid" if not space.torus else (0, (5, 10)) # Set the border of the plot diff --git a/tests/test_components_matplotlib.py b/tests/test_components_matplotlib.py index d630f59812c..1b45d95e5a2 100644 --- a/tests/test_components_matplotlib.py +++ b/tests/test_components_matplotlib.py @@ -1,5 +1,7 @@ """tests for matplotlib components.""" +import matplotlib.pyplot as plt + from mesa import Agent, Model from mesa.experimental.cell_space import ( CellAgent, @@ -16,7 +18,6 @@ draw_orthogonal_grid, draw_voroinoi_grid, ) -import matplotlib.pyplot as plt def agent_portrayal(agent): From 84954fd655001009d7766e36dbb67aced0fc1fcf Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Mon, 28 Oct 2024 15:46:03 +0100 Subject: [PATCH 30/74] fix for failing tests --- mesa/visualization/components/matplotlib.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 3e2e074840c..f6439d601c7 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -360,7 +360,6 @@ def draw_network(space: Network, agent_portrayal: Callable, ax): pos = np.asarray(list(pos.values())) arguments["loc"] = pos[arguments["loc"]] - ax = fig.add_subplot(111) ax.set_axis_off() ax.set_xlim(xmin=xmin, xmax=xmax) ax.set_ylim(ymin=ymin, ymax=ymax) From 24e3154e0f79bf1e327b6154c51e9b3899578a7d Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Mon, 28 Oct 2024 15:55:26 +0100 Subject: [PATCH 31/74] move propertlayer drawing out of specific draw_x functions --- mesa/visualization/components/matplotlib.py | 26 +++++++-------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index f6439d601c7..b984be13e14 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -9,6 +9,7 @@ import networkx as nx import numpy as np import solara + from matplotlib.cm import ScalarMappable from matplotlib.collections import PatchCollection from matplotlib.colors import LinearSegmentedColormap, Normalize, to_rgba @@ -30,6 +31,7 @@ PropertyLayer, SingleGrid, ) +from mesa import Model from mesa.visualization.utils import update_counter OrthogonalGrid = SingleGrid | MultiGrid | OrthogonalMooreGrid | OrthogonalVonNeumannGrid @@ -79,19 +81,19 @@ def SpaceMatplotlib( match space: case mesa.space._Grid() | OrthogonalMooreGrid() | OrthogonalVonNeumannGrid(): draw_orthogonal_grid( - space, agent_portrayal, propertylayer_portrayal, model, ax + space, agent_portrayal, ax ) - case HexSingleGrid() | HexSingleGrid() | mesa.experimental.cell_space.HexGrid(): - draw_hex_grid(space, agent_portrayal, propertylayer_portrayal, model, ax) + case HexSingleGrid() | HexMultiGrid() | mesa.experimental.cell_space.HexGrid(): + draw_hex_grid(space, agent_portrayal, ax) case mesa.space.NetworkGrid() | mesa.experimental.cell_space.Network(): draw_network(space, agent_portrayal, ax) case mesa.space.ContinuousSpace(): draw_continuous_space(space, agent_portrayal, ax) case VoronoiGrid(): draw_voroinoi_grid(space, agent_portrayal, ax) - case None: - if propertylayer_portrayal: - draw_property_layers(space, propertylayer_portrayal, model, ax) + + if propertylayer_portrayal: + draw_property_layers(space, propertylayer_portrayal, model, ax) solara.FigureMatplotlib( fig, format="png", bbox_inches="tight", dependencies=dependencies @@ -213,8 +215,6 @@ def collect_agent_data( def draw_orthogonal_grid( space: OrthogonalGrid, agent_portrayal: Callable, - propertylayer_portrayal: Callable | None, - model, ax, ): """Visualize a orthogonal grid. @@ -222,8 +222,6 @@ def draw_orthogonal_grid( Args: space: the space to visualize agent_portrayal: a callable that is called with the agent and returns a dict - propertylayer_portrayal: a callable that is called with the agent and returns a dict - model: a model instance ax: a Matplotlib Axes instance Returns: @@ -244,15 +242,10 @@ def draw_orthogonal_grid( _scatter(ax, arguments) - if propertylayer_portrayal: - draw_property_layers(ax, space, propertylayer_portrayal, model) - def draw_hex_grid( space: HexGrid, agent_portrayal: Callable, - propertylayer_portrayal: Callable, - model, ax, ): """Visualize a hex grid. @@ -325,9 +318,6 @@ def setup_hexmesh( ) ) - if propertylayer_portrayal: - draw_property_layers(ax, space, propertylayer_portrayal, model) - def draw_network(space: Network, agent_portrayal: Callable, ax): """Visualize a network space. From 81d90e7b6f5e508f7a7adde438cef24292835be6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 14:58:42 +0000 Subject: [PATCH 32/74] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/visualization/components/matplotlib.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index b984be13e14..88570d1de6f 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -9,7 +9,6 @@ import networkx as nx import numpy as np import solara - from matplotlib.cm import ScalarMappable from matplotlib.collections import PatchCollection from matplotlib.colors import LinearSegmentedColormap, Normalize, to_rgba @@ -31,7 +30,6 @@ PropertyLayer, SingleGrid, ) -from mesa import Model from mesa.visualization.utils import update_counter OrthogonalGrid = SingleGrid | MultiGrid | OrthogonalMooreGrid | OrthogonalVonNeumannGrid @@ -80,9 +78,7 @@ def SpaceMatplotlib( # https://stackoverflow.com/questions/67524641/convert-multiple-isinstance-checks-to-structural-pattern-matching match space: case mesa.space._Grid() | OrthogonalMooreGrid() | OrthogonalVonNeumannGrid(): - draw_orthogonal_grid( - space, agent_portrayal, ax - ) + draw_orthogonal_grid(space, agent_portrayal, ax) case HexSingleGrid() | HexMultiGrid() | mesa.experimental.cell_space.HexGrid(): draw_hex_grid(space, agent_portrayal, ax) case mesa.space.NetworkGrid() | mesa.experimental.cell_space.Network(): From 36b9623f812508a554cbeb646bd53f7f1258bd97 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Mon, 28 Oct 2024 17:19:00 +0100 Subject: [PATCH 33/74] add tests for and fixes errors in draw_property_layers --- mesa/visualization/components/matplotlib.py | 25 +++++++++++---- tests/test_components_matplotlib.py | 35 ++++++++++++--------- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 88570d1de6f..eddadbbafb9 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -5,6 +5,8 @@ import warnings from collections.abc import Callable +from typing import Dict + import matplotlib.pyplot as plt import networkx as nx import numpy as np @@ -96,17 +98,30 @@ def SpaceMatplotlib( ) -def draw_property_layers(space, propertylayer_portrayal, model, ax): +def draw_property_layers(space, propertylayer_portrayal: Dict[str:Dict], ax): """Draw PropertyLayers on the given axes. Args: - ax (matplotlib.axes.Axes): The axes to draw on. space (mesa.space._Grid): The space containing the PropertyLayers. - propertylayer_portrayal (dict): Dictionary of PropertyLayer portrayal specifications. + propertylayer_portrayal (dict): the key is the name of the layer, the value is a dict with + fields specifying how the layer is to be portrayed model (mesa.Model): The model instance. + ax (matplotlib.axes.Axes): The axes to draw on. + + Notes: + valid fields in in the inner dict of propertylayer_portrayal are "alpha", "vmin", "vmax", "color" or "colormap", and "colorbar" + so you can do `{"some_layer":{"colormap":'viridis', 'alpha':.25, "colorbar":False}}` + """ + try: + # old style spaces + property_layers = space.properties + except AttributeError: + # new style spaces + property_layers = space.property_layers + for layer_name, portrayal in propertylayer_portrayal.items(): - layer = getattr(model, layer_name, None) + layer = property_layers.get(layer_name, None) if not isinstance(layer, PropertyLayer): continue @@ -138,7 +153,6 @@ def draw_property_layers(space, propertylayer_portrayal, model, ax): ) im = ax.imshow( rgba_data.transpose(1, 0, 2), - extent=(0, width, 0, height), origin="lower", ) if colorbar: @@ -157,7 +171,6 @@ def draw_property_layers(space, propertylayer_portrayal, model, ax): alpha=alpha, vmin=vmin, vmax=vmax, - extent=(0, width, 0, height), origin="lower", ) if colorbar: diff --git a/tests/test_components_matplotlib.py b/tests/test_components_matplotlib.py index 1b45d95e5a2..df9d5e91326 100644 --- a/tests/test_components_matplotlib.py +++ b/tests/test_components_matplotlib.py @@ -3,6 +3,7 @@ import matplotlib.pyplot as plt from mesa import Agent, Model +from mesa.space import PropertyLayer from mesa.experimental.cell_space import ( CellAgent, HexGrid, @@ -17,6 +18,7 @@ draw_network, draw_orthogonal_grid, draw_voroinoi_grid, + draw_property_layers ) @@ -43,7 +45,7 @@ def test_draw_hex_grid(): grid.move_to_empty(agent) fig, ax = plt.subplots() - draw_hex_grid(grid, agent_portrayal, None, model, ax) + draw_hex_grid(grid, agent_portrayal, ax) model = Model(seed=42) grid = HexGrid((10, 10), torus=True, random=model.random, capacity=1) @@ -52,7 +54,7 @@ def test_draw_hex_grid(): agent.cell = grid.select_random_empty_cell() fig, ax = plt.subplots() - draw_hex_grid(grid, agent_portrayal, None, model, ax) + draw_hex_grid(grid, agent_portrayal, ax) def test_draw_voroinoi_grid(): @@ -79,7 +81,7 @@ def test_draw_orthogonal_grid(): grid.move_to_empty(agent) fig, ax = plt.subplots() - draw_orthogonal_grid(grid, agent_portrayal, None, model, ax) + draw_orthogonal_grid(grid, agent_portrayal, ax) model = Model(seed=42) grid = OrthogonalMooreGrid((10, 10), torus=True, random=model.random, capacity=1) @@ -88,7 +90,7 @@ def test_draw_orthogonal_grid(): agent.cell = grid.select_random_empty_cell() fig, ax = plt.subplots() - draw_orthogonal_grid(grid, agent_portrayal, None, model, ax) + draw_orthogonal_grid(grid, agent_portrayal, ax) def test_draw_continuous_space(): @@ -107,16 +109,6 @@ def test_draw_continuous_space(): def test_draw_network(): """Test drawing network.""" - - def agent_portrayal(agent): - """Simple portrayal of an agent. - - Args: - agent (Agent): The agent to portray - - """ - return {"node_size": 10, "node_color": "tab:blue"} - import networkx as nx n = 10 @@ -146,3 +138,18 @@ def agent_portrayal(agent): def test_draw_property_layers(): """Test drawing property layers.""" + + model = Model(seed=42) + grid = SingleGrid(10, 10, torus=True) + grid.add_property_layer(PropertyLayer("test", grid.width, grid.height, 0)) + + fig, ax = plt.subplots() + draw_property_layers(grid, {"test":{"colormap":"viridis", "colorbar":True}}, ax) + + model = Model(seed=42) + grid = OrthogonalMooreGrid((10, 10), torus=True, random=model.random, capacity=1) + grid.add_property_layer(PropertyLayer("test", grid.width, grid.height, 0)) + + fig, ax = plt.subplots() + draw_property_layers(grid, {"test":{"colormap":"viridis", "colorbar":True}}, ax) + From 5c28476a1797d8cfc8e3e9353702dea887e31e28 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 16:19:09 +0000 Subject: [PATCH 34/74] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/visualization/components/matplotlib.py | 4 +--- tests/test_components_matplotlib.py | 17 ++++++++++------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index eddadbbafb9..e9790837080 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -5,8 +5,6 @@ import warnings from collections.abc import Callable -from typing import Dict - import matplotlib.pyplot as plt import networkx as nx import numpy as np @@ -98,7 +96,7 @@ def SpaceMatplotlib( ) -def draw_property_layers(space, propertylayer_portrayal: Dict[str:Dict], ax): +def draw_property_layers(space, propertylayer_portrayal: dict[str:dict], ax): """Draw PropertyLayers on the given axes. Args: diff --git a/tests/test_components_matplotlib.py b/tests/test_components_matplotlib.py index df9d5e91326..c85dd1ce292 100644 --- a/tests/test_components_matplotlib.py +++ b/tests/test_components_matplotlib.py @@ -3,7 +3,6 @@ import matplotlib.pyplot as plt from mesa import Agent, Model -from mesa.space import PropertyLayer from mesa.experimental.cell_space import ( CellAgent, HexGrid, @@ -11,14 +10,20 @@ OrthogonalMooreGrid, VoronoiGrid, ) -from mesa.space import ContinuousSpace, HexSingleGrid, NetworkGrid, SingleGrid +from mesa.space import ( + ContinuousSpace, + HexSingleGrid, + NetworkGrid, + PropertyLayer, + SingleGrid, +) from mesa.visualization.components.matplotlib import ( draw_continuous_space, draw_hex_grid, draw_network, draw_orthogonal_grid, + draw_property_layers, draw_voroinoi_grid, - draw_property_layers ) @@ -138,18 +143,16 @@ def test_draw_network(): def test_draw_property_layers(): """Test drawing property layers.""" - model = Model(seed=42) grid = SingleGrid(10, 10, torus=True) grid.add_property_layer(PropertyLayer("test", grid.width, grid.height, 0)) fig, ax = plt.subplots() - draw_property_layers(grid, {"test":{"colormap":"viridis", "colorbar":True}}, ax) + draw_property_layers(grid, {"test": {"colormap": "viridis", "colorbar": True}}, ax) model = Model(seed=42) grid = OrthogonalMooreGrid((10, 10), torus=True, random=model.random, capacity=1) grid.add_property_layer(PropertyLayer("test", grid.width, grid.height, 0)) fig, ax = plt.subplots() - draw_property_layers(grid, {"test":{"colormap":"viridis", "colorbar":True}}, ax) - + draw_property_layers(grid, {"test": {"colormap": "viridis", "colorbar": True}}, ax) From 0cec04e6d31b5cd54dcd2f0f0617c62011d08565 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Mon, 28 Oct 2024 17:40:01 +0100 Subject: [PATCH 35/74] Update matplotlib.py --- mesa/visualization/components/matplotlib.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index e9790837080..138e5c128b0 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -5,6 +5,8 @@ import warnings from collections.abc import Callable +from typing import Dict, Any + import matplotlib.pyplot as plt import networkx as nx import numpy as np @@ -96,7 +98,7 @@ def SpaceMatplotlib( ) -def draw_property_layers(space, propertylayer_portrayal: dict[str:dict], ax): +def draw_property_layers(space, propertylayer_portrayal: Dict[str, Dict[str, Any]], ax): """Draw PropertyLayers on the given axes. Args: From b07620f719c13404474095c3bd06cb281a7ef8ff Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 16:40:43 +0000 Subject: [PATCH 36/74] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/visualization/components/matplotlib.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 138e5c128b0..17ede9a1dbd 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -4,8 +4,7 @@ import math import warnings from collections.abc import Callable - -from typing import Dict, Any +from typing import Any import matplotlib.pyplot as plt import networkx as nx @@ -98,7 +97,7 @@ def SpaceMatplotlib( ) -def draw_property_layers(space, propertylayer_portrayal: Dict[str, Dict[str, Any]], ax): +def draw_property_layers(space, propertylayer_portrayal: dict[str, dict[str, Any]], ax): """Draw PropertyLayers on the given axes. Args: From 315e7eb411beaad7effe2fb281cd60f70a4f43b9 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Mon, 28 Oct 2024 17:50:13 +0100 Subject: [PATCH 37/74] cleanup --- mesa/examples/advanced/wolf_sheep/app.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/mesa/examples/advanced/wolf_sheep/app.py b/mesa/examples/advanced/wolf_sheep/app.py index 46f577b4ba0..5270be7a753 100644 --- a/mesa/examples/advanced/wolf_sheep/app.py +++ b/mesa/examples/advanced/wolf_sheep/app.py @@ -1,11 +1,3 @@ -import os.path -import sys - -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../")) -) - - from mesa.examples.advanced.wolf_sheep.agents import GrassPatch, Sheep, Wolf from mesa.examples.advanced.wolf_sheep.model import WolfSheep from mesa.visualization import ( From f685db486cdd007bc439a14aa59373c1a4ceabed Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Mon, 28 Oct 2024 17:54:29 +0100 Subject: [PATCH 38/74] some further cleanup --- mesa/visualization/components/matplotlib.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 17ede9a1dbd..a4c66fb20bf 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -33,6 +33,8 @@ ) from mesa.visualization.utils import update_counter + +# For typing OrthogonalGrid = SingleGrid | MultiGrid | OrthogonalMooreGrid | OrthogonalVonNeumannGrid HexGrid = HexSingleGrid | HexMultiGrid | mesa.experimental.cell_space.HexGrid Network = NetworkGrid | mesa.experimental.cell_space.Network @@ -90,7 +92,7 @@ def SpaceMatplotlib( draw_voroinoi_grid(space, agent_portrayal, ax) if propertylayer_portrayal: - draw_property_layers(space, propertylayer_portrayal, model, ax) + draw_property_layers(space, propertylayer_portrayal, ax) solara.FigureMatplotlib( fig, format="png", bbox_inches="tight", dependencies=dependencies From 4c727ecd07614df688cf5a553f2c35a3e966590a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 16:54:38 +0000 Subject: [PATCH 39/74] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/visualization/components/matplotlib.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index a4c66fb20bf..32fe33166a2 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -33,7 +33,6 @@ ) from mesa.visualization.utils import update_counter - # For typing OrthogonalGrid = SingleGrid | MultiGrid | OrthogonalMooreGrid | OrthogonalVonNeumannGrid HexGrid = HexSingleGrid | HexMultiGrid | mesa.experimental.cell_space.HexGrid From c102abb24609982199472e1403969133a5acbeda Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Mon, 28 Oct 2024 18:11:13 +0100 Subject: [PATCH 40/74] further annotation of code --- mesa/visualization/components/matplotlib.py | 46 ++++++++++++++------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 32fe33166a2..4c20117d1e9 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -105,7 +105,6 @@ def draw_property_layers(space, propertylayer_portrayal: dict[str, dict[str, Any space (mesa.space._Grid): The space containing the PropertyLayers. propertylayer_portrayal (dict): the key is the name of the layer, the value is a dict with fields specifying how the layer is to be portrayed - model (mesa.Model): The model instance. ax (matplotlib.axes.Axes): The axes to draw on. Notes: @@ -237,9 +236,14 @@ def draw_orthogonal_grid( A Figure and Axes instance """ + # gather agent data s_default = (180 / max(space.width, space.height)) ** 2 arguments = collect_agent_data(space, agent_portrayal, s_default=s_default) + # plot the agents + _scatter(ax, arguments) + + # further styling ax.set_xlim(-0.5, space.width - 0.5) ax.set_ylim(-0.5, space.height - 0.5) @@ -249,7 +253,7 @@ def draw_orthogonal_grid( for y in np.arange(-0.5, space.height - 0.5, 1): ax.axhline(y, color="gray", linestyle=":") - _scatter(ax, arguments) + def draw_hex_grid( @@ -270,19 +274,20 @@ def draw_hex_grid( A Figure and Axes instance """ + # gather data s_default = (180 / max(space.width, space.height)) ** 2 arguments = collect_agent_data(space, agent_portrayal, s_default=s_default) - # give all odd rows an offset in the x direction + + # for hexgrids we have to go from logical coordinates to visual coordinates + # this is a bit messy. + + # give all even rows an offset in the x direction # give all rows an offset in the y direction + # numbers here are based on a distance of 1 between centers of hexes offset = math.sqrt(0.75) - # logical = np.mod(arguments["y"], 2) == 1 - # arguments["y"] = arguments["y"].astype(float) * offset - # arguments["x"] = arguments["x"].astype(float) - # arguments["x"][logical] += 0.5 - loc = arguments["loc"].astype(float) logical = np.mod(loc[:, 1], 2) == 0 @@ -290,11 +295,13 @@ def draw_hex_grid( loc[:, 1] *= offset arguments["loc"] = loc + # plot the agents + _scatter(ax, arguments) + + # further styling and adding of grid ax.set_xlim(-1, space.width + 0.5) ax.set_ylim(-offset, space.height * offset) - _scatter(ax, arguments) - def setup_hexmesh( width, height, @@ -320,6 +327,7 @@ def setup_hexmesh( ) return mesh + # add grid ax.add_collection( setup_hexmesh( space.width, @@ -342,6 +350,7 @@ def draw_network(space: Network, agent_portrayal: Callable, ax): """ + # gather locations for nodes in network graph = space.G pos = nx.spring_layout(graph, seed=0) x, y = list(zip(*pos.values())) @@ -351,6 +360,7 @@ def draw_network(space: Network, agent_portrayal: Callable, ax): width = xmax - xmin height = ymax - ymin + # gather agent data s_default = (180 / max(width, height)) ** 2 arguments = collect_agent_data(space, agent_portrayal, s_default=s_default) @@ -359,11 +369,15 @@ def draw_network(space: Network, agent_portrayal: Callable, ax): pos = np.asarray(list(pos.values())) arguments["loc"] = pos[arguments["loc"]] + # plot the agents + _scatter(ax, arguments) + + + # further styling ax.set_axis_off() ax.set_xlim(xmin=xmin, xmax=xmax) ax.set_ylim(ymin=ymin, ymax=ymax) - _scatter(ax, arguments) nx.draw_networkx_edges(graph, pos, ax=ax) @@ -379,17 +393,21 @@ def draw_continuous_space(space: ContinuousSpace, agent_portrayal: Callable, ax) A Figure and Axes instance """ + # space related setup width = space.x_max - space.x_min x_padding = width / 20 height = space.y_max - space.y_min y_padding = height / 20 + # gather agent data s_default = (180 / max(width, height)) ** 2 arguments = collect_agent_data(space, agent_portrayal, s_default=s_default) - border_style = "solid" if not space.torus else (0, (5, 10)) + # plot the agents + _scatter(ax, arguments) - # Set the border of the plot + # further visual styling + border_style = "solid" if not space.torus else (0, (5, 10)) for spine in ax.spines.values(): spine.set_linewidth(1.5) spine.set_color("black") @@ -398,7 +416,7 @@ def draw_continuous_space(space: ContinuousSpace, agent_portrayal: Callable, ax) ax.set_xlim(space.x_min - x_padding, space.x_max + x_padding) ax.set_ylim(space.y_min - y_padding, space.y_max + y_padding) - _scatter(ax, arguments) + def draw_voroinoi_grid(space: VoronoiGrid, agent_portrayal: Callable, ax): From cad794f20915c7038f189d47f6531d44d3c70d95 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 17:45:34 +0000 Subject: [PATCH 41/74] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/visualization/components/matplotlib.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 4c20117d1e9..a6981c9db13 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -254,8 +254,6 @@ def draw_orthogonal_grid( ax.axhline(y, color="gray", linestyle=":") - - def draw_hex_grid( space: HexGrid, agent_portrayal: Callable, @@ -278,7 +276,6 @@ def draw_hex_grid( s_default = (180 / max(space.width, space.height)) ** 2 arguments = collect_agent_data(space, agent_portrayal, s_default=s_default) - # for hexgrids we have to go from logical coordinates to visual coordinates # this is a bit messy. @@ -372,7 +369,6 @@ def draw_network(space: Network, agent_portrayal: Callable, ax): # plot the agents _scatter(ax, arguments) - # further styling ax.set_axis_off() ax.set_xlim(xmin=xmin, xmax=xmax) @@ -417,8 +413,6 @@ def draw_continuous_space(space: ContinuousSpace, agent_portrayal: Callable, ax) ax.set_ylim(space.y_min - y_padding, space.y_max + y_padding) - - def draw_voroinoi_grid(space: VoronoiGrid, agent_portrayal: Callable, ax): """Visualize a voronoi grid. From dfe15df194fdb7724c540d067413b90162ea3272 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Mon, 28 Oct 2024 19:30:37 +0100 Subject: [PATCH 42/74] minor updates --- mesa/examples/advanced/wolf_sheep/app.py | 6 +++++- mesa/visualization/components/matplotlib.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/mesa/examples/advanced/wolf_sheep/app.py b/mesa/examples/advanced/wolf_sheep/app.py index 5270be7a753..7ccdc938193 100644 --- a/mesa/examples/advanced/wolf_sheep/app.py +++ b/mesa/examples/advanced/wolf_sheep/app.py @@ -1,3 +1,7 @@ +import sys +import os.path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../../'))) + from mesa.examples.advanced.wolf_sheep.agents import GrassPatch, Sheep, Wolf from mesa.examples.advanced.wolf_sheep.model import WolfSheep from mesa.visualization import ( @@ -64,7 +68,7 @@ def wolf_sheep_portrayal(agent): space_component = make_space_matplotlib(wolf_sheep_portrayal) -lineplot_component = make_plot_measure(["Wolves", "Sheep", "Grass"]) +lineplot_component = make_plot_measure({"Wolves":"tab:orange", "Sheep":"tab:blue", "Grass":"tab:green"}) model = WolfSheep(grass=True) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index a6981c9db13..5d8df4b4fba 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -75,7 +75,7 @@ def SpaceMatplotlib( space = getattr(model, "space", None) fig = Figure() - ax = fig.subplots(111) + ax = fig.subplots() # https://stackoverflow.com/questions/67524641/convert-multiple-isinstance-checks-to-structural-pattern-matching match space: From 95b963fec9f456fce0516500459fa44f89dcf1c8 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Mon, 28 Oct 2024 20:52:52 +0100 Subject: [PATCH 43/74] Update matplotlib.py first step towards addressing #2243 --- mesa/visualization/components/matplotlib.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 5d8df4b4fba..23db6a52614 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -333,13 +333,16 @@ def setup_hexmesh( ) -def draw_network(space: Network, agent_portrayal: Callable, ax): +def draw_network(space: Network, agent_portrayal: Callable, ax, + layout_alg=nx.spring_layout, layout_kwargs={"seed":0}): """Visualize a network space. Args: space: the space to visualize agent_portrayal: a callable that is called with the agent and returns a dict ax: a Matplotlib Axes instance + layout_alg: a networkx layout algorithm or other callable with the same behavior + layout_kwargs: a dictionary of keyword arguments for the layout algorithm Notes: this uses networkx.draw under the hood so agent portrayal fields should match those used there @@ -349,7 +352,7 @@ def draw_network(space: Network, agent_portrayal: Callable, ax): """ # gather locations for nodes in network graph = space.G - pos = nx.spring_layout(graph, seed=0) + pos = layout_alg(graph, **layout_kwargs) x, y = list(zip(*pos.values())) xmin, xmax = min(x), max(x) ymin, ymax = min(y), max(y) From d5f69c1bf2659811ac69bda11a435c318fbefadf Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Mon, 28 Oct 2024 20:57:53 +0100 Subject: [PATCH 44/74] add boolean kwarg to control grid drawing --- mesa/visualization/components/matplotlib.py | 46 +++++++++++++-------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 23db6a52614..521588f869f 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -224,6 +224,7 @@ def draw_orthogonal_grid( space: OrthogonalGrid, agent_portrayal: Callable, ax, + draw_grid:bool =True ): """Visualize a orthogonal grid. @@ -231,6 +232,7 @@ def draw_orthogonal_grid( space: the space to visualize agent_portrayal: a callable that is called with the agent and returns a dict ax: a Matplotlib Axes instance + draw_grid: whether to draw the grid Returns: A Figure and Axes instance @@ -247,26 +249,27 @@ def draw_orthogonal_grid( ax.set_xlim(-0.5, space.width - 0.5) ax.set_ylim(-0.5, space.height - 0.5) - # Draw grid lines - for x in np.arange(-0.5, space.width - 0.5, 1): - ax.axvline(x, color="gray", linestyle=":") - for y in np.arange(-0.5, space.height - 0.5, 1): - ax.axhline(y, color="gray", linestyle=":") + if draw_grid: + # Draw grid lines + for x in np.arange(-0.5, space.width - 0.5, 1): + ax.axvline(x, color="gray", linestyle=":") + for y in np.arange(-0.5, space.height - 0.5, 1): + ax.axhline(y, color="gray", linestyle=":") def draw_hex_grid( space: HexGrid, agent_portrayal: Callable, ax, + draw_grid: bool = True ): """Visualize a hex grid. Args: space: the space to visualize agent_portrayal: a callable that is called with the agent and returns a dict - propertylayer_portrayal: a callable that is called with the agent and returns a dict - model: a model instance ax: a Matplotlib Axes instance + draw_grid: whether to draw the grid Returns: A Figure and Axes instance @@ -324,23 +327,26 @@ def setup_hexmesh( ) return mesh - # add grid - ax.add_collection( - setup_hexmesh( - space.width, - space.height, + if draw_grid: + # add grid + ax.add_collection( + setup_hexmesh( + space.width, + space.height, + ) ) - ) -def draw_network(space: Network, agent_portrayal: Callable, ax, - layout_alg=nx.spring_layout, layout_kwargs={"seed":0}): +def draw_network(space: Network, agent_portrayal: Callable, ax, draw_grid: bool=True, + layout_alg=nx.spring_layout, layout_kwargs={"seed":0}, + ): """Visualize a network space. Args: space: the space to visualize agent_portrayal: a callable that is called with the agent and returns a dict ax: a Matplotlib Axes instance + draw_grid: whether to draw the grid layout_alg: a networkx layout algorithm or other callable with the same behavior layout_kwargs: a dictionary of keyword arguments for the layout algorithm @@ -359,6 +365,8 @@ def draw_network(space: Network, agent_portrayal: Callable, ax, width = xmax - xmin height = ymax - ymin + x_padding = width / 20 + y_padding = height / 20 # gather agent data s_default = (180 / max(width, height)) ** 2 @@ -374,10 +382,12 @@ def draw_network(space: Network, agent_portrayal: Callable, ax, # further styling ax.set_axis_off() - ax.set_xlim(xmin=xmin, xmax=xmax) - ax.set_ylim(ymin=ymin, ymax=ymax) + ax.set_xlim(xmin=xmin-x_padding, xmax=xmax+x_padding) + ax.set_ylim(ymin=ymin-y_padding, ymax=ymax+y_padding) - nx.draw_networkx_edges(graph, pos, ax=ax) + if draw_grid: + # fixme we need to draw the empty nodes as well + nx.draw_networkx_edges(graph, pos, ax=ax) def draw_continuous_space(space: ContinuousSpace, agent_portrayal: Callable, ax): From 9d2c970bc7f9d91bc2adf414d8b7aebde378889e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 20:06:13 +0000 Subject: [PATCH 45/74] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/examples/advanced/wolf_sheep/app.py | 11 ++++++--- mesa/visualization/components/matplotlib.py | 25 ++++++++++----------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/mesa/examples/advanced/wolf_sheep/app.py b/mesa/examples/advanced/wolf_sheep/app.py index 7ccdc938193..f34d19d32eb 100644 --- a/mesa/examples/advanced/wolf_sheep/app.py +++ b/mesa/examples/advanced/wolf_sheep/app.py @@ -1,6 +1,9 @@ -import sys import os.path -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../../'))) +import sys + +sys.path.insert( + 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../")) +) from mesa.examples.advanced.wolf_sheep.agents import GrassPatch, Sheep, Wolf from mesa.examples.advanced.wolf_sheep.model import WolfSheep @@ -68,7 +71,9 @@ def wolf_sheep_portrayal(agent): space_component = make_space_matplotlib(wolf_sheep_portrayal) -lineplot_component = make_plot_measure({"Wolves":"tab:orange", "Sheep":"tab:blue", "Grass":"tab:green"}) +lineplot_component = make_plot_measure( + {"Wolves": "tab:orange", "Sheep": "tab:blue", "Grass": "tab:green"} +) model = WolfSheep(grass=True) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 521588f869f..58030c407fc 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -221,10 +221,7 @@ def collect_agent_data( def draw_orthogonal_grid( - space: OrthogonalGrid, - agent_portrayal: Callable, - ax, - draw_grid:bool =True + space: OrthogonalGrid, agent_portrayal: Callable, ax, draw_grid: bool = True ): """Visualize a orthogonal grid. @@ -258,10 +255,7 @@ def draw_orthogonal_grid( def draw_hex_grid( - space: HexGrid, - agent_portrayal: Callable, - ax, - draw_grid: bool = True + space: HexGrid, agent_portrayal: Callable, ax, draw_grid: bool = True ): """Visualize a hex grid. @@ -337,9 +331,14 @@ def setup_hexmesh( ) -def draw_network(space: Network, agent_portrayal: Callable, ax, draw_grid: bool=True, - layout_alg=nx.spring_layout, layout_kwargs={"seed":0}, - ): +def draw_network( + space: Network, + agent_portrayal: Callable, + ax, + draw_grid: bool = True, + layout_alg=nx.spring_layout, + layout_kwargs={"seed": 0}, +): """Visualize a network space. Args: @@ -382,8 +381,8 @@ def draw_network(space: Network, agent_portrayal: Callable, ax, draw_grid: bool= # further styling ax.set_axis_off() - ax.set_xlim(xmin=xmin-x_padding, xmax=xmax+x_padding) - ax.set_ylim(ymin=ymin-y_padding, ymax=ymax+y_padding) + ax.set_xlim(xmin=xmin - x_padding, xmax=xmax + x_padding) + ax.set_ylim(ymin=ymin - y_padding, ymax=ymax + y_padding) if draw_grid: # fixme we need to draw the empty nodes as well From aa2110f239d86421f7f566d1dda87673a16959c7 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Mon, 28 Oct 2024 21:18:33 +0100 Subject: [PATCH 46/74] Update matplotlib.py --- mesa/visualization/components/matplotlib.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 58030c407fc..63ca5bc0a43 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -386,7 +386,8 @@ def draw_network( if draw_grid: # fixme we need to draw the empty nodes as well - nx.draw_networkx_edges(graph, pos, ax=ax) + edge_collection = nx.draw_networkx_edges(graph, pos, ax=ax, alpha=0.5, style='--') + edge_collection.set_zorder(0) def draw_continuous_space(space: ContinuousSpace, agent_portrayal: Callable, ax): From cdea05172c37791e9366568eaf6dc6a3092c0d51 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 20:20:59 +0000 Subject: [PATCH 47/74] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/visualization/components/matplotlib.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 63ca5bc0a43..7905997a7f9 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -386,7 +386,9 @@ def draw_network( if draw_grid: # fixme we need to draw the empty nodes as well - edge_collection = nx.draw_networkx_edges(graph, pos, ax=ax, alpha=0.5, style='--') + edge_collection = nx.draw_networkx_edges( + graph, pos, ax=ax, alpha=0.5, style="--" + ) edge_collection.set_zorder(0) From 22f57e47de4b510a138573f3147768fca5d7145a Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Mon, 28 Oct 2024 21:48:22 +0100 Subject: [PATCH 48/74] ruff related fix --- mesa/visualization/components/matplotlib.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 7905997a7f9..d08de57d1ec 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -337,7 +337,7 @@ def draw_network( ax, draw_grid: bool = True, layout_alg=nx.spring_layout, - layout_kwargs={"seed": 0}, + layout_kwargs=None, ): """Visualize a network space. @@ -355,6 +355,9 @@ def draw_network( """ + if layout_kwargs is None: + layout_kwargs = {"seed": 0} + # gather locations for nodes in network graph = space.G pos = layout_alg(graph, **layout_kwargs) From 511214e6591f7c4fb87429283100289ef33e7e35 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Mon, 28 Oct 2024 21:49:12 +0100 Subject: [PATCH 49/74] Update app.py --- mesa/examples/advanced/wolf_sheep/app.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/mesa/examples/advanced/wolf_sheep/app.py b/mesa/examples/advanced/wolf_sheep/app.py index f34d19d32eb..bea98fd3630 100644 --- a/mesa/examples/advanced/wolf_sheep/app.py +++ b/mesa/examples/advanced/wolf_sheep/app.py @@ -1,10 +1,3 @@ -import os.path -import sys - -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../")) -) - from mesa.examples.advanced.wolf_sheep.agents import GrassPatch, Sheep, Wolf from mesa.examples.advanced.wolf_sheep.model import WolfSheep from mesa.visualization import ( From 0fa5c1f78836c84b552e324814f1f9de5e3c774f Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Tue, 29 Oct 2024 16:53:42 +0100 Subject: [PATCH 50/74] make ax optional --- mesa/visualization/components/matplotlib.py | 39 +++++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index d08de57d1ec..7e81cdb1f44 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -10,6 +10,8 @@ import networkx as nx import numpy as np import solara + +from matplotlib.axes import Axes from matplotlib.cm import ScalarMappable from matplotlib.collections import PatchCollection from matplotlib.colors import LinearSegmentedColormap, Normalize, to_rgba @@ -221,20 +223,23 @@ def collect_agent_data( def draw_orthogonal_grid( - space: OrthogonalGrid, agent_portrayal: Callable, ax, draw_grid: bool = True + space: OrthogonalGrid, agent_portrayal: Callable, ax: Axes = None, draw_grid: bool = True ): """Visualize a orthogonal grid. Args: space: the space to visualize agent_portrayal: a callable that is called with the agent and returns a dict - ax: a Matplotlib Axes instance + ax: a Matplotlib Axes instance. If none is provided a new figure and ax will be created using plt.subplots draw_grid: whether to draw the grid Returns: A Figure and Axes instance """ + if ax is None: + fig, ax = plt.subplots() + # gather agent data s_default = (180 / max(space.width, space.height)) ** 2 arguments = collect_agent_data(space, agent_portrayal, s_default=s_default) @@ -255,20 +260,23 @@ def draw_orthogonal_grid( def draw_hex_grid( - space: HexGrid, agent_portrayal: Callable, ax, draw_grid: bool = True + space: HexGrid, agent_portrayal: Callable, ax: Axes = None , draw_grid: bool = True ): """Visualize a hex grid. Args: space: the space to visualize agent_portrayal: a callable that is called with the agent and returns a dict - ax: a Matplotlib Axes instance + ax: a Matplotlib Axes instance. If none is provided a new figure and ax will be created using plt.subplots draw_grid: whether to draw the grid Returns: A Figure and Axes instance """ + if ax is None: + fig, ax = plt.subplots() + # gather data s_default = (180 / max(space.width, space.height)) ** 2 arguments = collect_agent_data(space, agent_portrayal, s_default=s_default) @@ -334,7 +342,7 @@ def setup_hexmesh( def draw_network( space: Network, agent_portrayal: Callable, - ax, + ax: Axes = None , draw_grid: bool = True, layout_alg=nx.spring_layout, layout_kwargs=None, @@ -344,7 +352,7 @@ def draw_network( Args: space: the space to visualize agent_portrayal: a callable that is called with the agent and returns a dict - ax: a Matplotlib Axes instance + ax: a Matplotlib Axes instance. If none is provided a new figure and ax will be created using plt.subplots draw_grid: whether to draw the grid layout_alg: a networkx layout algorithm or other callable with the same behavior layout_kwargs: a dictionary of keyword arguments for the layout algorithm @@ -355,6 +363,8 @@ def draw_network( """ + if ax is None: + fig, ax = plt.subplots() if layout_kwargs is None: layout_kwargs = {"seed": 0} @@ -395,18 +405,21 @@ def draw_network( edge_collection.set_zorder(0) -def draw_continuous_space(space: ContinuousSpace, agent_portrayal: Callable, ax): +def draw_continuous_space(space: ContinuousSpace, agent_portrayal: Callable, ax: Axes = None ): """Visualize a continuous space. Args: space: the space to visualize agent_portrayal: a callable that is called with the agent and returns a dict - ax: a Matplotlib Axes instance + ax: a Matplotlib Axes instance. If none is provided a new figure and ax will be created using plt.subplots Returns: A Figure and Axes instance """ + if ax is None: + fig, ax = plt.subplots() + # space related setup width = space.x_max - space.x_min x_padding = width / 20 @@ -431,18 +444,21 @@ def draw_continuous_space(space: ContinuousSpace, agent_portrayal: Callable, ax) ax.set_ylim(space.y_min - y_padding, space.y_max + y_padding) -def draw_voroinoi_grid(space: VoronoiGrid, agent_portrayal: Callable, ax): +def draw_voroinoi_grid(space: VoronoiGrid, agent_portrayal: Callable, ax: Axes = None ): """Visualize a voronoi grid. Args: space: the space to visualize agent_portrayal: a callable that is called with the agent and returns a dict - ax: a Matplotlib Axes instance + ax: a Matplotlib Axes instance. If none is provided a new figure and ax will be created using plt.subplots Returns: A Figure and Axes instance """ + if ax is None: + fig, ax = plt.subplots() + x_list = [i[0] for i in space.centroids_coordinates] y_list = [i[1] for i in space.centroids_coordinates] x_max = max(x_list) @@ -474,7 +490,8 @@ def draw_voroinoi_grid(space: VoronoiGrid, agent_portrayal: Callable, ax): ax.plot(*zip(*polygon), color="black") # Plot polygon edges in black -def _scatter(ax, arguments): +def _scatter(ax: Axes, arguments): + """Helper function for plotting the agents.""" loc = arguments.pop("loc") x = loc[:, 0] From 7914328e9d2403b0245ffc10e56e924dfdf215db Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:53:55 +0000 Subject: [PATCH 51/74] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/visualization/components/matplotlib.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 7e81cdb1f44..b7dad465e57 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -10,7 +10,6 @@ import networkx as nx import numpy as np import solara - from matplotlib.axes import Axes from matplotlib.cm import ScalarMappable from matplotlib.collections import PatchCollection @@ -223,7 +222,10 @@ def collect_agent_data( def draw_orthogonal_grid( - space: OrthogonalGrid, agent_portrayal: Callable, ax: Axes = None, draw_grid: bool = True + space: OrthogonalGrid, + agent_portrayal: Callable, + ax: Axes = None, + draw_grid: bool = True, ): """Visualize a orthogonal grid. @@ -260,7 +262,7 @@ def draw_orthogonal_grid( def draw_hex_grid( - space: HexGrid, agent_portrayal: Callable, ax: Axes = None , draw_grid: bool = True + space: HexGrid, agent_portrayal: Callable, ax: Axes = None, draw_grid: bool = True ): """Visualize a hex grid. @@ -342,7 +344,7 @@ def setup_hexmesh( def draw_network( space: Network, agent_portrayal: Callable, - ax: Axes = None , + ax: Axes = None, draw_grid: bool = True, layout_alg=nx.spring_layout, layout_kwargs=None, @@ -405,7 +407,9 @@ def draw_network( edge_collection.set_zorder(0) -def draw_continuous_space(space: ContinuousSpace, agent_portrayal: Callable, ax: Axes = None ): +def draw_continuous_space( + space: ContinuousSpace, agent_portrayal: Callable, ax: Axes = None +): """Visualize a continuous space. Args: @@ -444,7 +448,7 @@ def draw_continuous_space(space: ContinuousSpace, agent_portrayal: Callable, ax: ax.set_ylim(space.y_min - y_padding, space.y_max + y_padding) -def draw_voroinoi_grid(space: VoronoiGrid, agent_portrayal: Callable, ax: Axes = None ): +def draw_voroinoi_grid(space: VoronoiGrid, agent_portrayal: Callable, ax: Axes = None): """Visualize a voronoi grid. Args: From d9db041353de050253215193ffeeedc9b5f3b188 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Tue, 29 Oct 2024 17:22:17 +0100 Subject: [PATCH 52/74] add a draw_space function and improve typing --- mesa/visualization/components/matplotlib.py | 50 ++++++++++++++------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index b7dad465e57..0ce58eed49d 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -76,30 +76,46 @@ def SpaceMatplotlib( space = getattr(model, "space", None) fig = Figure() - ax = fig.subplots() + ax = fig.add_subplot() + + draw_space(space, agent_portrayal, propertylayer_portrayal=propertylayer_portrayal, ax=ax) + + solara.FigureMatplotlib( + fig, format="png", bbox_inches="tight", dependencies=dependencies + ) + + +def draw_space(space, agent_portrayal: Callable, propertylayer_portrayal: dict | None = None, ax: Axes | None = None): + """Draw a Matplotlib-based visualization of the space. + + Args: + space: the space of the mesa model + agent_portrayal: A callable that returns a dict specifying how to show the agent + propertylayer_portrayal: a dict specifying how to show propertylayer(s) + ax: the axes upon which to draw the plot + + """ + if ax is None: + fig, ax = plt.subplots() # https://stackoverflow.com/questions/67524641/convert-multiple-isinstance-checks-to-structural-pattern-matching match space: case mesa.space._Grid() | OrthogonalMooreGrid() | OrthogonalVonNeumannGrid(): - draw_orthogonal_grid(space, agent_portrayal, ax) + draw_orthogonal_grid(space, agent_portrayal, ax=ax) case HexSingleGrid() | HexMultiGrid() | mesa.experimental.cell_space.HexGrid(): - draw_hex_grid(space, agent_portrayal, ax) + draw_hex_grid(space, agent_portrayal, ax=ax) case mesa.space.NetworkGrid() | mesa.experimental.cell_space.Network(): - draw_network(space, agent_portrayal, ax) + draw_network(space, agent_portrayal, ax=ax) case mesa.space.ContinuousSpace(): - draw_continuous_space(space, agent_portrayal, ax) + draw_continuous_space(space, agent_portrayal, ax=ax) case VoronoiGrid(): - draw_voroinoi_grid(space, agent_portrayal, ax) + draw_voroinoi_grid(space, agent_portrayal, ax=ax) if propertylayer_portrayal: - draw_property_layers(space, propertylayer_portrayal, ax) - - solara.FigureMatplotlib( - fig, format="png", bbox_inches="tight", dependencies=dependencies - ) + draw_property_layers(space, propertylayer_portrayal, ax=ax) -def draw_property_layers(space, propertylayer_portrayal: dict[str, dict[str, Any]], ax): +def draw_property_layers(space, propertylayer_portrayal: dict[str, dict[str, Any]], ax: Axes): """Draw PropertyLayers on the given axes. Args: @@ -224,7 +240,7 @@ def collect_agent_data( def draw_orthogonal_grid( space: OrthogonalGrid, agent_portrayal: Callable, - ax: Axes = None, + ax: Axes | None = None, draw_grid: bool = True, ): """Visualize a orthogonal grid. @@ -262,7 +278,7 @@ def draw_orthogonal_grid( def draw_hex_grid( - space: HexGrid, agent_portrayal: Callable, ax: Axes = None, draw_grid: bool = True + space: HexGrid, agent_portrayal: Callable, ax: Axes | None = None, draw_grid: bool = True ): """Visualize a hex grid. @@ -344,7 +360,7 @@ def setup_hexmesh( def draw_network( space: Network, agent_portrayal: Callable, - ax: Axes = None, + ax: Axes | None = None, draw_grid: bool = True, layout_alg=nx.spring_layout, layout_kwargs=None, @@ -408,7 +424,7 @@ def draw_network( def draw_continuous_space( - space: ContinuousSpace, agent_portrayal: Callable, ax: Axes = None + space: ContinuousSpace, agent_portrayal: Callable, ax: Axes | None = None ): """Visualize a continuous space. @@ -448,7 +464,7 @@ def draw_continuous_space( ax.set_ylim(space.y_min - y_padding, space.y_max + y_padding) -def draw_voroinoi_grid(space: VoronoiGrid, agent_portrayal: Callable, ax: Axes = None): +def draw_voroinoi_grid(space: VoronoiGrid, agent_portrayal: Callable, ax: Axes | None = None): """Visualize a voronoi grid. Args: From f9adfba63763642680eb00192c9ad4493f3908ac Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:22:27 +0000 Subject: [PATCH 53/74] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/visualization/components/matplotlib.py | 24 ++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 0ce58eed49d..f6a34d6937a 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -78,14 +78,21 @@ def SpaceMatplotlib( fig = Figure() ax = fig.add_subplot() - draw_space(space, agent_portrayal, propertylayer_portrayal=propertylayer_portrayal, ax=ax) + draw_space( + space, agent_portrayal, propertylayer_portrayal=propertylayer_portrayal, ax=ax + ) solara.FigureMatplotlib( fig, format="png", bbox_inches="tight", dependencies=dependencies ) -def draw_space(space, agent_portrayal: Callable, propertylayer_portrayal: dict | None = None, ax: Axes | None = None): +def draw_space( + space, + agent_portrayal: Callable, + propertylayer_portrayal: dict | None = None, + ax: Axes | None = None, +): """Draw a Matplotlib-based visualization of the space. Args: @@ -115,7 +122,9 @@ def draw_space(space, agent_portrayal: Callable, propertylayer_portrayal: dict | draw_property_layers(space, propertylayer_portrayal, ax=ax) -def draw_property_layers(space, propertylayer_portrayal: dict[str, dict[str, Any]], ax: Axes): +def draw_property_layers( + space, propertylayer_portrayal: dict[str, dict[str, Any]], ax: Axes +): """Draw PropertyLayers on the given axes. Args: @@ -278,7 +287,10 @@ def draw_orthogonal_grid( def draw_hex_grid( - space: HexGrid, agent_portrayal: Callable, ax: Axes | None = None, draw_grid: bool = True + space: HexGrid, + agent_portrayal: Callable, + ax: Axes | None = None, + draw_grid: bool = True, ): """Visualize a hex grid. @@ -464,7 +476,9 @@ def draw_continuous_space( ax.set_ylim(space.y_min - y_padding, space.y_max + y_padding) -def draw_voroinoi_grid(space: VoronoiGrid, agent_portrayal: Callable, ax: Axes | None = None): +def draw_voroinoi_grid( + space: VoronoiGrid, agent_portrayal: Callable, ax: Axes | None = None +): """Visualize a voronoi grid. Args: From aad0a8cdc197817582f7cdad23e92056450fc9f5 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Tue, 29 Oct 2024 17:25:39 +0100 Subject: [PATCH 54/74] Update matplotlib.py --- mesa/visualization/components/matplotlib.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index f6a34d6937a..843a8ef73a2 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -224,7 +224,7 @@ def collect_agent_data( s_default: default size Notes: - agent portray dict is limited to s (size of marker), c (color of marker, and marker (marker style) + agent portray dict is limited to size (size of marker), color (color of marker, and marker (marker style) see `Matplotlib`_. @@ -239,8 +239,8 @@ def collect_agent_data( loc = agent.cell.coordinate arguments["loc"].append(loc) - arguments["s"].append(portray.get("s", s_default)) - arguments["c"].append(portray.get("c", c_default)) + arguments["s"].append(portray.get("size", s_default)) + arguments["c"].append(portray.get("color", c_default)) arguments["marker"].append(portray.get("marker", marker_default)) return {k: np.asarray(v) for k, v in arguments.items()} From 6d7fae8013903484200ff1b207c3224b7d049b21 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Tue, 29 Oct 2024 17:36:59 +0100 Subject: [PATCH 55/74] reorganization of file and warning added for ignored fields --- mesa/visualization/components/matplotlib.py | 95 +++++++++++---------- 1 file changed, 50 insertions(+), 45 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 843a8ef73a2..1c48833a490 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -87,6 +87,51 @@ def SpaceMatplotlib( ) +def collect_agent_data( + space: OrthogonalGrid | HexGrid | Network | ContinuousSpace | VoronoiGrid, + agent_portrayal: Callable, + color="tab:blue", + size=25, + marker="o", +): + """Collect the plotting data for all agents in the space. + + Args: + space: The space containing the Agents. + agent_portrayal: A callable that is called with the agent and returns a dict + loc: a boolean indicating whether to gather agent x, y data or not + color: default color + marker: default marker + size: default size + + Notes: + agent portray dict is limited to size (size of marker), color (color of marker, and marker (marker style) + see `Matplotlib`_. + + + .. _Matplotlib: https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.scatter.html + + """ + arguments = {"loc": [], "s": [], "c": [], "marker": []} + for agent in space.agents: + portray = agent_portrayal(agent) + loc = agent.pos + if loc is None: + loc = agent.cell.coordinate + + arguments["loc"].append(loc) + arguments["s"].append(portray.pop("size", size)) + arguments["c"].append(portray.pop("color", color)) + arguments["marker"].append(portray.pop("marker", marker)) + + if len(portray) > 0: + ignored_fields = list(portray.keys()) + msg = ", ".join(ignored_fields) + warnings.warn(f"the following fields are not used in agent portrayal and thus ignored: {msg}.") + + return {k: np.asarray(v) for k, v in arguments.items()} + + def draw_space( space, agent_portrayal: Callable, @@ -206,46 +251,6 @@ def draw_property_layers( ) -def collect_agent_data( - space: OrthogonalGrid | HexGrid | Network | ContinuousSpace, - agent_portrayal: Callable, - c_default="tab:blue", - marker_default="o", - s_default=25, -): - """Collect the plotting data for all agents in the space. - - Args: - space: The space containing the Agents. - agent_portrayal: A callable that is called with the agent and returns a dict - loc: a boolean indicating whether to gather agent x, y data or not - c_default: default color - marker_default: default marker - s_default: default size - - Notes: - agent portray dict is limited to size (size of marker), color (color of marker, and marker (marker style) - see `Matplotlib`_. - - - .. _Matplotlib: https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.scatter.html - - """ - arguments = {"loc": [], "s": [], "c": [], "marker": []} - for agent in space.agents: - portray = agent_portrayal(agent) - loc = agent.pos - if loc is None: - loc = agent.cell.coordinate - - arguments["loc"].append(loc) - arguments["s"].append(portray.get("size", s_default)) - arguments["c"].append(portray.get("color", c_default)) - arguments["marker"].append(portray.get("marker", marker_default)) - - return {k: np.asarray(v) for k, v in arguments.items()} - - def draw_orthogonal_grid( space: OrthogonalGrid, agent_portrayal: Callable, @@ -269,7 +274,7 @@ def draw_orthogonal_grid( # gather agent data s_default = (180 / max(space.width, space.height)) ** 2 - arguments = collect_agent_data(space, agent_portrayal, s_default=s_default) + arguments = collect_agent_data(space, agent_portrayal, size=s_default) # plot the agents _scatter(ax, arguments) @@ -309,7 +314,7 @@ def draw_hex_grid( # gather data s_default = (180 / max(space.width, space.height)) ** 2 - arguments = collect_agent_data(space, agent_portrayal, s_default=s_default) + arguments = collect_agent_data(space, agent_portrayal, size=s_default) # for hexgrids we have to go from logical coordinates to visual coordinates # this is a bit messy. @@ -412,7 +417,7 @@ def draw_network( # gather agent data s_default = (180 / max(width, height)) ** 2 - arguments = collect_agent_data(space, agent_portrayal, s_default=s_default) + arguments = collect_agent_data(space, agent_portrayal, size=s_default) # this assumes that nodes are identified by an integer # which is true for default nx graphs but might user changeable @@ -460,7 +465,7 @@ def draw_continuous_space( # gather agent data s_default = (180 / max(width, height)) ** 2 - arguments = collect_agent_data(space, agent_portrayal, s_default=s_default) + arguments = collect_agent_data(space, agent_portrayal, size=s_default) # plot the agents _scatter(ax, arguments) @@ -506,7 +511,7 @@ def draw_voroinoi_grid( y_padding = height / 20 s_default = (180 / max(width, height)) ** 2 - arguments = collect_agent_data(space, agent_portrayal, s_default=s_default) + arguments = collect_agent_data(space, agent_portrayal, size=s_default) ax.set_xlim(x_min - x_padding, x_max + x_padding) ax.set_ylim(y_min - y_padding, y_max + y_padding) From de5ec29940736d1cd47a67736aae1d841f95126f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:38:02 +0000 Subject: [PATCH 56/74] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/visualization/components/matplotlib.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 1c48833a490..d44c8ad607f 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -127,7 +127,9 @@ def collect_agent_data( if len(portray) > 0: ignored_fields = list(portray.keys()) msg = ", ".join(ignored_fields) - warnings.warn(f"the following fields are not used in agent portrayal and thus ignored: {msg}.") + warnings.warn( + f"the following fields are not used in agent portrayal and thus ignored: {msg}." + ) return {k: np.asarray(v) for k, v in arguments.items()} From e6938c1623217b39395bae275f4b811a185e74cc Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Tue, 29 Oct 2024 17:58:43 +0100 Subject: [PATCH 57/74] further doc updates --- mesa/visualization/components/matplotlib.py | 37 ++++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index d44c8ad607f..188afd5a291 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -40,12 +40,22 @@ Network = NetworkGrid | mesa.experimental.cell_space.Network -def make_space_matplotlib(agent_portrayal=None, propertylayer_portrayal=None): +def make_space_matplotlib(agent_portrayal: Callable | None = None, + propertylayer_portrayal: dict | None = None, + post_process: Callable | None = None, + **space_drawing_kwargs): """Create a Matplotlib-based space visualization component. Args: - agent_portrayal (function): Function to portray agents - propertylayer_portrayal (dict): Dictionary of PropertyLayer portrayal specifications + agent_portrayal: Function to portray agents. + propertylayer_portrayal: Dictionary of PropertyLayer portrayal specifications + post_process : a callable that will be called with the Axes instance. Allows for fine tuning plots (e.g., control ticks) + space_drawing_kwargs : additional keyword arguments to be passed on to the underlying space drawer function. See + the functions for drawing the various spaces for further details. + + ``agent_portrayal`` is called with an agent and should return a dict. Valid fields in this dict are "color", + "size", and "marker". Other field are ignored and will result in a user warning. + Returns: function: A function that creates a SpaceMatplotlib component @@ -53,10 +63,10 @@ def make_space_matplotlib(agent_portrayal=None, propertylayer_portrayal=None): if agent_portrayal is None: def agent_portrayal(a): - return {"id": a.unique_id} + return {} def MakeSpaceMatplotlib(model): - return SpaceMatplotlib(model, agent_portrayal, propertylayer_portrayal) + return SpaceMatplotlib(model, agent_portrayal, propertylayer_portrayal, post_process=post_process, **space_drawing_kwargs) return MakeSpaceMatplotlib @@ -67,6 +77,8 @@ def SpaceMatplotlib( agent_portrayal, propertylayer_portrayal, dependencies: list[any] | None = None, + post_process: Callable| None = None, + **space_drawing_kwargs ): """Create a Matplotlib-based space visualization component.""" update_counter.get() @@ -79,7 +91,7 @@ def SpaceMatplotlib( ax = fig.add_subplot() draw_space( - space, agent_portrayal, propertylayer_portrayal=propertylayer_portrayal, ax=ax + space, agent_portrayal, propertylayer_portrayal=propertylayer_portrayal, ax=ax, post_process=post_process, **space_drawing_kwargs ) solara.FigureMatplotlib( @@ -139,6 +151,8 @@ def draw_space( agent_portrayal: Callable, propertylayer_portrayal: dict | None = None, ax: Axes | None = None, + space_drawing_kwargs: dict | None = None, + post_process: Callable| None = None, ): """Draw a Matplotlib-based visualization of the space. @@ -147,6 +161,8 @@ def draw_space( agent_portrayal: A callable that returns a dict specifying how to show the agent propertylayer_portrayal: a dict specifying how to show propertylayer(s) ax: the axes upon which to draw the plot + postprocess: a user-specified callable to do post-processing called with the Axes instance. This callable + can be used for any further fine-tuning of the plot (e.g., changing ticks, etc.) """ if ax is None: @@ -155,11 +171,11 @@ def draw_space( # https://stackoverflow.com/questions/67524641/convert-multiple-isinstance-checks-to-structural-pattern-matching match space: case mesa.space._Grid() | OrthogonalMooreGrid() | OrthogonalVonNeumannGrid(): - draw_orthogonal_grid(space, agent_portrayal, ax=ax) + draw_orthogonal_grid(space, agent_portrayal, ax=ax, **space_drawing_kwargs) case HexSingleGrid() | HexMultiGrid() | mesa.experimental.cell_space.HexGrid(): - draw_hex_grid(space, agent_portrayal, ax=ax) + draw_hex_grid(space, agent_portrayal, ax=ax, **space_drawing_kwargs) case mesa.space.NetworkGrid() | mesa.experimental.cell_space.Network(): - draw_network(space, agent_portrayal, ax=ax) + draw_network(space, agent_portrayal, ax=ax, **space_drawing_kwargs) case mesa.space.ContinuousSpace(): draw_continuous_space(space, agent_portrayal, ax=ax) case VoronoiGrid(): @@ -168,6 +184,9 @@ def draw_space( if propertylayer_portrayal: draw_property_layers(space, propertylayer_portrayal, ax=ax) + if post_process is not None: + post_process(ax=ax) + def draw_property_layers( space, propertylayer_portrayal: dict[str, dict[str, Any]], ax: Axes From 292510694859281e9f66369946c6ee201376dddd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:59:33 +0000 Subject: [PATCH 58/74] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/visualization/components/matplotlib.py | 31 +++++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 188afd5a291..cf6a9dec9c6 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -40,10 +40,12 @@ Network = NetworkGrid | mesa.experimental.cell_space.Network -def make_space_matplotlib(agent_portrayal: Callable | None = None, - propertylayer_portrayal: dict | None = None, - post_process: Callable | None = None, - **space_drawing_kwargs): +def make_space_matplotlib( + agent_portrayal: Callable | None = None, + propertylayer_portrayal: dict | None = None, + post_process: Callable | None = None, + **space_drawing_kwargs, +): """Create a Matplotlib-based space visualization component. Args: @@ -66,7 +68,13 @@ def agent_portrayal(a): return {} def MakeSpaceMatplotlib(model): - return SpaceMatplotlib(model, agent_portrayal, propertylayer_portrayal, post_process=post_process, **space_drawing_kwargs) + return SpaceMatplotlib( + model, + agent_portrayal, + propertylayer_portrayal, + post_process=post_process, + **space_drawing_kwargs, + ) return MakeSpaceMatplotlib @@ -77,8 +85,8 @@ def SpaceMatplotlib( agent_portrayal, propertylayer_portrayal, dependencies: list[any] | None = None, - post_process: Callable| None = None, - **space_drawing_kwargs + post_process: Callable | None = None, + **space_drawing_kwargs, ): """Create a Matplotlib-based space visualization component.""" update_counter.get() @@ -91,7 +99,12 @@ def SpaceMatplotlib( ax = fig.add_subplot() draw_space( - space, agent_portrayal, propertylayer_portrayal=propertylayer_portrayal, ax=ax, post_process=post_process, **space_drawing_kwargs + space, + agent_portrayal, + propertylayer_portrayal=propertylayer_portrayal, + ax=ax, + post_process=post_process, + **space_drawing_kwargs, ) solara.FigureMatplotlib( @@ -152,7 +165,7 @@ def draw_space( propertylayer_portrayal: dict | None = None, ax: Axes | None = None, space_drawing_kwargs: dict | None = None, - post_process: Callable| None = None, + post_process: Callable | None = None, ): """Draw a Matplotlib-based visualization of the space. From 0ce0c7c4f74dea7a826ce02cb105b7edbc23f8ce Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Tue, 29 Oct 2024 18:05:08 +0100 Subject: [PATCH 59/74] Update test_solara_viz.py --- tests/test_solara_viz.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_solara_viz.py b/tests/test_solara_viz.py index a0d2b449399..f6b0a5ac64e 100644 --- a/tests/test_solara_viz.py +++ b/tests/test_solara_viz.py @@ -97,7 +97,7 @@ def test_call_space_drawer(mocker): # noqa: D103 mocker.patch.object(mesa.Model, "__init__", return_value=None) agent_portrayal = { - "Shape": "circle", + "marker": "circle", "color": "gray", } propertylayer_portrayal = None @@ -106,7 +106,7 @@ def test_call_space_drawer(mocker): # noqa: D103 solara.render(SolaraViz(model, components=[make_space_matplotlib(agent_portrayal)])) # should call default method with class instance and agent portrayal mock_space_matplotlib.assert_called_with( - model, agent_portrayal, propertylayer_portrayal + model, agent_portrayal, propertylayer_portrayal, post_process=None ) # specify no space should be drawn From 9282bfeba6cdded42043ed92d36e924c9beeb6c7 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Tue, 29 Oct 2024 18:14:30 +0100 Subject: [PATCH 60/74] ruff related fixes --- mesa/visualization/components/matplotlib.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index cf6a9dec9c6..3241e48fc4c 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -153,7 +153,8 @@ def collect_agent_data( ignored_fields = list(portray.keys()) msg = ", ".join(ignored_fields) warnings.warn( - f"the following fields are not used in agent portrayal and thus ignored: {msg}." + f"the following fields are not used in agent portrayal and thus ignored: {msg}.", + stacklevel=2 ) return {k: np.asarray(v) for k, v in arguments.items()} @@ -164,8 +165,8 @@ def draw_space( agent_portrayal: Callable, propertylayer_portrayal: dict | None = None, ax: Axes | None = None, - space_drawing_kwargs: dict | None = None, post_process: Callable | None = None, + **space_drawing_kwargs, ): """Draw a Matplotlib-based visualization of the space. @@ -174,6 +175,9 @@ def draw_space( agent_portrayal: A callable that returns a dict specifying how to show the agent propertylayer_portrayal: a dict specifying how to show propertylayer(s) ax: the axes upon which to draw the plot + post_process: a callable called with the Axes instance + space_drawing_kwargs: any additional keyword arguments to be passed on to the underlying function for drawing the space. + postprocess: a user-specified callable to do post-processing called with the Axes instance. This callable can be used for any further fine-tuning of the plot (e.g., changing ticks, etc.) From d5b8ec6ebfd0742d8c76c3018f480830ce7ab191 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 29 Oct 2024 17:14:40 +0000 Subject: [PATCH 61/74] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/visualization/components/matplotlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 3241e48fc4c..8b8db2f018d 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -154,7 +154,7 @@ def collect_agent_data( msg = ", ".join(ignored_fields) warnings.warn( f"the following fields are not used in agent portrayal and thus ignored: {msg}.", - stacklevel=2 + stacklevel=2, ) return {k: np.asarray(v) for k, v in arguments.items()} From ce2a8defb6748960cb8761235fe4df7bff145476 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Tue, 29 Oct 2024 19:39:21 +0100 Subject: [PATCH 62/74] update example --- mesa/examples/advanced/wolf_sheep/app.py | 27 ++++++++++--------- .../basic/conways_game_of_life/app.py | 9 +++++-- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/mesa/examples/advanced/wolf_sheep/app.py b/mesa/examples/advanced/wolf_sheep/app.py index bea98fd3630..f4c53171337 100644 --- a/mesa/examples/advanced/wolf_sheep/app.py +++ b/mesa/examples/advanced/wolf_sheep/app.py @@ -7,34 +7,28 @@ make_space_matplotlib, ) -WOLF_COLOR = "#000000" -SHEEP_COLOR = "#648FFF" - def wolf_sheep_portrayal(agent): if agent is None: return portrayal = { - "s": 25, + "size": 25, } if isinstance(agent, Wolf): - portrayal["c"] = "tab:orange" + portrayal["color"] = "tab:orange" portrayal["marker"] = "o" elif isinstance(agent, Sheep): - portrayal["c"] = "tab:blue" - portrayal["zorder"] = 2 + portrayal["color"] = "tab:blue" portrayal["marker"] = "o" elif isinstance(agent, GrassPatch): if agent.fully_grown: - portrayal["c"] = "tab:green" + portrayal["color"] = "tab:green" else: - portrayal["c"] = "tab:brown" + portrayal["color"] = "tab:brown" portrayal["marker"] = "s" - # portrayal["Filled"] = "true" - portrayal["zorder"] = 1 - portrayal["s"] = 75 + portrayal["size"] = 75 return portrayal @@ -63,7 +57,13 @@ def wolf_sheep_portrayal(agent): } -space_component = make_space_matplotlib(wolf_sheep_portrayal) +def post_process(ax): + ax.set_aspect("equal") + ax.set_xticks([]) + ax.set_yticks([]) + + +space_component = make_space_matplotlib(wolf_sheep_portrayal, draw_grid=False, post_process=post_process) lineplot_component = make_plot_measure( {"Wolves": "tab:orange", "Sheep": "tab:blue", "Grass": "tab:green"} ) @@ -71,6 +71,7 @@ def wolf_sheep_portrayal(agent): model = WolfSheep(grass=True) + page = SolaraViz( model, components=[space_component, lineplot_component], diff --git a/mesa/examples/basic/conways_game_of_life/app.py b/mesa/examples/basic/conways_game_of_life/app.py index 2c9dace8635..66bf5dfe5b2 100644 --- a/mesa/examples/basic/conways_game_of_life/app.py +++ b/mesa/examples/basic/conways_game_of_life/app.py @@ -1,12 +1,17 @@ +import sys +import os.path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../../'))) + + from mesa.examples.basic.conways_game_of_life.model import ConwaysGameOfLife from mesa.visualization import ( SolaraViz, make_space_matplotlib, ) - def agent_portrayal(agent): - return {"color": "white" if agent.state == 0 else "black"} + return {"c": "white" if agent.state == 0 else "black", + "marker":'s'} model_params = { From 9efaa99276678ef26a6d3db71b20f8a4f898b26c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 29 Oct 2024 18:39:34 +0000 Subject: [PATCH 63/74] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/examples/advanced/wolf_sheep/app.py | 5 +++-- mesa/examples/basic/conways_game_of_life/app.py | 11 +++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/mesa/examples/advanced/wolf_sheep/app.py b/mesa/examples/advanced/wolf_sheep/app.py index f4c53171337..d4fd8189b48 100644 --- a/mesa/examples/advanced/wolf_sheep/app.py +++ b/mesa/examples/advanced/wolf_sheep/app.py @@ -63,7 +63,9 @@ def post_process(ax): ax.set_yticks([]) -space_component = make_space_matplotlib(wolf_sheep_portrayal, draw_grid=False, post_process=post_process) +space_component = make_space_matplotlib( + wolf_sheep_portrayal, draw_grid=False, post_process=post_process +) lineplot_component = make_plot_measure( {"Wolves": "tab:orange", "Sheep": "tab:blue", "Grass": "tab:green"} ) @@ -71,7 +73,6 @@ def post_process(ax): model = WolfSheep(grass=True) - page = SolaraViz( model, components=[space_component, lineplot_component], diff --git a/mesa/examples/basic/conways_game_of_life/app.py b/mesa/examples/basic/conways_game_of_life/app.py index 66bf5dfe5b2..bafeb1a937b 100644 --- a/mesa/examples/basic/conways_game_of_life/app.py +++ b/mesa/examples/basic/conways_game_of_life/app.py @@ -1,6 +1,9 @@ -import sys import os.path -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../../'))) +import sys + +sys.path.insert( + 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../")) +) from mesa.examples.basic.conways_game_of_life.model import ConwaysGameOfLife @@ -9,9 +12,9 @@ make_space_matplotlib, ) + def agent_portrayal(agent): - return {"c": "white" if agent.state == 0 else "black", - "marker":'s'} + return {"c": "white" if agent.state == 0 else "black", "marker": "s"} model_params = { From 3a753bbae3bc6ad869b036cdaece4e4b2c273039 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Tue, 29 Oct 2024 19:54:07 +0100 Subject: [PATCH 64/74] ensure visualization.components is included in the docs --- docs/apis/visualization.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/apis/visualization.md b/docs/apis/visualization.md index 10349aaf240..7815ec568b8 100644 --- a/docs/apis/visualization.md +++ b/docs/apis/visualization.md @@ -19,3 +19,22 @@ For a detailed tutorial, please refer to our [Visualization Tutorial](../tutoria :undoc-members: :show-inheritance: ``` + + +## Matplotlib-based components + +```{eval-rst} +.. automodule:: mesa.visualization.components.matplotlib + :members: + :undoc-members: + :show-inheritance: +``` + +## Altair-based components + +```{eval-rst} +.. automodule:: mesa.visualization.components.altair + :members: + :undoc-members: + :show-inheritance: +``` \ No newline at end of file From 27e5e2636242745f6a0bef9e1c889f3cb1e6b9b9 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Tue, 29 Oct 2024 20:09:18 +0100 Subject: [PATCH 65/74] add zorder in and update example --- mesa/examples/advanced/wolf_sheep/app.py | 14 +++++++++++--- mesa/visualization/components/matplotlib.py | 19 ++++++++++++++----- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/mesa/examples/advanced/wolf_sheep/app.py b/mesa/examples/advanced/wolf_sheep/app.py index d4fd8189b48..6f30489c145 100644 --- a/mesa/examples/advanced/wolf_sheep/app.py +++ b/mesa/examples/advanced/wolf_sheep/app.py @@ -1,3 +1,9 @@ +import sys +import os.path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../../'))) + + + from mesa.examples.advanced.wolf_sheep.agents import GrassPatch, Sheep, Wolf from mesa.examples.advanced.wolf_sheep.model import WolfSheep from mesa.visualization import ( @@ -17,11 +23,13 @@ def wolf_sheep_portrayal(agent): } if isinstance(agent, Wolf): - portrayal["color"] = "tab:orange" + portrayal["color"] = "tab:red" portrayal["marker"] = "o" + portrayal["zorder"] = 2 elif isinstance(agent, Sheep): - portrayal["color"] = "tab:blue" + portrayal["color"] = "tab:cyan" portrayal["marker"] = "o" + portrayal["zorder"] = 2 elif isinstance(agent, GrassPatch): if agent.fully_grown: portrayal["color"] = "tab:green" @@ -67,7 +75,7 @@ def post_process(ax): wolf_sheep_portrayal, draw_grid=False, post_process=post_process ) lineplot_component = make_plot_measure( - {"Wolves": "tab:orange", "Sheep": "tab:blue", "Grass": "tab:green"} + {"Wolves": "tab:orange", "Sheep": "tab:cyan", "Grass": "tab:green"} ) model = WolfSheep(grass=True) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 8b8db2f018d..23e42d95060 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -118,6 +118,7 @@ def collect_agent_data( color="tab:blue", size=25, marker="o", + zorder:int = 1 ): """Collect the plotting data for all agents in the space. @@ -128,6 +129,7 @@ def collect_agent_data( color: default color marker: default marker size: default size + zorder: default zorder Notes: agent portray dict is limited to size (size of marker), color (color of marker, and marker (marker style) @@ -137,7 +139,8 @@ def collect_agent_data( .. _Matplotlib: https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.scatter.html """ - arguments = {"loc": [], "s": [], "c": [], "marker": []} + arguments = {"s":[],"c":[], "marker":[], "zorder":[], 'loc':[]} + for agent in space.agents: portray = agent_portrayal(agent) loc = agent.pos @@ -148,6 +151,7 @@ def collect_agent_data( arguments["s"].append(portray.pop("size", size)) arguments["c"].append(portray.pop("color", color)) arguments["marker"].append(portray.pop("marker", marker)) + arguments["zorder"].append(portray.pop("zorder", zorder)) if len(portray) > 0: ignored_fields = list(portray.keys()) @@ -574,12 +578,17 @@ def _scatter(ax: Axes, arguments): x = loc[:, 0] y = loc[:, 1] marker = arguments.pop("marker") + zorder = arguments.pop("zorder") + for mark in np.unique(marker): - mask = marker == mark - ax.scatter( - x[mask], y[mask], marker=mark, **{k: v[mask] for k, v in arguments.items()} - ) + mark_mask = marker == mark + for z_order in np.unique(zorder): + zorder_mask = z_order == zorder + logical = mark_mask & zorder_mask + ax.scatter( + x[logical], y[logical], marker=mark, zorder=z_order, **{k: v[logical] for k, v in arguments.items()} + ) def make_plot_measure(measure: str | dict[str, str] | list[str] | tuple[str]): From f36fbbcd49e2ee6e589be78963283a4db548210d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 29 Oct 2024 19:09:28 +0000 Subject: [PATCH 66/74] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/examples/advanced/wolf_sheep/app.py | 6 ++++-- mesa/visualization/components/matplotlib.py | 11 +++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/mesa/examples/advanced/wolf_sheep/app.py b/mesa/examples/advanced/wolf_sheep/app.py index 6f30489c145..1c60fe7b3b0 100644 --- a/mesa/examples/advanced/wolf_sheep/app.py +++ b/mesa/examples/advanced/wolf_sheep/app.py @@ -1,7 +1,9 @@ -import sys import os.path -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../../'))) +import sys +sys.path.insert( + 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../")) +) from mesa.examples.advanced.wolf_sheep.agents import GrassPatch, Sheep, Wolf diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 23e42d95060..d30f302a5a8 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -118,7 +118,7 @@ def collect_agent_data( color="tab:blue", size=25, marker="o", - zorder:int = 1 + zorder: int = 1, ): """Collect the plotting data for all agents in the space. @@ -139,7 +139,7 @@ def collect_agent_data( .. _Matplotlib: https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.scatter.html """ - arguments = {"s":[],"c":[], "marker":[], "zorder":[], 'loc':[]} + arguments = {"s": [], "c": [], "marker": [], "zorder": [], "loc": []} for agent in space.agents: portray = agent_portrayal(agent) @@ -580,14 +580,17 @@ def _scatter(ax: Axes, arguments): marker = arguments.pop("marker") zorder = arguments.pop("zorder") - for mark in np.unique(marker): mark_mask = marker == mark for z_order in np.unique(zorder): zorder_mask = z_order == zorder logical = mark_mask & zorder_mask ax.scatter( - x[logical], y[logical], marker=mark, zorder=z_order, **{k: v[logical] for k, v in arguments.items()} + x[logical], + y[logical], + marker=mark, + zorder=z_order, + **{k: v[logical] for k, v in arguments.items()}, ) From 9c2e8cdbf81a9859e53551282e9672a121523f67 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Tue, 29 Oct 2024 20:11:44 +0100 Subject: [PATCH 67/74] docstring fixes --- mesa/visualization/components/matplotlib.py | 30 ++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index d30f302a5a8..318a3abe898 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -56,7 +56,7 @@ def make_space_matplotlib( the functions for drawing the various spaces for further details. ``agent_portrayal`` is called with an agent and should return a dict. Valid fields in this dict are "color", - "size", and "marker". Other field are ignored and will result in a user warning. + "size", "marker", and "zorder". Other field are ignored and will result in a user warning. Returns: @@ -180,10 +180,12 @@ def draw_space( propertylayer_portrayal: a dict specifying how to show propertylayer(s) ax: the axes upon which to draw the plot post_process: a callable called with the Axes instance - space_drawing_kwargs: any additional keyword arguments to be passed on to the underlying function for drawing the space. - postprocess: a user-specified callable to do post-processing called with the Axes instance. This callable can be used for any further fine-tuning of the plot (e.g., changing ticks, etc.) + space_drawing_kwargs: any additional keyword arguments to be passed on to the underlying function for drawing the space. + + ``agent_portrayal`` is called with an agent and should return a dict. Valid fields in this dict are "color", + "size", "marker", and "zorder". Other field are ignored and will result in a user warning. """ if ax is None: @@ -307,8 +309,8 @@ def draw_orthogonal_grid( ax: a Matplotlib Axes instance. If none is provided a new figure and ax will be created using plt.subplots draw_grid: whether to draw the grid - Returns: - A Figure and Axes instance + ``agent_portrayal`` is called with an agent and should return a dict. Valid fields in this dict are "color", + "size", "marker", and "zorder". Other field are ignored and will result in a user warning. """ if ax is None: @@ -347,8 +349,8 @@ def draw_hex_grid( ax: a Matplotlib Axes instance. If none is provided a new figure and ax will be created using plt.subplots draw_grid: whether to draw the grid - Returns: - A Figure and Axes instance + ``agent_portrayal`` is called with an agent and should return a dict. Valid fields in this dict are "color", + "size", "marker", and "zorder". Other field are ignored and will result in a user warning. """ if ax is None: @@ -434,10 +436,8 @@ def draw_network( layout_alg: a networkx layout algorithm or other callable with the same behavior layout_kwargs: a dictionary of keyword arguments for the layout algorithm - Notes: - this uses networkx.draw under the hood so agent portrayal fields should match those used there - i.e., node_size and node_color. - + ``agent_portrayal`` is called with an agent and should return a dict. Valid fields in this dict are "color", + "size", "marker", and "zorder". Other field are ignored and will result in a user warning. """ if ax is None: @@ -492,8 +492,8 @@ def draw_continuous_space( agent_portrayal: a callable that is called with the agent and returns a dict ax: a Matplotlib Axes instance. If none is provided a new figure and ax will be created using plt.subplots - Returns: - A Figure and Axes instance + ``agent_portrayal`` is called with an agent and should return a dict. Valid fields in this dict are "color", + "size", "marker", and "zorder". Other field are ignored and will result in a user warning. """ if ax is None: @@ -533,8 +533,8 @@ def draw_voroinoi_grid( agent_portrayal: a callable that is called with the agent and returns a dict ax: a Matplotlib Axes instance. If none is provided a new figure and ax will be created using plt.subplots - Returns: - A Figure and Axes instance + ``agent_portrayal`` is called with an agent and should return a dict. Valid fields in this dict are "color", + "size", "marker", and "zorder". Other field are ignored and will result in a user warning. """ if ax is None: From cdbea6e2ffedba7879fea0cd9b25a7610c06ed69 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Tue, 29 Oct 2024 20:24:25 +0100 Subject: [PATCH 68/74] cleanup --- mesa/examples/advanced/wolf_sheep/app.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/mesa/examples/advanced/wolf_sheep/app.py b/mesa/examples/advanced/wolf_sheep/app.py index 1c60fe7b3b0..fc37949487f 100644 --- a/mesa/examples/advanced/wolf_sheep/app.py +++ b/mesa/examples/advanced/wolf_sheep/app.py @@ -1,11 +1,3 @@ -import os.path -import sys - -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../")) -) - - from mesa.examples.advanced.wolf_sheep.agents import GrassPatch, Sheep, Wolf from mesa.examples.advanced.wolf_sheep.model import WolfSheep from mesa.visualization import ( From f371ef0be8fb7b94917f85bde22f9868107b821b Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Tue, 29 Oct 2024 20:29:25 +0100 Subject: [PATCH 69/74] rename to make_space_component, return ax and update docs --- docs/migration_guide.md | 4 +- docs/overview.md | 6 +- .../advanced/epstein_civil_violence/app.py | 4 +- mesa/examples/advanced/pd_grid/app.py | 4 +- mesa/examples/advanced/sugarscape_g1mt/app.py | 8 -- mesa/examples/advanced/wolf_sheep/app.py | 4 +- mesa/examples/basic/boid_flockers/app.py | 4 +- .../basic/boltzmann_wealth_model/app.py | 4 +- .../basic/conways_game_of_life/app.py | 4 +- mesa/examples/basic/schelling/app.py | 4 +- mesa/examples/basic/virus_on_network/app.py | 4 +- mesa/visualization/__init__.py | 4 +- mesa/visualization/components/matplotlib.py | 119 +++++++++++------- tests/test_solara_viz.py | 6 +- 14 files changed, 100 insertions(+), 79 deletions(-) diff --git a/docs/migration_guide.md b/docs/migration_guide.md index b4f54c3dfd7..016053e84fb 100644 --- a/docs/migration_guide.md +++ b/docs/migration_guide.md @@ -268,9 +268,9 @@ from mesa.experimental import SolaraViz SolaraViz(model_cls, model_params, agent_portrayal=agent_portrayal) # new -from mesa.visualization import SolaraViz, make_space_matplotlib +from mesa.visualization import SolaraViz, make_space_component -SolaraViz(model, components=[make_space_matplotlib(agent_portrayal)]) +SolaraViz(model, components=[make_space_component(agent_portrayal)]) ``` #### Plotting "measures" diff --git a/docs/overview.md b/docs/overview.md index 17ef67302fe..7d0c750ed84 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -168,11 +168,13 @@ The results are returned as a list of dictionaries, which can be easily converte Mesa now uses a new browser-based visualization system called SolaraViz. This allows for interactive, customizable visualizations of your models. Here's a basic example of how to set up a visualization: ```python -from mesa.visualization import SolaraViz, make_space_matplotlib, make_plot_measure +from mesa.visualization import SolaraViz, make_space_component, make_plot_measure + def agent_portrayal(agent): return {"color": "blue", "size": 50} + model_params = { "N": { "type": "SliderInt", @@ -187,7 +189,7 @@ model_params = { page = SolaraViz( MyModel, [ - make_space_matplotlib(agent_portrayal), + make_space_component(agent_portrayal), make_plot_measure("mean_age") ], model_params=model_params diff --git a/mesa/examples/advanced/epstein_civil_violence/app.py b/mesa/examples/advanced/epstein_civil_violence/app.py index 862ca6220d8..538ef186f57 100644 --- a/mesa/examples/advanced/epstein_civil_violence/app.py +++ b/mesa/examples/advanced/epstein_civil_violence/app.py @@ -8,7 +8,7 @@ Slider, SolaraViz, make_plot_measure, - make_space_matplotlib, + make_space_component, ) COP_COLOR = "#000000" @@ -47,7 +47,7 @@ def citizen_cop_portrayal(agent): "max_jail_term": Slider("Max Jail Term", 30, 0, 50, 1), } -space_component = make_space_matplotlib(citizen_cop_portrayal) +space_component = make_space_component(citizen_cop_portrayal) chart_component = make_plot_measure([state.name.lower() for state in CitizenState]) epstein_model = EpsteinCivilViolence() diff --git a/mesa/examples/advanced/pd_grid/app.py b/mesa/examples/advanced/pd_grid/app.py index c8ceec9fe16..6edf8140536 100644 --- a/mesa/examples/advanced/pd_grid/app.py +++ b/mesa/examples/advanced/pd_grid/app.py @@ -3,7 +3,7 @@ """ from mesa.examples.advanced.pd_grid.model import PdGrid -from mesa.visualization import SolaraViz, make_plot_measure, make_space_matplotlib +from mesa.visualization import SolaraViz, make_plot_measure, make_space_component from mesa.visualization.UserParam import Slider @@ -32,7 +32,7 @@ def pd_agent_portrayal(agent): # Create grid visualization component using Altair -grid_viz = make_space_matplotlib(agent_portrayal=pd_agent_portrayal) +grid_viz = make_space_component(agent_portrayal=pd_agent_portrayal) # Create plot for tracking cooperating agents over time plot_component = make_plot_measure("Cooperating_Agents") diff --git a/mesa/examples/advanced/sugarscape_g1mt/app.py b/mesa/examples/advanced/sugarscape_g1mt/app.py index 7c8cc2cfead..752998891bd 100644 --- a/mesa/examples/advanced/sugarscape_g1mt/app.py +++ b/mesa/examples/advanced/sugarscape_g1mt/app.py @@ -1,11 +1,3 @@ -import os.path -import sys - -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../")) -) - - import numpy as np import solara from matplotlib.figure import Figure diff --git a/mesa/examples/advanced/wolf_sheep/app.py b/mesa/examples/advanced/wolf_sheep/app.py index fc37949487f..a8c0a1e9c49 100644 --- a/mesa/examples/advanced/wolf_sheep/app.py +++ b/mesa/examples/advanced/wolf_sheep/app.py @@ -4,7 +4,7 @@ Slider, SolaraViz, make_plot_measure, - make_space_matplotlib, + make_space_component, ) @@ -65,7 +65,7 @@ def post_process(ax): ax.set_yticks([]) -space_component = make_space_matplotlib( +space_component = make_space_component( wolf_sheep_portrayal, draw_grid=False, post_process=post_process ) lineplot_component = make_plot_measure( diff --git a/mesa/examples/basic/boid_flockers/app.py b/mesa/examples/basic/boid_flockers/app.py index 482d582b8ba..bcecb0a3ebd 100644 --- a/mesa/examples/basic/boid_flockers/app.py +++ b/mesa/examples/basic/boid_flockers/app.py @@ -1,5 +1,5 @@ from mesa.examples.basic.boid_flockers.model import BoidFlockers -from mesa.visualization import Slider, SolaraViz, make_space_matplotlib +from mesa.visualization import Slider, SolaraViz, make_space_component def boid_draw(agent): @@ -51,7 +51,7 @@ def boid_draw(agent): page = SolaraViz( model, - [make_space_matplotlib(agent_portrayal=boid_draw)], + [make_space_component(agent_portrayal=boid_draw)], model_params=model_params, name="Boid Flocking Model", ) diff --git a/mesa/examples/basic/boltzmann_wealth_model/app.py b/mesa/examples/basic/boltzmann_wealth_model/app.py index 7e3f41e64de..2ab6d06bf73 100644 --- a/mesa/examples/basic/boltzmann_wealth_model/app.py +++ b/mesa/examples/basic/boltzmann_wealth_model/app.py @@ -2,7 +2,7 @@ from mesa.visualization import ( SolaraViz, make_plot_measure, - make_space_matplotlib, + make_space_component, ) @@ -36,7 +36,7 @@ def agent_portrayal(agent): # 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_matplotlib(agent_portrayal) +SpaceGraph = make_space_component(agent_portrayal) GiniPlot = make_plot_measure("Gini") # Create the SolaraViz page. This will automatically create a server and display the diff --git a/mesa/examples/basic/conways_game_of_life/app.py b/mesa/examples/basic/conways_game_of_life/app.py index bafeb1a937b..aee1733e523 100644 --- a/mesa/examples/basic/conways_game_of_life/app.py +++ b/mesa/examples/basic/conways_game_of_life/app.py @@ -9,7 +9,7 @@ from mesa.examples.basic.conways_game_of_life.model import ConwaysGameOfLife from mesa.visualization import ( SolaraViz, - make_space_matplotlib, + make_space_component, ) @@ -30,7 +30,7 @@ def agent_portrayal(agent): # 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_matplotlib(agent_portrayal) +SpaceGraph = make_space_component(agent_portrayal) # Create the SolaraViz page. This will automatically create a server and display the diff --git a/mesa/examples/basic/schelling/app.py b/mesa/examples/basic/schelling/app.py index 72ae6ddc1ec..53fab7ba0f0 100644 --- a/mesa/examples/basic/schelling/app.py +++ b/mesa/examples/basic/schelling/app.py @@ -5,7 +5,7 @@ Slider, SolaraViz, make_plot_measure, - make_space_matplotlib, + make_space_component, ) @@ -33,7 +33,7 @@ def agent_portrayal(agent): page = SolaraViz( model1, components=[ - make_space_matplotlib(agent_portrayal), + make_space_component(agent_portrayal), make_plot_measure("happy"), get_happy_agents, ], diff --git a/mesa/examples/basic/virus_on_network/app.py b/mesa/examples/basic/virus_on_network/app.py index 0183d256790..7cf54f308d5 100644 --- a/mesa/examples/basic/virus_on_network/app.py +++ b/mesa/examples/basic/virus_on_network/app.py @@ -9,7 +9,7 @@ VirusOnNetwork, number_infected, ) -from mesa.visualization import Slider, SolaraViz, make_space_matplotlib +from mesa.visualization import Slider, SolaraViz, make_space_component def agent_portrayal(graph): @@ -119,7 +119,7 @@ def make_plot(model): ), } -SpacePlot = make_space_matplotlib(agent_portrayal) +SpacePlot = make_space_component(agent_portrayal) model1 = VirusOnNetwork() diff --git a/mesa/visualization/__init__.py b/mesa/visualization/__init__.py index d6e50c37e36..0e1875c751c 100644 --- a/mesa/visualization/__init__.py +++ b/mesa/visualization/__init__.py @@ -1,7 +1,7 @@ """Solara based visualization for Mesa models.""" from .components.altair import make_space_altair -from .components.matplotlib import make_plot_measure, make_space_matplotlib +from .components.matplotlib import make_plot_measure, make_space_component from .solara_viz import JupyterViz, SolaraViz from .UserParam import Slider @@ -10,6 +10,6 @@ "SolaraViz", "Slider", "make_space_altair", - "make_space_matplotlib", + "make_space_component", "make_plot_measure", ] diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 318a3abe898..f860660955b 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -40,11 +40,11 @@ Network = NetworkGrid | mesa.experimental.cell_space.Network -def make_space_matplotlib( - agent_portrayal: Callable | None = None, - propertylayer_portrayal: dict | None = None, - post_process: Callable | None = None, - **space_drawing_kwargs, +def make_space_component( + agent_portrayal: Callable | None = None, + propertylayer_portrayal: dict | None = None, + post_process: Callable | None = None, + **space_drawing_kwargs, ): """Create a Matplotlib-based space visualization component. @@ -63,7 +63,6 @@ def make_space_matplotlib( function: A function that creates a SpaceMatplotlib component """ if agent_portrayal is None: - def agent_portrayal(a): return {} @@ -81,12 +80,12 @@ def MakeSpaceMatplotlib(model): @solara.component def SpaceMatplotlib( - model, - agent_portrayal, - propertylayer_portrayal, - dependencies: list[any] | None = None, - post_process: Callable | None = None, - **space_drawing_kwargs, + model, + agent_portrayal, + propertylayer_portrayal, + dependencies: list[any] | None = None, + post_process: Callable | None = None, + **space_drawing_kwargs, ): """Create a Matplotlib-based space visualization component.""" update_counter.get() @@ -113,26 +112,25 @@ def SpaceMatplotlib( def collect_agent_data( - space: OrthogonalGrid | HexGrid | Network | ContinuousSpace | VoronoiGrid, - agent_portrayal: Callable, - color="tab:blue", - size=25, - marker="o", - zorder: int = 1, + space: OrthogonalGrid | HexGrid | Network | ContinuousSpace | VoronoiGrid, + agent_portrayal: Callable, + color="tab:blue", + size=25, + marker="o", + zorder: int = 1, ): """Collect the plotting data for all agents in the space. Args: space: The space containing the Agents. agent_portrayal: A callable that is called with the agent and returns a dict - loc: a boolean indicating whether to gather agent x, y data or not color: default color - marker: default marker size: default size + marker: default marker zorder: default zorder Notes: - agent portray dict is limited to size (size of marker), color (color of marker, and marker (marker style) + agent portray dict is limited to size (size of marker), color (color of marker, zorder, and marker (marker style) see `Matplotlib`_. @@ -165,12 +163,12 @@ def collect_agent_data( def draw_space( - space, - agent_portrayal: Callable, - propertylayer_portrayal: dict | None = None, - ax: Axes | None = None, - post_process: Callable | None = None, - **space_drawing_kwargs, + space, + agent_portrayal: Callable, + propertylayer_portrayal: dict | None = None, + ax: Axes | None = None, + post_process: Callable | None = None, + **space_drawing_kwargs, ): """Draw a Matplotlib-based visualization of the space. @@ -184,6 +182,9 @@ def draw_space( can be used for any further fine-tuning of the plot (e.g., changing ticks, etc.) space_drawing_kwargs: any additional keyword arguments to be passed on to the underlying function for drawing the space. + Returns: + Returns the Axes object with the plot drawn onto it. + ``agent_portrayal`` is called with an agent and should return a dict. Valid fields in this dict are "color", "size", "marker", and "zorder". Other field are ignored and will result in a user warning. @@ -210,9 +211,11 @@ def draw_space( if post_process is not None: post_process(ax=ax) + return ax + def draw_property_layers( - space, propertylayer_portrayal: dict[str, dict[str, Any]], ax: Axes + space, propertylayer_portrayal: dict[str, dict[str, Any]], ax: Axes ): """Draw PropertyLayers on the given axes. @@ -296,10 +299,10 @@ def draw_property_layers( def draw_orthogonal_grid( - space: OrthogonalGrid, - agent_portrayal: Callable, - ax: Axes | None = None, - draw_grid: bool = True, + space: OrthogonalGrid, + agent_portrayal: Callable, + ax: Axes | None = None, + draw_grid: bool = True, ): """Visualize a orthogonal grid. @@ -309,6 +312,9 @@ def draw_orthogonal_grid( ax: a Matplotlib Axes instance. If none is provided a new figure and ax will be created using plt.subplots draw_grid: whether to draw the grid + Returns: + Returns the Axes object with the plot drawn onto it. + ``agent_portrayal`` is called with an agent and should return a dict. Valid fields in this dict are "color", "size", "marker", and "zorder". Other field are ignored and will result in a user warning. @@ -334,12 +340,14 @@ def draw_orthogonal_grid( for y in np.arange(-0.5, space.height - 0.5, 1): ax.axhline(y, color="gray", linestyle=":") + return ax + def draw_hex_grid( - space: HexGrid, - agent_portrayal: Callable, - ax: Axes | None = None, - draw_grid: bool = True, + space: HexGrid, + agent_portrayal: Callable, + ax: Axes | None = None, + draw_grid: bool = True, ): """Visualize a hex grid. @@ -349,6 +357,9 @@ def draw_hex_grid( ax: a Matplotlib Axes instance. If none is provided a new figure and ax will be created using plt.subplots draw_grid: whether to draw the grid + Returns: + Returns the Axes object with the plot drawn onto it. + ``agent_portrayal`` is called with an agent and should return a dict. Valid fields in this dict are "color", "size", "marker", and "zorder". Other field are ignored and will result in a user warning. @@ -384,8 +395,8 @@ def draw_hex_grid( ax.set_ylim(-offset, space.height * offset) def setup_hexmesh( - width, - height, + width, + height, ): """Helper function for creating the hexmaesh.""" # fixme: this should be done once, rather than in each update @@ -416,15 +427,16 @@ def setup_hexmesh( space.height, ) ) + return ax def draw_network( - space: Network, - agent_portrayal: Callable, - ax: Axes | None = None, - draw_grid: bool = True, - layout_alg=nx.spring_layout, - layout_kwargs=None, + space: Network, + agent_portrayal: Callable, + ax: Axes | None = None, + draw_grid: bool = True, + layout_alg=nx.spring_layout, + layout_kwargs=None, ): """Visualize a network space. @@ -436,6 +448,9 @@ def draw_network( layout_alg: a networkx layout algorithm or other callable with the same behavior layout_kwargs: a dictionary of keyword arguments for the layout algorithm + Returns: + Returns the Axes object with the plot drawn onto it. + ``agent_portrayal`` is called with an agent and should return a dict. Valid fields in this dict are "color", "size", "marker", and "zorder". Other field are ignored and will result in a user warning. @@ -481,9 +496,11 @@ def draw_network( ) edge_collection.set_zorder(0) + return ax + def draw_continuous_space( - space: ContinuousSpace, agent_portrayal: Callable, ax: Axes | None = None + space: ContinuousSpace, agent_portrayal: Callable, ax: Axes | None = None ): """Visualize a continuous space. @@ -492,6 +509,9 @@ def draw_continuous_space( agent_portrayal: a callable that is called with the agent and returns a dict ax: a Matplotlib Axes instance. If none is provided a new figure and ax will be created using plt.subplots + Returns: + Returns the Axes object with the plot drawn onto it. + ``agent_portrayal`` is called with an agent and should return a dict. Valid fields in this dict are "color", "size", "marker", and "zorder". Other field are ignored and will result in a user warning. @@ -522,9 +542,11 @@ def draw_continuous_space( ax.set_xlim(space.x_min - x_padding, space.x_max + x_padding) ax.set_ylim(space.y_min - y_padding, space.y_max + y_padding) + return ax + def draw_voroinoi_grid( - space: VoronoiGrid, agent_portrayal: Callable, ax: Axes | None = None + space: VoronoiGrid, agent_portrayal: Callable, ax: Axes | None = None ): """Visualize a voronoi grid. @@ -533,6 +555,9 @@ def draw_voroinoi_grid( agent_portrayal: a callable that is called with the agent and returns a dict ax: a Matplotlib Axes instance. If none is provided a new figure and ax will be created using plt.subplots + Returns: + Returns the Axes object with the plot drawn onto it. + ``agent_portrayal`` is called with an agent and should return a dict. Valid fields in this dict are "color", "size", "marker", and "zorder". Other field are ignored and will result in a user warning. @@ -570,6 +595,8 @@ def draw_voroinoi_grid( ) # Plot filled polygon ax.plot(*zip(*polygon), color="black") # Plot polygon edges in black + return ax + def _scatter(ax: Axes, arguments): """Helper function for plotting the agents.""" diff --git a/tests/test_solara_viz.py b/tests/test_solara_viz.py index f6b0a5ac64e..af6badd0bc2 100644 --- a/tests/test_solara_viz.py +++ b/tests/test_solara_viz.py @@ -8,7 +8,7 @@ import mesa import mesa.visualization.components.altair import mesa.visualization.components.matplotlib -from mesa.visualization.components.matplotlib import make_space_matplotlib +from mesa.visualization.components.matplotlib import make_space_component from mesa.visualization.solara_viz import Slider, SolaraViz, UserInputs @@ -103,7 +103,7 @@ def test_call_space_drawer(mocker): # noqa: D103 propertylayer_portrayal = None # initialize with space drawer unspecified (use default) # component must be rendered for code to run - solara.render(SolaraViz(model, components=[make_space_matplotlib(agent_portrayal)])) + solara.render(SolaraViz(model, components=[make_space_component(agent_portrayal)])) # should call default method with class instance and agent portrayal mock_space_matplotlib.assert_called_with( model, agent_portrayal, propertylayer_portrayal, post_process=None @@ -132,7 +132,7 @@ def drawer(model): centroids_coordinates=[(0, 1), (0, 0), (1, 0)], ) solara.render( - SolaraViz(voronoi_model, components=[make_space_matplotlib(agent_portrayal)]) + SolaraViz(voronoi_model, components=[make_space_component(agent_portrayal)]) ) From eabcbabfec15889c30b686ab06ce27f28e6d1c05 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 29 Oct 2024 19:30:11 +0000 Subject: [PATCH 70/74] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/visualization/components/matplotlib.py | 83 +++++++++++---------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index f860660955b..3c9ad970ce7 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -41,10 +41,10 @@ def make_space_component( - agent_portrayal: Callable | None = None, - propertylayer_portrayal: dict | None = None, - post_process: Callable | None = None, - **space_drawing_kwargs, + agent_portrayal: Callable | None = None, + propertylayer_portrayal: dict | None = None, + post_process: Callable | None = None, + **space_drawing_kwargs, ): """Create a Matplotlib-based space visualization component. @@ -63,6 +63,7 @@ def make_space_component( function: A function that creates a SpaceMatplotlib component """ if agent_portrayal is None: + def agent_portrayal(a): return {} @@ -80,12 +81,12 @@ def MakeSpaceMatplotlib(model): @solara.component def SpaceMatplotlib( - model, - agent_portrayal, - propertylayer_portrayal, - dependencies: list[any] | None = None, - post_process: Callable | None = None, - **space_drawing_kwargs, + model, + agent_portrayal, + propertylayer_portrayal, + dependencies: list[any] | None = None, + post_process: Callable | None = None, + **space_drawing_kwargs, ): """Create a Matplotlib-based space visualization component.""" update_counter.get() @@ -112,12 +113,12 @@ def SpaceMatplotlib( def collect_agent_data( - space: OrthogonalGrid | HexGrid | Network | ContinuousSpace | VoronoiGrid, - agent_portrayal: Callable, - color="tab:blue", - size=25, - marker="o", - zorder: int = 1, + space: OrthogonalGrid | HexGrid | Network | ContinuousSpace | VoronoiGrid, + agent_portrayal: Callable, + color="tab:blue", + size=25, + marker="o", + zorder: int = 1, ): """Collect the plotting data for all agents in the space. @@ -163,12 +164,12 @@ def collect_agent_data( def draw_space( - space, - agent_portrayal: Callable, - propertylayer_portrayal: dict | None = None, - ax: Axes | None = None, - post_process: Callable | None = None, - **space_drawing_kwargs, + space, + agent_portrayal: Callable, + propertylayer_portrayal: dict | None = None, + ax: Axes | None = None, + post_process: Callable | None = None, + **space_drawing_kwargs, ): """Draw a Matplotlib-based visualization of the space. @@ -215,7 +216,7 @@ def draw_space( def draw_property_layers( - space, propertylayer_portrayal: dict[str, dict[str, Any]], ax: Axes + space, propertylayer_portrayal: dict[str, dict[str, Any]], ax: Axes ): """Draw PropertyLayers on the given axes. @@ -299,10 +300,10 @@ def draw_property_layers( def draw_orthogonal_grid( - space: OrthogonalGrid, - agent_portrayal: Callable, - ax: Axes | None = None, - draw_grid: bool = True, + space: OrthogonalGrid, + agent_portrayal: Callable, + ax: Axes | None = None, + draw_grid: bool = True, ): """Visualize a orthogonal grid. @@ -344,10 +345,10 @@ def draw_orthogonal_grid( def draw_hex_grid( - space: HexGrid, - agent_portrayal: Callable, - ax: Axes | None = None, - draw_grid: bool = True, + space: HexGrid, + agent_portrayal: Callable, + ax: Axes | None = None, + draw_grid: bool = True, ): """Visualize a hex grid. @@ -395,8 +396,8 @@ def draw_hex_grid( ax.set_ylim(-offset, space.height * offset) def setup_hexmesh( - width, - height, + width, + height, ): """Helper function for creating the hexmaesh.""" # fixme: this should be done once, rather than in each update @@ -431,12 +432,12 @@ def setup_hexmesh( def draw_network( - space: Network, - agent_portrayal: Callable, - ax: Axes | None = None, - draw_grid: bool = True, - layout_alg=nx.spring_layout, - layout_kwargs=None, + space: Network, + agent_portrayal: Callable, + ax: Axes | None = None, + draw_grid: bool = True, + layout_alg=nx.spring_layout, + layout_kwargs=None, ): """Visualize a network space. @@ -500,7 +501,7 @@ def draw_network( def draw_continuous_space( - space: ContinuousSpace, agent_portrayal: Callable, ax: Axes | None = None + space: ContinuousSpace, agent_portrayal: Callable, ax: Axes | None = None ): """Visualize a continuous space. @@ -546,7 +547,7 @@ def draw_continuous_space( def draw_voroinoi_grid( - space: VoronoiGrid, agent_portrayal: Callable, ax: Axes | None = None + space: VoronoiGrid, agent_portrayal: Callable, ax: Axes | None = None ): """Visualize a voronoi grid. From 40cc9e4a549cca4e709ea9a6b30132a29fec402e Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Tue, 29 Oct 2024 20:39:47 +0100 Subject: [PATCH 71/74] Update visualization_tutorial.ipynb --- docs/tutorials/visualization_tutorial.ipynb | 284 +++++++++++++++----- 1 file changed, 217 insertions(+), 67 deletions(-) diff --git a/docs/tutorials/visualization_tutorial.ipynb b/docs/tutorials/visualization_tutorial.ipynb index 7460ca30a19..aff166e9b82 100644 --- a/docs/tutorials/visualization_tutorial.ipynb +++ b/docs/tutorials/visualization_tutorial.ipynb @@ -3,9 +3,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": [ - "# Visualization Tutorial" - ] + "source": "# Visualization Tutorial" }, { "cell_type": "markdown", @@ -52,40 +50,50 @@ }, { "cell_type": "code", - "execution_count": 1, - "metadata": {}, + "metadata": { + "ExecuteTime": { + "end_time": "2024-10-29T19:38:46.075682Z", + "start_time": "2024-10-29T19:38:45.449918Z" + } + }, + "source": [ + "import mesa\n", + "print(f\"Mesa version: {mesa.__version__}\")\n", + "\n", + "from mesa.visualization import SolaraViz, make_plot_measure, make_space_component\n", + "\n", + "# Import the local MoneyModel.py\n", + "from MoneyModel import MoneyModel\n" + ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Mesa version: 3.0.0b1\n" + "Mesa version: 3.0.0b2\n" ] } ], - "source": [ - "import mesa\n", - "print(f\"Mesa version: {mesa.__version__}\")\n", - "\n", - "from mesa.visualization import SolaraViz, make_plot_measure, make_space_matplotlib\n", - "# Import the local MoneyModel.py\n", - "from MoneyModel import MoneyModel\n" - ] + "execution_count": 1 }, { "cell_type": "code", - "execution_count": null, "metadata": { - "tags": [] + "tags": [], + "ExecuteTime": { + "end_time": "2024-10-29T19:38:46.079286Z", + "start_time": "2024-10-29T19:38:46.076984Z" + } }, - "outputs": [], "source": [ "def agent_portrayal(agent):\n", " return {\n", " \"color\": \"tab:blue\",\n", " \"size\": 50,\n", " }" - ] + ], + "outputs": [], + "execution_count": 2 }, { "cell_type": "markdown", @@ -96,9 +104,12 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2024-10-29T19:38:46.081662Z", + "start_time": "2024-10-29T19:38:46.079838Z" + } + }, "source": [ "model_params = {\n", " \"n\": {\n", @@ -112,7 +123,9 @@ " \"width\": 10,\n", " \"height\": 10,\n", "}" - ] + ], + "outputs": [], + "execution_count": 3 }, { "cell_type": "markdown", @@ -130,16 +143,18 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { - "tags": [] + "tags": [], + "ExecuteTime": { + "end_time": "2024-10-29T19:38:46.864371Z", + "start_time": "2024-10-29T19:38:46.082810Z" + } }, - "outputs": [], "source": [ "# Create initial model instance\n", "model1 = MoneyModel(50, 10, 10)\n", "\n", - "SpaceGraph = make_space_matplotlib(agent_portrayal)\n", + "SpaceGraph = make_space_component(agent_portrayal)\n", "GiniPlot = make_plot_measure(\"Gini\")\n", "\n", "page = SolaraViz(\n", @@ -150,7 +165,27 @@ ")\n", "# This is required to render the visualization in the Jupyter notebook\n", "page" - ] + ], + "outputs": [ + { + "data": { + "text/plain": [ + "Cannot show ipywidgets in text" + ], + "text/html": [ + "Cannot show widget. You probably want to rerun the code cell above (Click in the code cell, and press Shift+Enter +)." + ], + "application/vnd.jupyter.widget-view+json": { + "version_major": 2, + "version_minor": 0, + "model_id": "c9f2ef2b5a24483c92fa129213414a2c" + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "execution_count": 4 }, { "cell_type": "markdown", @@ -169,23 +204,39 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2024-10-29T19:38:46.867576Z", + "start_time": "2024-10-29T19:38:46.865205Z" + } + }, "source": [ "import mesa\n", "print(f\"Mesa version: {mesa.__version__}\")\n", "\n", - "from mesa.visualization import SolaraViz, make_plot_measure, make_space_matplotlib\n", + "from mesa.visualization import SolaraViz, make_plot_measure, make_space_component\n", "# Import the local MoneyModel.py\n", "from MoneyModel import MoneyModel\n" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Mesa version: 3.0.0b2\n" + ] + } + ], + "execution_count": 5 }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2024-10-29T19:38:46.870617Z", + "start_time": "2024-10-29T19:38:46.868336Z" + } + }, "source": [ "def agent_portrayal(agent):\n", " size = 10\n", @@ -207,18 +258,23 @@ " \"width\": 10,\n", " \"height\": 10,\n", "}" - ] + ], + "outputs": [], + "execution_count": 6 }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2024-10-29T19:38:47.881911Z", + "start_time": "2024-10-29T19:38:46.871328Z" + } + }, "source": [ "# Create initial model instance\n", "model1 = MoneyModel(50, 10, 10)\n", "\n", - "SpaceGraph = make_space_matplotlib(agent_portrayal)\n", + "SpaceGraph = make_space_component(agent_portrayal)\n", "GiniPlot = make_plot_measure(\"Gini\")\n", "\n", "page = SolaraViz(\n", @@ -229,7 +285,27 @@ ")\n", "# This is required to render the visualization in the Jupyter notebook\n", "page" - ] + ], + "outputs": [ + { + "data": { + "text/plain": [ + "Cannot show ipywidgets in text" + ], + "text/html": [ + "Cannot show widget. You probably want to rerun the code cell above (Click in the code cell, and press Shift+Enter +)." + ], + "application/vnd.jupyter.widget-view+json": { + "version_major": 2, + "version_minor": 0, + "model_id": "da8518ec9ce74c068288bec0c8d3793e" + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "execution_count": 7 }, { "cell_type": "markdown", @@ -250,9 +326,12 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2024-10-29T19:38:47.885386Z", + "start_time": "2024-10-29T19:38:47.882808Z" + } + }, "source": [ "import mesa\n", "print(f\"Mesa version: {mesa.__version__}\")\n", @@ -260,16 +339,29 @@ "from matplotlib.figure import Figure\n", "\n", "from mesa.visualization.utils import update_counter\n", - "from mesa.visualization import SolaraViz, make_plot_measure, make_space_matplotlib\n", + "from mesa.visualization import SolaraViz, make_plot_measure, make_space_component\n", "# Import the local MoneyModel.py\n", "from MoneyModel import MoneyModel\n" - ] + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Mesa version: 3.0.0b2\n" + ] + } + ], + "execution_count": 8 }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2024-10-29T19:38:47.888491Z", + "start_time": "2024-10-29T19:38:47.886217Z" + } + }, "source": [ "def agent_portrayal(agent):\n", " size = 10\n", @@ -291,7 +383,9 @@ " \"width\": 10,\n", " \"height\": 10,\n", "}" - ] + ], + "outputs": [], + "execution_count": 9 }, { "cell_type": "markdown", @@ -302,9 +396,12 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2024-10-29T19:38:47.893643Z", + "start_time": "2024-10-29T19:38:47.891084Z" + } + }, "source": [ "@solara.component\n", "def Histogram(model):\n", @@ -318,26 +415,36 @@ " # because plt.hist is not thread-safe.\n", " ax.hist(wealth_vals, bins=10)\n", " solara.FigureMatplotlib(fig)" - ] + ], + "outputs": [], + "execution_count": 10 }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2024-10-29T19:38:47.896565Z", + "start_time": "2024-10-29T19:38:47.894387Z" + } + }, "source": [ "# Create initial model instance\n", "model1 = MoneyModel(50, 10, 10)\n", "\n", - "SpaceGraph = make_space_matplotlib(agent_portrayal)\n", + "SpaceGraph = make_space_component(agent_portrayal)\n", "GiniPlot = make_plot_measure(\"Gini\")" - ] + ], + "outputs": [], + "execution_count": 11 }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2024-10-29T19:38:49.471838Z", + "start_time": "2024-10-29T19:38:47.897295Z" + } + }, "source": [ "page = SolaraViz(\n", " model1,\n", @@ -347,7 +454,27 @@ ")\n", "# This is required to render the visualization in the Jupyter notebook\n", "page" - ] + ], + "outputs": [ + { + "data": { + "text/plain": [ + "Cannot show ipywidgets in text" + ], + "text/html": [ + "Cannot show widget. You probably want to rerun the code cell above (Click in the code cell, and press Shift+Enter +)." + ], + "application/vnd.jupyter.widget-view+json": { + "version_major": 2, + "version_minor": 0, + "model_id": "bc71b89ee5684038a194eee4c36f4a4c" + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "execution_count": 12 }, { "cell_type": "markdown", @@ -358,12 +485,35 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "ExecuteTime": { + "end_time": "2024-10-29T19:38:49.505725Z", + "start_time": "2024-10-29T19:38:49.472599Z" + } + }, "source": [ "Histogram(model1)" - ] + ], + "outputs": [ + { + "data": { + "text/plain": [ + "Cannot show ipywidgets in text" + ], + "text/html": [ + "Cannot show widget. You probably want to rerun the code cell above (Click in the code cell, and press Shift+Enter +)." + ], + "application/vnd.jupyter.widget-view+json": { + "version_major": 2, + "version_minor": 0, + "model_id": "0491f167a1434a92b78535078bd082a8" + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "execution_count": 13 }, { "cell_type": "markdown", From 8c3d928424b9615670a22a836a76f2cde3da3235 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Tue, 29 Oct 2024 20:48:23 +0100 Subject: [PATCH 72/74] docs --- mesa/visualization/components/matplotlib.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 3c9ad970ce7..8601d2392e2 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -130,12 +130,8 @@ def collect_agent_data( marker: default marker zorder: default zorder - Notes: - agent portray dict is limited to size (size of marker), color (color of marker, zorder, and marker (marker style) - see `Matplotlib`_. - - - .. _Matplotlib: https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.scatter.html + agent_portrayal should return a dict, limited to size (size of marker), color (color of marker), zorder (z-order), + and marker (marker style) """ arguments = {"s": [], "c": [], "marker": [], "zorder": [], "loc": []} From 642ba120af31d98fc5babe45b0e66e5f7f13ef87 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 29 Oct 2024 19:49:44 +0000 Subject: [PATCH 73/74] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/visualization/components/matplotlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesa/visualization/components/matplotlib.py b/mesa/visualization/components/matplotlib.py index 8601d2392e2..7e9982a7387 100644 --- a/mesa/visualization/components/matplotlib.py +++ b/mesa/visualization/components/matplotlib.py @@ -130,7 +130,7 @@ def collect_agent_data( marker: default marker zorder: default zorder - agent_portrayal should return a dict, limited to size (size of marker), color (color of marker), zorder (z-order), + agent_portrayal should return a dict, limited to size (size of marker), color (color of marker), zorder (z-order), and marker (marker style) """ From 10c24c9e82c4e33a745e6faa8e7665482bd0edb5 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Wed, 30 Oct 2024 09:17:42 +0100 Subject: [PATCH 74/74] Update mesa/examples/basic/conways_game_of_life/app.py Co-authored-by: Ewout ter Hoeven --- mesa/examples/basic/conways_game_of_life/app.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/mesa/examples/basic/conways_game_of_life/app.py b/mesa/examples/basic/conways_game_of_life/app.py index aee1733e523..7a45125a30a 100644 --- a/mesa/examples/basic/conways_game_of_life/app.py +++ b/mesa/examples/basic/conways_game_of_life/app.py @@ -1,11 +1,3 @@ -import os.path -import sys - -sys.path.insert( - 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../")) -) - - from mesa.examples.basic.conways_game_of_life.model import ConwaysGameOfLife from mesa.visualization import ( SolaraViz,