Modeling Parallel Routes in NetworkX with MultiDiGraph
Learn how to use NetworkX MultiDiGraph to model transportation networks with parallel edges, ensuring you don't lose critical route data like travel time and cost.
31 May 2026, 17:42 UTC

The Problem: When One Edge Isn't Enough
In standard graph theory, an edge represents a connection between two nodes. For many applications, this is sufficient. However, real-world infrastructure—like city transit systems—rarely works this way. Between two subway stations, there might be an express line, a local line, and a bus route. Each has a different travel time, capacity, and cost.
If you use a standard DiGraph (Directed Graph), adding a second edge between Station A and Station B simply overwrites the first. You lose the express line the moment you add the local line. To model these parallel connections without losing data, you need a MultiDiGraph.
Understanding the MultiDiGraph Structure
A MultiDiGraph allows multiple directed edges between the same pair of nodes. While a DiGraph stores edges as {node_u: {node_v: attributes}}, a MultiDiGraph introduces an intermediate key: {node_u: {node_v: {key: attributes}}}.
This structure means that every edge is uniquely identified by a triplet: the source node, the target node, and an edge key. If you don't provide a key when adding an edge, NetworkX automatically assigns an integer (0, 1, 2, etc.).
API Consistency
The transition from DiGraph to MultiDiGraph is lightweight. Most method calls remain identical, but when you retrieve edge data, you must account for the keys. Accessing G['A']['B'] in a MultiDiGraph returns a dictionary of all edges between those nodes, rather than a single attribute dictionary.
Implementation: Modeling a Transit Segment
The following example demonstrates how to define parallel routes between two hubs and calculate the fastest path among them. This requires NetworkX 3.2 or newer.
import networkx as nx
# Initialize a MultiDiGraph
G = nx.MultiDiGraph()
# Define stations
G.add_node("Central", pos=(0, 0))
G.add_node("Airport", pos=(1, 1))
# Add parallel edges with different attributes
# Edge 1: Local Train
G.add_edge("Central", "Airport", key="local", time=45, cost=2)
# Edge 2: Express Train
G.add_edge("Central", "Airport", key="express", time=20, cost=10)
# Edge 3: Bus
G.add_edge("Central", "Airport", key="bus", time=60, cost=1)
# Verification: Check all edges between the two nodes
# Run this in your local Python environment
print("Edges from Central to Airport:")
for key, data in G["Central"]["Airport"].items():
print(f"Route {key}: {data}")
# Calculate the shortest path based on travel time
# NetworkX algorithms automatically evaluate all parallel edges
fastest_time = nx.shortest_path_length(G, source="Central", target="Airport", weight="time")
print(f"\nFastest travel time: {fastest_time} minutes")Execution Details
- Environment: Run in a Python 3.x environment with
pip install networkx==3.2. - Permissions: No special system permissions required; standard user privileges are sufficient.
- Expected Result: The output should list three distinct routes (local, express, bus) and return 20 as the fastest travel time.
Trade-offs and Technical Limitations
While MultiDiGraph provides the necessary flexibility for transportation modeling, it introduces specific overheads that engineers must manage.
Memory Overhead
Because each parallel edge maintains its own attribute dictionary, memory usage scales linearly with the number of edges, not the number of unique node pairs. In massive networks with hundreds of parallel options per node, this can lead to significant RAM consumption compared to a simple graph where attributes might be aggregated.
Metric Inflation
Be cautious when using centrality measures. Algorithms that calculate the degree of a node (the number of connections) will count every parallel edge. In the example above, "Central" has a degree of 3, even though it only connects to one other location. If your analysis requires the number of unique destinations, you must convert the graph to a simple DiGraph or filter the edges manually.
Visualization Clutter
Standard drawing functions like nx.draw_networkx often render parallel edges directly on top of one another, making them invisible. To differentiate them, you must use connectionstyle attributes or separate the graph into subgraphs based on edge keys.
Verification and Result Checking
To verify that your MultiDiGraph is behaving as expected, use the G.edges(data=True, keys=True) method. This returns a list of tuples in the format (u, v, key, data). If the key element is missing or if you only see one entry for a node pair that should have several, you have likely instantiated a DiGraph instead of a MultiDiGraph.
Warning: Converting a MultiDiGraph back to a DiGraph via nx.DiGraph(G) is a lossy operation. NetworkX will collapse all parallel edges into one, keeping only the attributes of the last edge processed. Always perform this conversion on a copy of the data if the original route details are needed for later analysis.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.