Skip to content

Added AgentPortrayalStyle and PropertyLayerStyle #2786

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
May 26, 2025

Conversation

Sahil-Chhoker
Copy link
Collaborator

@Sahil-Chhoker Sahil-Chhoker commented May 19, 2025

Summary

Added AgentPortrayalStyle and PropertyLayerStyle, both are returned from a callable called upon agents and layers respectively.

In AgnetPortrayalStyle one can define:

  • x (agent's position) (no default)
  • y (no default)
  • color (default: tab:blue)
  • marker (default: 'o')
  • size (passed by the drawing functions)
  • zorder (default: 1)
  • alpha (default: 1)
  • edgecolor (default: color)
  • linewidth (default: 1)

In PropertyLayerStyle one can define:

  • colormap (default: None)
  • color (default: None)
  • alpha (default: 0.8)
  • colorbar (default: True)
  • vmin (default: min value in property layer)
  • vmax (default: max value in property layer)
    color and colormap can't coexist.

Motive

Eliminating the use of dictionary in defining styles.

Usage Example

  1. AgnetPortraylStyle:
def agent_portrayal(agent):
    return AgentPortrayalStyle(
        x=agent.cell.coordinate[0],
        y=agent.cell.coordinate[1],
        color="tab:blue" if agent.type == 0 else "tab:orange",
        marker="o",
        size=50,
        edgecolors="tab:blue",
        linewidths=3,
    )
  1. PropertyLayerStyle:
def propertylayer_portrayl(layer):
    if layer.name == "sugar":
        return PropertyLayerStyle(color="tab:blue", alpha=0.6, colorbar=False)
    return return PropertyLayerStyle(color="tab:orange", alpha=0.6, colorbar=False)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced customizable styling options for agent and property layer visualizations, allowing more precise control over appearance such as color, marker shape, size, and transparency.

- **Refactor**
  - Updated the visualization interface to use structured style objects for agent and property layer portrayals, replacing previous dictionary-based configurations for improved clarity and consistency.
  - Enhanced default handling and validation for visualization styles.
  - Modified drawing functions to support the new style objects and updated parameters.

- **Documentation**
  - Refreshed docstrings to reflect the new portrayal interface and available styling options.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Copy link

Performance benchmarks:

Model Size Init time [95% CI] Run time [95% CI]
BoltzmannWealth small 🔵 -0.8% [-1.6%, +0.1%] 🔵 -1.7% [-2.0%, -1.4%]
BoltzmannWealth large 🔵 -2.2% [-2.9%, -1.5%] 🟢 -8.8% [-10.7%, -6.7%]
Schelling small 🔵 -0.6% [-0.8%, -0.3%] 🔵 -0.8% [-1.1%, -0.4%]
Schelling large 🔵 -0.5% [-1.1%, +0.2%] 🔵 -2.7% [-5.8%, +0.1%]
WolfSheep small 🔵 +2.2% [+1.7%, +2.7%] 🔵 +0.1% [-0.3%, +0.5%]
WolfSheep large 🔵 +4.4% [+2.4%, +6.0%] 🔴 +7.6% [+4.0%, +10.8%]
BoidFlockers small 🔵 +1.3% [+0.5%, +2.0%] 🔵 +1.3% [+1.1%, +1.5%]
BoidFlockers large 🔵 +0.5% [-0.3%, +1.2%] 🔵 +1.1% [+0.7%, +1.5%]

@Sahil-Chhoker
Copy link
Collaborator Author

@coderabbitai full review

Copy link

coderabbitai bot commented May 19, 2025

"""

Walkthrough

The changes introduce two new dataclasses, AgentPortrayalStyle and PropertyLayerStyle, to standardize the styling of agents and property layers in Mesa visualizations. The visualization interface is refactored to use these classes instead of dictionaries, updating function signatures, docstrings, and internal logic to support the new, strongly typed portrayal system.

Changes

File(s) Change Summary
mesa/visualization/components/__init__.py Updated module docstring; imported and exported AgentPortrayalStyle and PropertyLayerStyle in __all__.
mesa/visualization/components/portrayal_components.py Added new module defining AgentPortrayalStyle and PropertyLayerStyle dataclasses with validation and update logic.
mesa/visualization/mpl_space_drawing.py Refactored to require AgentPortrayalStyle and PropertyLayerStyle for agent and property layer styling; updated function signatures, docstrings, and internal logic; removed unused imports.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant VisualizationFunction
    participant AgentPortrayalFn
    participant AgentPortrayalStyle
    participant PropertyLayerPortrayalFn
    participant PropertyLayerStyle

    User->>VisualizationFunction: Call draw_space(..., agent_portrayal, ...)
    VisualizationFunction->>AgentPortrayalFn: agent_portrayal(agent)
    AgentPortrayalFn-->>VisualizationFunction: AgentPortrayalStyle instance
    VisualizationFunction->>AgentPortrayalStyle: Access style attributes
    VisualizationFunction->>VisualizationFunction: Render agent using style

    User->>VisualizationFunction: Call draw_property_layers(..., propertylayer_portrayal, ...)
    VisualizationFunction->>PropertyLayerPortrayalFn: propertylayer_portrayal(property_layer)
    PropertyLayerPortrayalFn-->>VisualizationFunction: PropertyLayerStyle instance
    VisualizationFunction->>PropertyLayerStyle: Access style attributes
    VisualizationFunction->>VisualizationFunction: Render property layer using style
Loading

Possibly related issues

Poem

In fields of code where agents play,
New styles arrive to light the way.
Portrayals now are typed and neat,
With dataclasses—oh, what a treat!
Colors, shapes, and layers blend,
Visualization’s now my friend.
🐇✨
"""

Note

⚡️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
Learn more here.


Note

⚡️ Faster reviews with caching

CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.
Enjoy the performance boost—your workflow just got faster.

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🔭 Outside diff range comments (4)
mesa/visualization/components/__init__.py (1)

21-27: ⚠️ Potential issue

propertylayer_portrayal typing & docstring are out-of-date

mpl_space_drawing.draw_property_layers now expects a callable that returns a PropertyLayerStyle, but make_space_component still advertises (and type-hints) this parameter as a dict. Passing a dict will raise TypeError: 'dict' object is not callable.

-    propertylayer_portrayal: dict | None = None,
+    propertylayer_portrayal: Callable | None = None,

Please adjust the docstring accordingly (Dictionary of PropertyLayer portrayal specifications → “Callable returning a PropertyLayerStyle”) and propagate the new type to any other interfaces (draw_space, examples, docs).

mesa/visualization/mpl_space_drawing.py (3)

161-175: 🛠️ Refactor suggestion

draw_space still advertises dict-based portrayal

The function signature and docstring say propertylayer_portrayal: dict | None, but the implementation now forwards this value to draw_property_layers, which calls it as a function (portrayal = propertylayer_portrayal(layer)).
Consequently, passing a dict will crash at runtime.

Please switch the annotation to Callable | None (mirroring the behaviour in draw_property_layers) and update the surrounding docstring.


251-276: ⚠️ Potential issue

Callable vs. Dict mismatch may raise TypeError

draw_property_layers assumes propertylayer_portrayal is callable. If users still pass a dict (as the public API previously required), this line will fail:

portrayal = propertylayer_portrayal(layer)

Consider:

  1. Adding a compatibility shim similar to the one in collect_agent_data that recognises a dict and issues a deprecation warning, or
  2. Updating all upstream signatures/guides/examples to require a callable.

Fail-fast with a clear message if the input is neither callable nor None.


688-696: ⚠️ Potential issue

Boolean-mask bug in _scatter produces TypeError

mark_mask is currently built as a Python list, which cannot participate in element-wise & with a NumPy array (zorder_mask). At runtime this raises:

TypeError: unsupported operand type(s) for &: 'list' and 'numpy.ndarray'

-        mark_mask = [m == mark for m in list(marker)]
+        # Vectorised NumPy comparison – much faster and avoids the type error
+        mark_mask = marker == mark
...
-            logical = mark_mask & zorder_mask
+            logical = np.logical_and(mark_mask, zorder_mask)

Add a small unit test that plots two markers with different z-orders to catch this regression in CI.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e6ab85 and b852f5b.

📒 Files selected for processing (3)
  • mesa/visualization/components/__init__.py (2 hunks)
  • mesa/visualization/components/portrayal_components.py (1 hunks)
  • mesa/visualization/mpl_space_drawing.py (16 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
mesa/visualization/components/__init__.py (1)
mesa/visualization/components/portrayal_components.py (2)
  • AgentPortrayalStyle (17-51)
  • PropertyLayerStyle (55-78)

@Sahil-Chhoker
Copy link
Collaborator Author

This is my current implementation of AgnetPortrayalStyle and PropertyLayerStyle. @tpike3, @quaquel and @EwoutH, I would love your opinions on this.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🔭 Outside diff range comments (2)
mesa/visualization/components/__init__.py (1)

21-36: 🛠️ Refactor suggestion

Update type hints & doc-strings to the new Callable-based propertylayer_portrayal

The function signature and docs still describe propertylayer_portrayal as a dict, but the implementation in mpl_space_drawing.draw_property_layers now expects a callable returning a PropertyLayerStyle.

Please align the public interface:

-    propertylayer_portrayal: dict | None = None,
+    propertylayer_portrayal: Callable | None = None,
...
-        propertylayer_portrayal: Dictionary of PropertyLayer portrayal specifications
+        propertylayer_portrayal: A callable that receives a `PropertyLayer`
+            instance and returns a `PropertyLayerStyle` (or `None` to skip).

Failing to do so misleads users and static type checkers alike.

mesa/visualization/mpl_space_drawing.py (1)

290-307: 🛠️ Refactor suggestion

Edge case: generated colormap may explode if vmin == vmax

When all layer values are identical, the normalisation rgba = (data - vmin) / (vmax - vmin) will divide by zero. Guard against this by early-returning or perturbing vmax:

if vmin == vmax:
    vmax = vmin + 1e-9

This prevents RuntimeWarnings and NaNs showing up in the visualisation.

♻️ Duplicate comments (2)
mesa/visualization/components/__init__.py (1)

15-18: __all__ still clobbers the pre-existing public API – extend, don’t overwrite

The PR keeps the previous behaviour that replaces __all__ with just the two new classes.
Everything that used to be publicly re-exported from this package (e.g. SpaceMatplotlib, make_space_component, …) silently disappears and will break from mesa.visualization.components import … in downstream code.

-__all__ = [
-    "AgentPortrayalStyle",
-    "PropertyLayerStyle",
-]
+# Preserve the earlier exports (if any) and append the newcomers
+try:
+    __all__  # type: ignore[name-defined]
+except NameError:
+    __all__: list[str] = []
+
+__all__ += [
+    "AgentPortrayalStyle",
+    "PropertyLayerStyle",
+]
mesa/visualization/components/portrayal_components.py (1)

35-45: Use typing.Any and consider a more Pythonic update(**kwargs)

any is the built-in predicate, not a type. Static analysers will complain.

-from dataclasses import dataclass
+from dataclasses import dataclass
+from typing import Any
...
-    def update(self, *updates_fields: tuple[str, any]):
+    def update(self, *updates_fields: tuple[str, Any]):

While touching this, you may want to offer the more idiomatic signature update(**fields_to_change) to avoid the slightly awkward style.update(("color", "red")) call-site.

Optionally add __slots__ = () to both dataclasses; these objects can be created thousands of times per tick and the space saving is noticeable.

🧹 Nitpick comments (2)
mesa/visualization/components/portrayal_components.py (1)

16-34: Lightweight optimisation: add __slots__ to both style dataclasses

As these style objects are instantiated per agent / per layer every frame, ditching the per-instance __dict__ can cut memory usage and speed up attribute access:

 @dataclass
 class AgentPortrayalStyle:
+    __slots__ = ("x", "y", "color", "marker", "size", "zorder",
+                 "alpha", "edgecolors", "linewidths")

Repeat for PropertyLayerStyle.

mesa/visualization/mpl_space_drawing.py (1)

45-59: Doc-string & param type drift in collect_agent_data

default_size was introduced, but:

  1. The doc-string still talks about “limited to size, color …” dict return values.
  2. agent_portrayal now must yield an AgentPortrayalStyle, but the Args: section doesn’t mention the dataclass.

Please refresh the doc-string accordingly to avoid user confusion and mismatched IDE hints.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e6ab85 and b852f5b.

📒 Files selected for processing (3)
  • mesa/visualization/components/__init__.py (2 hunks)
  • mesa/visualization/components/portrayal_components.py (1 hunks)
  • mesa/visualization/mpl_space_drawing.py (16 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
mesa/visualization/components/__init__.py (1)
mesa/visualization/components/portrayal_components.py (2)
  • AgentPortrayalStyle (17-51)
  • PropertyLayerStyle (55-78)

@Sahil-Chhoker Sahil-Chhoker added breaking Release notes label enhancement Release notes label labels May 20, 2025
@tpike3
Copy link
Member

tpike3 commented May 23, 2025

@Sahil-Chhoker -- impressive work!

Can you add a mesa example, showing what implementation looks like for the user?

Appreciating this is a breaking change, we are going to have to do at least do a transition, where the existing approach works and agentportrayalstyle and propertylayerstyle works with a warning to transition. Unless there is a way to do a conversion so it will stay backwards compatible.

As this is GSoC, can you add some cprofile information, just to see where the processing is occurring. I am curious why WolfSheep and BoidFlockers increased while BoltzMannWealth and Schelling went down.

@Sahil-Chhoker
Copy link
Collaborator Author

Can you add a mesa example, showing what implementation looks like for the user?

Sure, will do.

Appreciating this is a breaking change, we are going to have to do at least do a transition, where the existing approach works and agentportrayalstyle and propertylayerstyle works with a warning to transition. Unless there is a way to do a conversion so it will stay backwards compatible.

The agent portrayal currently works for both dict and AgentPortrayalStyle, it is also possible with property layer portrayal, so I will add that as soon as I am free.

As this is GSoC, can you add some cprofile information, just to see where the processing is occurring. I am curious why WolfSheep and BoidFlockers increased while BoltzMannWealth and Schelling went down.

I will look into it.

@tpike3
Copy link
Member

tpike3 commented May 23, 2025

Can you add a mesa example, showing what implementation looks like for the user?

Sure, will do.

Appreciating this is a breaking change, we are going to have to do at least do a transition, where the existing approach works and agentportrayalstyle and propertylayerstyle works with a warning to transition. Unless there is a way to do a conversion so it will stay backwards compatible.

The agent portrayal currently works for both dict and AgentPortrayalStyle, it is also possible with property layer portrayal, so I will add that as soon as I am free.

As this is GSoC, can you add some cprofile information, just to see where the processing is occurring. I am curious why WolfSheep and BoidFlockers increased while BoltzMannWealth and Schelling went down.

I will look into it.

Thanks @Sahil-Chhoker!

@Sahil-Chhoker Sahil-Chhoker added the trigger-benchmarks Special label that triggers the benchmarking CI label May 24, 2025
@Sahil-Chhoker Sahil-Chhoker requested review from quaquel and removed request for quaquel and EwoutH May 24, 2025 06:01
Copy link
Member

@tpike3 tpike3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Sahil-Chhoker I am good with this.

@tpike3
Copy link
Member

tpike3 commented May 24, 2025

@EwoutH, @quaquel and @jackiekazil -- Please take a look and let me know if you have any concerns.

As this is mesa-vis I would like at least one more approval before merge.

Here is @Sahil-Chhoker project timeline for reference ---

Timeline:
Week 1 & 2: Develop AgentPortrayalStyle and PropertyLayerStyle
Week 3 & 4: Develop CoordinateMapper
Week 5, 6 & 7: Develop SpaceRenderer
Week 8: Buffer
Week 9: Complete model sharing through links feature.
Week 10, 11 & 12: Implement Altair backend inline with matplotlib.

@quaquel
Copy link
Member

quaquel commented May 25, 2025

I have one request: can you add unit tests to ensure we have 100% coverage on the new code?

I had a quick look at the code and that looks all fine for now. We'll have to see how it integrates with the rest that you have planned. But that is fine.

@Sahil-Chhoker
Copy link
Collaborator Author

I'm not quite able to understand what codecov is mentioning here, am I supposed to write tests for the components AgentPortrayalStyle and PropertyLayerStyle or am I supposed to write tests for the example sugarscape?

@quaquel
Copy link
Member

quaquel commented May 25, 2025

Tests for the new components, not for sugarscape.

@tpike3
Copy link
Member

tpike3 commented May 25, 2025

Tests for the new components, not for sugarscape.

That was weird that sugarscape had its own tests, looking through the history it looks like it is an artifact of moving from the examples repo.

@Sahil-Chhoker --- please feel free to delete the tests.py in sugarscape g1mt mesa/examples/advanced/sugarscape_g1mt/tests.py --- it is already test in the mesa/tests/test_examples.py

@tpike3
Copy link
Member

tpike3 commented May 26, 2025

All comments addressed so merging --- conversation continuing on at #2772

@tpike3 tpike3 added visualisation and removed breaking Release notes label enhancement Release notes label labels May 26, 2025
@tpike3 tpike3 merged commit 55770cd into projectmesa:main May 26, 2025
12 of 13 checks passed
@EwoutH
Copy link
Member

EwoutH commented May 26, 2025

Congratulations on having this PR merged!

@EwoutH
Copy link
Member

EwoutH commented May 26, 2025

A few things now that I think of it: since this is a breaking change, we should:

  • Split off a 3.2 maintenance branch (from main just before this commit)
  • Add a section to the migration guide
  • Consider if our next release is going to be 3.3 or 4.0

@quaquel
Copy link
Member

quaquel commented May 26, 2025

As far as I understand, the PR is not breaking because both dicts and the new class are supported. @Sahil-Chhoker correct me if I am wrong.

@Sahil-Chhoker
Copy link
Collaborator Author

Yes the older versions of both agent_portrayal and propertylayer_portryal works fine but just give a warning to switch.

@EwoutH
Copy link
Member

EwoutH commented May 26, 2025

Sorry, I thought that it replaced is. Should have looked better. Having both supported is great for now!

I would suggest disabling the warning for now, so users have one overlapping release to switch organically. After that we can start issuing deprecation warnings.

@Sahil-Chhoker
Copy link
Collaborator Author

Added a PR removing the deprecation warnings in PR #2797.

Holzhauer pushed a commit to UniK-INES/mesa that referenced this pull request May 28, 2025
* added AgentPortrayalStyle and PropertyLayerStyle

* added docstrings

* added coderabbit review

* added usage example and fix __init__ file

* added backwards compatability for dict propertylayer_portrayal

* fix the network logic

* null check for agent pos

* added tests

* delete unwanted test
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants