projectmesa / mesa

Mesa is an open-source Python library for agent-based modeling, ideal for simulating complex systems and exploring emergent behaviors.
https://mesa.readthedocs.io
Apache License 2.0
2.55k stars 892 forks source link

Towards a MVC/MVVM architecture for better portrayals? #1441

Open wang-boyu opened 2 years ago

wang-boyu commented 2 years ago

What's the problem this feature will solve?

The current way of defining custom portrayals for agents is through a function, for example: https://github.com/projectmesa/mesa/blob/478a66b05e7a96ba86c7abe41c555a18a889fa54/examples/sugarscape_cg/sugarscape_cg/server.py#L9-L31

There are nested if-else statements checking for types and so on. To make things worse, in Mesa-Geo there're various types of layers (e.g., raster layer, vector layer), other than agents, that would also need custom portrayals.

Describe the solution you'd like

Perhaps something like what was done in MASON (a JAVA ABM framework)? In this paper the authors talked about their MVC architecture where there are corresponding portrayals for agents, fields, etc.

Additional context

rht commented 2 years ago

Also, consider that in Matplotlib, you don't have to specify your plot's "portrayal" by default. It's a line with a default linewidth, and a color that is chosen automatically.

rht commented 2 years ago

I looked at the portrayals in Mesa-Geo, the most complex ones are comparable to the Sugarscape CG portrayals.

It's actually not that bad if you make the portrayal function less verbose / more declarative

def SsAgent_portrayal(agent):
    if agent is None:
        return

    portrayal = None

    if type(agent) is SsAgent:
        portrayal = {
            "Shape": "sugarscape_cg/resources/ant.png",
            "scale": 0.9,
            "Layer": 1
        }

    elif type(agent) is Sugar:
        if agent.amount != 0:
            color = color_dic[agent.amount]
        else:
            color = "#D6F5D6"
        portrayal = {
            "Color": color,
            "Shape": "rect",
            "Filled": "true",
            "Layer": 0,
            "w": 1,
            "h": 1,
        }

    return portrayal

The if agent.amount != 0 is not necessary. But for illustration purpose, people are bound to need to do this sort of if anyway. Having to specify each view class for each agent types would be more verbose than this nested ifs.

Alternatively, we can specify the portrayal as a dict

ssAgentPortrayal = {
    SsAgent: lambda agent: {
        "Shape": "sugarscape_cg/resources/ant.png",
        "scale": 0.9,
        "Layer": 1
    },
    Sugar: lambda agent: {
        "Color": color_dic[agent.amount] if agent.amount != 0 else "#D6F5D6",
        "Shape": "rect",
        "Filled": "true",
        "Layer": 0,
        "w": 1,
        "h": 1,
    }
}