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

Mesa NetworkGrid Requires 0-indexed nodes and can't take non-numeric node names #1433

Open
clarkpetri opened this issue Oct 1, 2022 · 3 comments

Comments

@clarkpetri
Copy link

Describe the bug
Mesa fails to run a model, without providing an error, when non-zero indexed network structures are provided as the NetworkGrid object. Additionally, this occurs if a zero indexed networkx object later has a node with a non-numeric name added.

Expected behavior
The model should run and visualize the graph object with arbitrary node names with the same ease in which a NetworkX object can be initiated as such

To Reproduce
This discussion explains everything.

Additional context
Occurred on an M1 MBP running Python 3.10.2

NetworkX can handle a simple example such as:
G = nx.Graph()

edges = [(0,1),(1,2),(2, 0),(2,'a')]

G.add_edges_from(edges)

nx.draw(G, with_labels=True)

But the presence of the (2,'a') edge will cause the bug in Mesa. Removing that edge causes Mesa to initiate properly.

@rht
Copy link
Contributor

rht commented Oct 5, 2022

The underlying cause is that Python's NetworkX allows node ID to be any hashable object (int, str, tuple, etc), but JavaScript's D3.js expects nodes to be identified by their order in the node list. There is no way around this (see https://stackoverflow.com/questions/23986466/d3-force-layout-linking-nodes-by-name-instead-of-index) than to write a mapper from NetworkX node ID to a node index number (that MUST start from 0).

It might be possible to implement the mapper inside Mesa so that it is automatic. I'm currently looking into it.

But for a temporary solution, the network_portrayal code of the virus on a network example can be modified to

    nodes = []
    node_label_to_node_id = {}
    node_id = 0
    for node_label, agents in G.nodes.data("agent"):
        agent = agents[0]
        nodes.append({
            "size": 6,
            "color": node_color(agent),
            "tooltip": f"id: {agent.unique_id}<br>state: {agent.state.name}",
        })
        node_label_to_node_id[node_label] = node_id
        node_id += 1

    portrayal = dict()
    portrayal["nodes"] = nodes

    portrayal["edges"] = [
        {
            "source": node_label_to_node_id[source],
            "target": node_label_to_node_id[target],
            "color": edge_color(*get_agents(source, target)),
            "width": edge_width(*get_agents(source, target)),
        }
        for (source, target) in G.edges
    ]

@clarkpetri
Copy link
Author

Thank as always for such a comprehensive explanation. I hadn't considered the JavaScript limitation. Knowing that NetworkX takes any washable object and that Mesa is using those tools, the limitation seemed elusive.

I'll implement this code soon and report back.

@clarkpetri
Copy link
Author

Implemented, works, and clever solution. I can maintain an external dictionary relating the enumerated IDs to original names.

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