Skip to content
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

Feature: Portrayal components for agents and property layers #2661

Conversation

Sahil-Chhoker
Copy link
Contributor

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
Contributor 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.

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

Successfully merging this pull request may close these issues.

2 participants