Skip to content

Feature: Portrayal components for agents and property layers #2661

New issue

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

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

Already on GitHub? # to your account

Conversation

Sahil-Chhoker
Copy link
Collaborator

Summary

This PR addresses issue #2436 and is based on discussions from PR #2640 and #2642. It introduces three new features into Mesa: AgentPortrayalStyle, PropertyLayerStyle, and PropertyLayerPortrayal. The goal is to initiate an API redesign for separating agent, grid, and property layer drawing, as discussed in #2642.

Implementation

As described, this PR introduces three new dataclasses in Mesa. Let's explore them:

  1. AgentPortrayalStyle:
@dataclass
class AgentPortrayalStyle:
    color: str | tuple | None = "tab:blue"
    marker: str | None = "o"
    size: Number | None = 50
    zorder: int | None = 1

This class validates parameters for agent portrayal in its __post_init__ method and uses the __iter__ dunder method to convert the class into a dictionary.

Usage:

def agent_portrayal(agent):
    portrayal = AgentPortrayalStyle(
        color='tab:orange' if agent.id == 1 else 'tab:blue',
        marker='^',
        size=70,
        zorder=1
    )
    return dict(portrayal)

  1. PropertyLayerStyle:
@dataclass
class PropertyLayerStyle:
    vmin: Optional[float] = None
    vmax: Optional[float] = None
    alpha: Optional[float] = None
    colorbar: Optional[bool] = None
    color: Optional[str] = None
    colormap: Optional[Union[str, Colormap]] = None

This class also validates parameters in its __post_init__ method. For instance, a property layer can take either a color or a colormap:

if self.color is None and self.colormap is None:
    raise ValueError("Please specify either color or colormap")
if self.color is not None and self.colormap is not None:
    raise ValueError("Cannot specify both color and colormap")

and so on...

This class serves as the basis for the next class PropertyLayerPortrayal that is explained below.


  1. PropertyLayerPortrayal:
    Manages property layer styles and introduces a new way to define property layer portrayal for multiple layers. Defines a dictionary named layers:
layers: dict[str, PropertyLayerStyle] = field(default_factory=dict)

add_layer method is used to add portrayal for different layers.

def add_layer(
        self,
        name: str,
        color: str | None = None,
        colormap: str | Colormap | None = None,
        vmin: float | None = None,
        vmax: float | None = None,
        alpha: float | None = None,
        colorbar: bool | None = None,
    ) -> None:

Usage:

portrayal = PropertyLayerPortrayal()
portrayal.add_layer('temperature', colormap='coolwarm', vmin=0, vmax=100)
portrayal.add_layer('elevation', color='brown', vmin=0, vmax=100)
...
propertylayer_portrayal = dict(portrayal)

Additional Notes:

Some changes are made in mpl_space_drawing.py according to the current features.

Copy link

github-actions bot commented Feb 4, 2025

Performance benchmarks:

Model Size Init time [95% CI] Run time [95% CI]
BoltzmannWealth small 🔵 +1.8% [+0.9%, +2.7%] 🔵 +0.3% [+0.1%, +0.5%]
BoltzmannWealth large 🔵 +1.0% [+0.1%, +1.9%] 🔴 +8.3% [+6.4%, +10.0%]
Schelling small 🔵 -1.0% [-1.3%, -0.7%] 🔵 -0.3% [-0.5%, -0.2%]
Schelling large 🔵 -0.6% [-0.8%, -0.4%] 🔵 +0.4% [-1.1%, +2.0%]
WolfSheep small 🔵 +1.0% [+0.6%, +1.3%] 🔵 +2.2% [+2.0%, +2.6%]
WolfSheep large 🔵 +2.4% [+1.2%, +3.8%] 🔴 +7.4% [+6.4%, +8.6%]
BoidFlockers small 🔵 -0.7% [-1.2%, -0.2%] 🔵 -0.0% [-0.2%, +0.2%]
BoidFlockers large 🔵 -0.1% [-0.8%, +0.7%] 🔵 +0.7% [+0.4%, +1.0%]

@quaquel
Copy link
Member

quaquel commented Feb 6, 2025

In your usage examples, the user has to convert stuff back into a dict. I suggest moving this to mesa side. From a user point of view, it makes more sense that they can return the AgentPortrayalStyle instance.

I am not sure about the property layer potrayal. This adds a PropertyLayerPortrayal and PropertyLayerPortrayalStyle. Personally, I am inclined to make it more analogous to how agent portrayal works so the user passes a callable. This callable will be called with the PropertyLayer as an argument and must return a PropertyLayerStyle instance. I am not sure this fully solves the problems I have run into, but at least from an API design perspective, it then works identically to how the plotting of agents works.

@Sahil-Chhoker
Copy link
Collaborator Author

I first saw this feature as something new that wouldn’t interfere with the current API design. But looking back at the discussion, it was meant to be the first step toward a full API redesign, and this PR doesn’t really help move things in that direction.

I am inclined to make it more analogous to how agent portrayal works so the user passes a callable. This callable will be called with the PropertyLayer as an argument and must return a PropertyLayerStyle instance.

Looking at your implementation, it will change the fundamental method used to draw property layers (draw_property_layers) drastically. Since that's the case, I see no reason to keep this PR open. I'll be looking forward to your implementation.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants