Network Visualization error when Agent Portrayal has inconsistent fields
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.
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
Updated description - please check
Description
When trying to visualize a network-based model with the draw_network function, I encountered an error. Initially, I thought it was related to having more agents than nodes, but through investigation with the help of @quaquel, we discovered that the actual issue was in the agent portrayal function.
The visualization fails when one agent type has fields in its portrayal that other agent types don't have. In my specific case, I was using "edgecolors": "black" for one agent type (OCO) but not for others, which caused the error.
To Reproduce
- Create a network model with multiple agent types
- Create an agent portrayal function where one agent type returns a different set of visual properties than others
- Try to visualize the model using
draw_network
Expected Behavior
Either:
- The visualization should handle inconsistent portrayal fields gracefully, or
- There should be clearer error messages indicating that all agent types must have the same portrayal fields
Solution
The issue is resolved by ensuring that all agent types return the same set of fields in the agent portrayal function, even if the values for those fields differ.
Additional Context
This issue affects both the stable and development versions of Mesa. I was using Mesa Version 3.1.0 in JupyterLab on MacOS Sequoia.
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.
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
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.
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.
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).
What is your agent_portrayal function?
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
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.
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?
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.
Yes, it appears to still throw the error with only marker or zorder per agent in the agent_portrayal function.
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?
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.
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!
Hey @nicolas-starck ,@quaquel, I would like to contribute to this issue. Could you assign it to me?
- Multiple agents are visualized just fine atm, in contrast to the title of this issue.
- The root cause of this issue instead was about needing to pass the same visual encoding for all agents in your model
- At the moment, multiple agents on the same cell are visualized on top of each other (this behavior is consistent across all spaces and the matplotlib visualization).
- #2693 also aims to address the issue of agents being visualized on top of each other, but as can be seen there, I am skeptical about the approach chosen there.
@nicolas-starck @quaquel I updated the issue title and added a updated description. Could you please check if they correctly and completely cover the actual issue at hand?
Might also have potential for a PyCon spint.