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

Visualizing Network Models with More Agents than Nodes #2691

Open
nicolas-starck opened this issue Feb 15, 2025 · 14 comments
Open

Visualizing Network Models with More Agents than Nodes #2691

nicolas-starck opened this issue Feb 15, 2025 · 14 comments

Comments

@nicolas-starck
Copy link

Describe the bug
When trying to visualize a network-based model, the draw_network function raises an error. I was working on making the error reproducible with the Virus on Network example code and noticed that the Stable and Latest versions use two different methods of creating the Network. The latest version seemed to tolerate visualizing the model with more agents than nodes (assuming the cell capacity was large enough). After adapting my code it visualizes the model with some agents greater than the number of nodes but fails when other agents are introduced, so it does not seem like it is strictly a function of the number of agents.

Image

Expected behavior
The draw_network code uses the agent_portrayal function to visualize the various type and number of agents in each node of the network.

To Reproduce
I haven't been able to reproduce the error with the VoN example.

Additional context
I am using Mesa Version 3.1.0 in JupyterLab on MacOS Sequoia

@quaquel
Copy link
Member

quaquel commented Feb 15, 2025

The latest version of the model uses the new style mesa.discrete_space.Network. I am surprised about the difference in behavior between this and the old style mesa.space.NetworkGrid.

The offending code is in mesa.visualization.mpl_space_drawing.draw_network

    # gather agent data
    s_default = (180 / max(width, height)) ** 2
    arguments = collect_agent_data(space, agent_portrayal, size=s_default)

    # this assumes that nodes are identified by an integer
    # which is true for default nx graphs but might user changeable
    pos = np.asarray(list(pos.values()))
    arguments["loc"] = pos[arguments["loc"]]

What is needed here is to have a more intelligent way of mapping from arguments["loc"] to the node position created by the networkx layouting (pos). As far as I can tell, based on quickly checking the code, both Network and NetworkGrid will have as arguments["loc"] the node id, so making a better mapping should be quite straightforward.

And #2593 is not the way to cleanly fix this.

@nicolas-starck
Copy link
Author

I was on 3.1.0 and just updated to 3.1.4. On both versions I am getting an error trying to load mesa.discrete_space.Network. instead I was using mesa.experimental.cell_space.Network

@quaquel
Copy link
Member

quaquel commented Feb 15, 2025

Yes, I am referring to the master branch here on Git Hub. For the future 3.2 release, cell_spaces are being stablized, moved, and renamed frommesa.experimental.cell_space to mesa.discrete_space.

@quaquel
Copy link
Member

quaquel commented Feb 15, 2025

So, replacing the snippet I showed with

    # this assumes that nodes are identified by an integer
    # which is true for default nx graphs but might user changeable
    agent_nodes = arguments["loc"]
    agent_position = [pos[node] for node in agent_nodes]
    arguments["loc"] = np.asarray(agent_position)

Should be sufficient. I will do some proper testing tomorrow.

@nicolas-starck
Copy link
Author

nicolas-starck commented Feb 15, 2025

Thanks for your help! I updated that section of mpl_space_drawing.py but _scatter still raised the same index error.

Oddly, I have noticed that I am only getting the error when including the second set of agents (commented out in the screenshot below). They are both versions of the same base class (modeled after the wolf/sheep/animal example). I can add the DCO agents, but as soon as I add the OCO agents the error is raised (even if they are the only dynamic agents added).

Image

@quaquel
Copy link
Member

quaquel commented Feb 15, 2025

What is your agent_portrayal function?

@nicolas-starck
Copy link
Author

def agent_portrayal(agent):
    portrayal = {
        "size": 25,
    }
    machine_color_dict = {
        "A": 'xkcd:light blue',
        "B": 'xkcd:pale red',
        "I": 'xkcd:light gray',
    }
    team_color_dict = {"A": 'xkcd:blue',"B": 'xkcd:red',}

    if isinstance(agent, OCO):
        portrayal["zorder"] = 3
        portrayal["color"] = team_color_dict[agent.nation]
        portrayal["marker"] = "D"
        portrayal["edgecolors"] = "black"
    elif isinstance(agent, DCO):
        portrayal["zorder"] = 2
        portrayal["color"] = team_color_dict[agent.nation]
        portrayal["marker"] = "o"
        portrayal["size"] = 25
    elif isinstance(agent, Machine):
        portrayal["zorder"] = 1
        portrayal["color"] = machine_color_dict[agent.nation]
        portrayal["marker"] = "s"
        portrayal["size"] = 1
    return portrayal

@quaquel
Copy link
Member

quaquel commented Feb 15, 2025

I need to some testing. It could be that the error you reported is not unique to networks, but due to having to handle marker and zorder at the same time.

@nicolas-starck
Copy link
Author

Got it, I appreciate your help. The zorder was my attempt to visualize multiple agents on the same cell, is there another (or better) way to achieve that effect? Perhaps add an x-y offset by agent type?

@quaquel
Copy link
Member

quaquel commented Feb 15, 2025

At the moment there is not, unless you write your own fully custom function for drawing space.

What you could test is to have only the marker or only the zorder and see if that still gives the error.

@nicolas-starck
Copy link
Author

Yes, it appears to still throw the error with only marker or zorder per agent in the agent_portrayal function.

@quaquel
Copy link
Member

quaquel commented Feb 16, 2025

I cannot reproduce your error. My test code is the following

class MyAgent(CellAgent):
    def __init__(self, model, cell, batch):
        super().__init__(model)
        self.cell = cell
        self.batch = batch

num_nodes = 10
avg_node_degree = 3

model = Model(seed=42)

prob = avg_node_degree / num_nodes
graph = nx.erdos_renyi_graph(n=num_nodes, p=prob)
grid = Network(graph, capacity=2, random=model.random)

cell = grid.all_cells.select_random_cell()

MyAgent.create_agents(model, num_nodes, list(grid.all_cells), 1)
MyAgent.create_agents(model, num_nodes, list(grid.all_cells), 2)


def portray_agent(agent):
    return {"color":"tab:blue" if (agent.batch %2==0) else "tab:orange", 
            "size":25,
            "zorder":1 if (agent.batch %2==1) else 0, 
            "marker":'o' if (agent.batch %2==0) else 'D', }

draw_network(grid, portray_agent)
plt.show()

I create 2 sets of agents, each equal, in my case, to the number of cells. Agent portrayal uses both marker and zorder, tied to whether it is an agent in the first or the second batch. It all visualizes normally (but on top of each other). I don't get the error you are getting.

Any idea, looking at your own code where I differ from what you are trying to do?

@quaquel
Copy link
Member

quaquel commented Feb 16, 2025

I have found the issue: you have portrayal["edgecolors"] = "black".

You only specify this for 1 agent class (OCO), not for any of the others. This causes the error. The content of the return from agent portrayal must contain the same field for all agents.

Whether this is desirable is a different question.

@nicolas-starck
Copy link
Author

Yes, I was wondering why the one agent class would cause the issue. Adding edgecolors for all (or removing from all) appears to resolve the issue. Thanks for your help!

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

No branches or pull requests

2 participants