From 65ef23461ef35986d023e85f392205ee4836569b Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Fri, 8 Nov 2024 10:01:23 +0100 Subject: [PATCH 1/8] add some more support to agent_portrayal --- mesa/visualization/mpl_space_drawing.py | 33 ++++++++++- tests/test_components_matplotlib.py | 79 +++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 3 deletions(-) diff --git a/mesa/visualization/mpl_space_drawing.py b/mesa/visualization/mpl_space_drawing.py index 6353d8106b8..9c068090f66 100644 --- a/mesa/visualization/mpl_space_drawing.py +++ b/mesa/visualization/mpl_space_drawing.py @@ -64,7 +64,12 @@ def collect_agent_data( and marker (marker style) """ - arguments = {"s": [], "c": [], "marker": [], "zorder": [], "loc": []} + # "edgecolor", "linewidth" can operate on individual agent level as list, or single command at ax.scatter level + # so interdependency if not passed at agent level but passed as extra kwargs --> you can do something + # "alpha": 0.7 $ only works for single scatter commend + + arguments = {"s": [], "c": [], "marker": [], "zorder": [], "loc": [], 'alpha': [], + 'edgecolors': [], 'linewidths': []} for agent in space.agents: portray = agent_portrayal(agent) @@ -78,6 +83,12 @@ def collect_agent_data( arguments["marker"].append(portray.pop("marker", marker)) arguments["zorder"].append(portray.pop("zorder", zorder)) + for entry in ["alpha", "edgecolors", "linewidths"]: + try: + arguments[entry].append(portray.pop(entry)) + except KeyError: + pass + if len(portray) > 0: ignored_fields = list(portray.keys()) msg = ", ".join(ignored_fields) @@ -118,16 +129,20 @@ 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, **space_drawing_kwargs) + # order matters here given the class structure of old-style grid spaces case HexSingleGrid() | HexMultiGrid() | mesa.experimental.cell_space.HexGrid(): draw_hex_grid(space, agent_portrayal, ax=ax, **space_drawing_kwargs) + case mesa.space.SingleGrid() | OrthogonalMooreGrid() | OrthogonalVonNeumannGrid() | mesa.space.MultiGrid(): + draw_orthogonal_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, **space_drawing_kwargs) case mesa.space.ContinuousSpace(): draw_continuous_space(space, agent_portrayal, ax=ax) case VoronoiGrid(): draw_voroinoi_grid(space, agent_portrayal, ax=ax) + case _: + raise ValueError(f"Unknown space type: {type(space)}") + if propertylayer_portrayal: draw_property_layers(space, propertylayer_portrayal, ax=ax) @@ -543,11 +558,23 @@ def _scatter(ax: Axes, arguments, **kwargs): marker = arguments.pop("marker") zorder = arguments.pop("zorder") + # we check if edgecolor, linewidth, and alhpa are specified + # at the agent level, if not, we remove them from the arguments dict + # and fallback to the default value in ax.scatter / use what is passed via **kwargs + for entry in ["edgecolors", "linewidths", "alpha"]: + if len(arguments[entry]) == 0: + arguments.pop(entry) + else: + if entry in kwargs: + raise ValueError(f"{entry} is specified in agent portrayal and via plotting kwargs, you can only use one or the other") + + 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], diff --git a/tests/test_components_matplotlib.py b/tests/test_components_matplotlib.py index 9c454e77b2e..202b1ad6f61 100644 --- a/tests/test_components_matplotlib.py +++ b/tests/test_components_matplotlib.py @@ -24,6 +24,7 @@ draw_orthogonal_grid, draw_property_layers, draw_voroinoi_grid, + draw_space ) @@ -41,6 +42,84 @@ def agent_portrayal(agent): } +def test_draw_space(): + """test draw_space helper method.""" + import networkx as nx + + def my_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", + "alpha": 0.5, + "linewidths": 1, + "linecolors": "tab:orange" + } + + # draw space for hexgrid + model = Model(seed=42) + grid = HexSingleGrid(10, 10, torus=True) + for _ in range(10): + agent = Agent(model) + grid.move_to_empty(agent) + + fig, ax = plt.subplots() + draw_space(grid, my_portrayal, ax=ax) + + # draw space for voroinoi + 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() + + fig, ax = plt.subplots() + draw_space(grid, my_portrayal, ax=ax) + + # draw orthogonal grid + 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() + fig, ax = plt.subplots() + draw_space(grid, my_portrayal, ax=ax) + + # draw network + n = 10 + m = 20 + seed = 42 + 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) + grid.place_agent(agent, pos) + fig, ax = plt.subplots() + draw_space(grid, my_portrayal, ax=ax) + + # draw 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)) + + fig, ax = plt.subplots() + draw_space(space, my_portrayal, ax=ax) + + def test_draw_hex_grid(): """Test drawing hexgrids.""" model = Model(seed=42) From 7d935a9ed0158faf766637172b79249ea81f7c5d Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Fri, 8 Nov 2024 10:02:28 +0100 Subject: [PATCH 2/8] Update mpl_space_drawing.py --- mesa/visualization/mpl_space_drawing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesa/visualization/mpl_space_drawing.py b/mesa/visualization/mpl_space_drawing.py index 9c068090f66..e1d0da8b014 100644 --- a/mesa/visualization/mpl_space_drawing.py +++ b/mesa/visualization/mpl_space_drawing.py @@ -61,7 +61,7 @@ def collect_agent_data( zorder: default zorder agent_portrayal should return a dict, limited to size (size of marker), color (color of marker), zorder (z-order), - and marker (marker style) + marker (marker style), alpha, linewidths, and edgecolors """ # "edgecolor", "linewidth" can operate on individual agent level as list, or single command at ax.scatter level From f161255a3ca90f2fd02aaef1ff7f98034482a3e2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 8 Nov 2024 09:07:00 +0000 Subject: [PATCH 3/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/visualization/mpl_space_drawing.py | 25 +++++++++++++++++++------ tests/test_components_matplotlib.py | 6 +++--- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/mesa/visualization/mpl_space_drawing.py b/mesa/visualization/mpl_space_drawing.py index e1d0da8b014..37f58be560c 100644 --- a/mesa/visualization/mpl_space_drawing.py +++ b/mesa/visualization/mpl_space_drawing.py @@ -68,8 +68,16 @@ def collect_agent_data( # so interdependency if not passed at agent level but passed as extra kwargs --> you can do something # "alpha": 0.7 $ only works for single scatter commend - arguments = {"s": [], "c": [], "marker": [], "zorder": [], "loc": [], 'alpha': [], - 'edgecolors': [], 'linewidths': []} + arguments = { + "s": [], + "c": [], + "marker": [], + "zorder": [], + "loc": [], + "alpha": [], + "edgecolors": [], + "linewidths": [], + } for agent in space.agents: portray = agent_portrayal(agent) @@ -132,7 +140,12 @@ def draw_space( # order matters here given the class structure of old-style grid spaces case HexSingleGrid() | HexMultiGrid() | mesa.experimental.cell_space.HexGrid(): draw_hex_grid(space, agent_portrayal, ax=ax, **space_drawing_kwargs) - case mesa.space.SingleGrid() | OrthogonalMooreGrid() | OrthogonalVonNeumannGrid() | mesa.space.MultiGrid(): + case ( + mesa.space.SingleGrid() + | OrthogonalMooreGrid() + | OrthogonalVonNeumannGrid() + | mesa.space.MultiGrid() + ): draw_orthogonal_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, **space_drawing_kwargs) @@ -143,7 +156,6 @@ def draw_space( case _: raise ValueError(f"Unknown space type: {type(space)}") - if propertylayer_portrayal: draw_property_layers(space, propertylayer_portrayal, ax=ax) @@ -566,8 +578,9 @@ def _scatter(ax: Axes, arguments, **kwargs): arguments.pop(entry) else: if entry in kwargs: - raise ValueError(f"{entry} is specified in agent portrayal and via plotting kwargs, you can only use one or the other") - + raise ValueError( + f"{entry} is specified in agent portrayal and via plotting kwargs, you can only use one or the other" + ) for mark in np.unique(marker): mark_mask = marker == mark diff --git a/tests/test_components_matplotlib.py b/tests/test_components_matplotlib.py index 202b1ad6f61..67ab800c181 100644 --- a/tests/test_components_matplotlib.py +++ b/tests/test_components_matplotlib.py @@ -23,8 +23,8 @@ draw_network, draw_orthogonal_grid, draw_property_layers, + draw_space, draw_voroinoi_grid, - draw_space ) @@ -43,7 +43,7 @@ def agent_portrayal(agent): def test_draw_space(): - """test draw_space helper method.""" + """Test draw_space helper method.""" import networkx as nx def my_portrayal(agent): @@ -59,7 +59,7 @@ def my_portrayal(agent): "marker": "s" if (agent.unique_id % 2) == 0 else "o", "alpha": 0.5, "linewidths": 1, - "linecolors": "tab:orange" + "linecolors": "tab:orange", } # draw space for hexgrid From 44e5829fa21468da086d72e7b20ef05cbe234a31 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Fri, 8 Nov 2024 10:19:01 +0100 Subject: [PATCH 4/8] Update mpl_space_drawing.py --- mesa/visualization/mpl_space_drawing.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mesa/visualization/mpl_space_drawing.py b/mesa/visualization/mpl_space_drawing.py index 37f58be560c..60415cf5b5c 100644 --- a/mesa/visualization/mpl_space_drawing.py +++ b/mesa/visualization/mpl_space_drawing.py @@ -5,7 +5,7 @@ for a paper. """ - +import contextlib import itertools import math import warnings @@ -92,10 +92,8 @@ def collect_agent_data( arguments["zorder"].append(portray.pop("zorder", zorder)) for entry in ["alpha", "edgecolors", "linewidths"]: - try: + with contextlib.suppress(KeyError): arguments[entry].append(portray.pop(entry)) - except KeyError: - pass if len(portray) > 0: ignored_fields = list(portray.keys()) From 4257f06cd4cfd10803bba194af1f699c56966d9a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 8 Nov 2024 09:19:43 +0000 Subject: [PATCH 5/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/visualization/mpl_space_drawing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mesa/visualization/mpl_space_drawing.py b/mesa/visualization/mpl_space_drawing.py index 60415cf5b5c..d6f3a551670 100644 --- a/mesa/visualization/mpl_space_drawing.py +++ b/mesa/visualization/mpl_space_drawing.py @@ -5,6 +5,7 @@ for a paper. """ + import contextlib import itertools import math From 1fca882cb68db831b99601e123ecb16b86472795 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Fri, 8 Nov 2024 10:23:10 +0100 Subject: [PATCH 6/8] Update mpl_space_drawing.py --- mesa/visualization/mpl_space_drawing.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/mesa/visualization/mpl_space_drawing.py b/mesa/visualization/mpl_space_drawing.py index d6f3a551670..686b1a97796 100644 --- a/mesa/visualization/mpl_space_drawing.py +++ b/mesa/visualization/mpl_space_drawing.py @@ -65,10 +65,6 @@ def collect_agent_data( marker (marker style), alpha, linewidths, and edgecolors """ - # "edgecolor", "linewidth" can operate on individual agent level as list, or single command at ax.scatter level - # so interdependency if not passed at agent level but passed as extra kwargs --> you can do something - # "alpha": 0.7 $ only works for single scatter commend - arguments = { "s": [], "c": [], @@ -569,7 +565,7 @@ def _scatter(ax: Axes, arguments, **kwargs): marker = arguments.pop("marker") zorder = arguments.pop("zorder") - # we check if edgecolor, linewidth, and alhpa are specified + # we check if edgecolor, linewidth, and alpha are specified # at the agent level, if not, we remove them from the arguments dict # and fallback to the default value in ax.scatter / use what is passed via **kwargs for entry in ["edgecolors", "linewidths", "alpha"]: From a77ef1319ab52132cb72a0b267588b5cca482429 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Fri, 8 Nov 2024 12:47:30 +0100 Subject: [PATCH 7/8] some additional docs --- mesa/visualization/components/matplotlib_components.py | 3 +-- mesa/visualization/mpl_space_drawing.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/mesa/visualization/components/matplotlib_components.py b/mesa/visualization/components/matplotlib_components.py index 6c7bb1ae040..9e8464f22fa 100644 --- a/mesa/visualization/components/matplotlib_components.py +++ b/mesa/visualization/components/matplotlib_components.py @@ -38,8 +38,7 @@ def make_mpl_space_component( 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", "marker", and "zorder". Other field are ignored and will result in a user warning. - + "size", "marker", "zorder", alpha, linewidths, and edgecolors. Other field are ignored and will result in a user warning. Returns: function: A function that creates a SpaceMatplotlib component diff --git a/mesa/visualization/mpl_space_drawing.py b/mesa/visualization/mpl_space_drawing.py index 686b1a97796..4505d8b5bf1 100644 --- a/mesa/visualization/mpl_space_drawing.py +++ b/mesa/visualization/mpl_space_drawing.py @@ -124,7 +124,7 @@ def draw_space( 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. + "size", "marker", "zorder", alpha, linewidths, and edgecolors. Other field are ignored and will result in a user warning. """ if ax is None: From 2594475759abfc456ed0a00ffd92aae28896ea78 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Fri, 8 Nov 2024 14:01:33 +0100 Subject: [PATCH 8/8] Update test_components_matplotlib.py --- tests/test_components_matplotlib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_components_matplotlib.py b/tests/test_components_matplotlib.py index dba61d45641..f258b58d90b 100644 --- a/tests/test_components_matplotlib.py +++ b/tests/test_components_matplotlib.py @@ -141,8 +141,8 @@ def test_draw_hex_grid(): draw_hex_grid(grid, agent_portrayal, ax) -def test_draw_voroinoi_grid(): - """Test drawing voroinoi grids.""" +def test_draw_voronoi_grid(): + """Test drawing voronoi grids.""" model = Model(seed=42) coordinates = model.rng.random((100, 2)) * 10