NetworkX vs ArcPy UN Trace — When to Use Each
Every upstream or downstream trace in a utility network eventually runs on one of two engines: the ArcGIS Utility Network trace API exposed through arcpy.un.Trace, or an in-memory directed graph traversed with NetworkX. They are not interchangeable, and choosing wrongly is expensive in both directions — a NetworkX prototype quietly promoted into an operational shutoff tool can miss a terminal-level barrier and de-energize the wrong span, while forcing every nightly analytic through the licensed trace API turns a two-second graph walk into a lock-contending, seat-consuming batch job. The two engines answer subtly different questions: arcpy.un.Trace answers what does the authoritative Utility Network, with its published connectivity rules and terminal configurations, say is connected right now?, and NetworkX answers what does this exported directed graph say is reachable under the barrier attributes I chose to carry? This comparison, which sits under the upstream and downstream tracing algorithms guide and the broader topology and tracing workflows discipline, gives engineers a decision matrix, benchmarks, and two runnable reference traces so the choice is made on evidence rather than habit. Both engines depend on the barrier semantics established in valve and isolator mapping strategies, and both ultimately feed the same downstream consumers — including impact analysis and affected-customer tracing.
Environment Prerequisites
The comparison only holds if both engines run against the same validated topology. Lock the following before benchmarking or trusting either result:
- ArcGIS Pro 3.2+ with a Standard or Advanced license and the
arcpymodule importable from the target Python 3.11 interpreter —arcpy.un.Tracerequires an active utility network and a validated topology, not merely a network dataset. - A validated Utility Network with configured terminal configurations, published connectivity rules, and defined subnetworks. Never benchmark a trace against a dirty-area backlog; an unvalidated network produces results that neither engine can defend.
- networkx 3.x and geopandas 1.x pinned in an isolated conda environment, with the graph exported from a reconciled, unversioned snapshot so the NetworkX side reflects committed geometry rather than an open edit session.
- A shared export contract — the same from-node, to-node, subtype, terminal, and barrier attributes carried out of the geodatabase into the graph, so a NetworkX traversal can reproduce the barrier logic the trace engine applies natively.
- A certification network — a small, hand-verified feeder or pressure zone whose correct upstream and downstream sets are known, used to prove parity between the two engines before either is trusted at scale.
Selection Protocol — Score the Task Before You Commit
Most engine-choice mistakes come from picking on familiarity, not on task shape. Work this ordered protocol; the first question decides the majority of cases.
- Is the output an operational decision governed by Utility Network rules? If the trace drives a shutoff, a switching order, or a regulator-facing affected-customer count, rule and terminal fidelity is non-negotiable and
arcpy.un.Traceis the default. A graph you assembled yourself is only as correct as the barrier attributes you remembered to export. - Does correctness depend on terminal-level connectivity? Multi-terminal devices — a three-phase sectionalizing switch, a valve with distinct inlet and outlet terminals — connect selectively across terminals. The trace API models this natively; a plain NetworkX node collapses the device to a single vertex and can report continuity the real device does not permit unless you model terminals explicitly as split nodes.
- Must the trace run offline, cross-platform, or inside CI? If the answer runs on a build agent with no ArcGIS license, in a container, or on a developer laptop, NetworkX is the only option that ships. This is the decisive factor for validation tooling and analytics.
- What is the call volume and latency budget? A single interactive trace favors the API’s native optimization; tens of thousands of repeated traversals over a static snapshot favor an in-memory graph that never touches a geodatabase lock.
- Can you prove parity? Whichever engine you lean toward, run both against the certification network and quantify divergence before production. An unexplained mismatch is a defect in your export contract, not a rounding difference.
Decision Matrix and Two Reference Implementations
The matrix below scores the two engines across the dimensions that actually move a production decision. Read it as per task, not per organization — most teams run both, using the trace API as the authoritative operational engine and NetworkX as the portable analytic and validation engine.
| Dimension | NetworkX (in-memory DiGraph) | ArcPy UN Trace (arcpy.un.Trace) | Reach for |
|---|---|---|---|
| Fidelity to UN connectivity rules | None inherent — the graph knows only the edges and attributes you exported; rule compliance is your responsibility | Native — evaluates published connectivity rules, associations, and subnetwork definitions directly against the validated network | ArcPy for any authoritative result |
| Barriers & terminal support | Barriers are node/edge attribute filters you implement; multi-terminal devices need explicit split-node modeling | First-class category, network-attribute, and function barriers; terminal-aware traversal across configured terminals | ArcPy when terminals decide connectivity |
| Portability / offline / cross-platform | Pure Python — runs in any container, CI agent, or laptop with no license or ArcGIS install | Requires ArcGIS Pro, an active license, and a validated utility network in reach | NetworkX for offline and CI |
| Performance at repeat volume | Trace is an in-memory descendants/ancestors walk in microseconds once the graph is loaded; load cost amortizes over many traces |
Optimized single-trace execution, but each call touches the geodatabase and can contend for version locks under batch load | NetworkX for high-volume static analytics |
| Licensing & cost | Free, open-source (BSD); no per-seat or per-core cost | Consumes an ArcGIS Pro seat and Standard/Advanced entitlement per running process | NetworkX where licenses are scarce |
| CI / testability / determinism | Trivially unit-testable with synthetic graphs; fully deterministic and mockable | Testable only where a licensed network is reachable; harder to fixture in a pipeline | NetworkX for gated validation |
| Data currency / system of record | Only as current as the last export; risks staleness against live device states | Reads the live, validated network — the authoritative current configuration | ArcPy when currency is safety-critical |
| Custom graph algorithms | Full algorithm library — shortest path, centrality, connected components, flow | Fixed trace types (upstream, downstream, connected, isolation, subnetwork) | NetworkX for analytics beyond tracing |
The authoritative engine is arcpy.un.Trace. It evaluates the same connectivity rules, terminals, and barriers that the network enforces natively, so its downstream trace is the defensible answer for an operational decision. The snippet below runs an upstream trace from a starting point, applying a category barrier so traversal halts at operable devices flagged as open:
import arcpy
import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
def un_upstream_trace(un_layer: str, starting_point_fc: str,
barrier_category: str = "Open") -> dict:
"""Run an authoritative upstream trace over the Utility Network.
Applies a category barrier so traversal halts at open devices. Returns a
structured, pipeline-gateable summary of the traced result.
"""
try:
result = arcpy.un.Trace(
in_utility_network=un_layer,
trace_type="UPSTREAM",
starting_points=starting_point_fc,
barriers=None,
include_barriers="INCLUDE_BARRIERS",
condition_barriers=[["Device Status", "IS_EQUAL_TO", barrier_category, "#"]],
)
except arcpy.ExecuteError as exc:
logging.error("UN trace failed: %s", arcpy.GetMessages(2))
raise RuntimeError("arcpy.un.Trace did not complete") from exc
aggregated = result.getOutput(0) # aggregated geometry / selection
logging.info("Upstream trace completed against %s", un_layer)
return {
"engine": "arcpy.un.Trace",
"trace_type": "UPSTREAM",
"barrier_category": barrier_category,
"result_layer": aggregated,
"ok": True,
}
The portable engine expresses the same upstream question as a directed-graph walk. Barriers become a node filter, and terminal-level behavior — if it matters — must be modeled by splitting each multi-terminal device into per-terminal nodes during export. The traversal itself is a few microseconds once the graph is resident:
import networkx as nx
def nx_upstream_trace(graph: nx.DiGraph, start: str,
open_devices: set[str]) -> dict:
"""Reproduce an upstream trace as an ancestors walk over an exported graph.
Barriers are applied as a node filter: traversal never passes through a
device present in `open_devices`. Returns a structured, comparable summary.
"""
if start not in graph:
raise KeyError(f"start node {start!r} not in exported graph")
# Remove open devices so ancestors cannot traverse through a barrier.
live = graph.copy()
live.remove_nodes_from(open_devices & set(live.nodes))
upstream = nx.ancestors(live, start) if start in live else set()
return {
"engine": "networkx",
"trace_type": "UPSTREAM",
"start": start,
"upstream_nodes": sorted(upstream),
"count": len(upstream),
"ok": True,
}
if __name__ == "__main__":
g = nx.DiGraph()
g.add_edges_from([("SRC", "V1"), ("V1", "V2"), ("V2", "SVC"), ("V1", "OPEN_SW")])
print(nx_upstream_trace(g, "SVC", open_devices={"OPEN_SW"}))
Run both against the certification network and diff the node sets. On a hand-verified feeder the two must agree exactly; any divergence is a barrier or terminal attribute the export contract dropped, and it must be reconciled before either result is trusted. In practice the NetworkX walk finishes in well under a millisecond for a graph of a few thousand nodes, while a single arcpy.un.Trace call typically runs in the low hundreds of milliseconds including its geodatabase round trip — a gap that is irrelevant for one interactive trace and decisive across a nightly batch of fifty thousand.
Production Deployment Pattern
The mature pattern is not to pick one engine but to give each its lane, wired into the asset lifecycle:
- Make ArcPy UN Trace the authoritative operational path. Any trace that drives a shutoff, a switching order, or a customer-facing count runs
arcpy.un.Traceagainst the live validated network, so the result inherits the published connectivity rules and terminal configurations rather than a snapshot’s memory of them. - Make NetworkX the portable analytic and CI path. Export the directed graph on a reconciled snapshot and run graph analytics, regression traces, and validation suites on unlicensed build agents. This is where determinism and zero licensing cost pay off, and it keeps trace-logic tests green without an ArcGIS seat.
- Enforce a shared export contract. Version the from-node, to-node, subtype, terminal, and barrier fields the export carries, and treat a NetworkX-versus-ArcPy divergence on the certification network as a build-breaking defect. Parity is the proof that your graph faithfully represents the network.
- Apply retry and backoff to the API path. Enterprise geodatabase and version reads fail intermittently under batch load; wrap
arcpy.un.Tracein a bounded retry so a transient lock produces a retry rather than a false empty trace. - Log both engines to one audit schema. Record engine, trace type, starting point, barrier configuration, result count, and snapshot timestamp for every run, so a reliability or rate-case review can reconstruct which engine produced which answer and why.
Conclusion
NetworkX and ArcPy UN Trace are complementary, not competing. arcpy.un.Trace is the authoritative engine whenever connectivity rules, terminals, and live data currency decide correctness — which is every operational and regulator-facing trace. NetworkX is the portable, license-free, endlessly testable engine for offline analytics, high-volume batch traversals, and CI-gated validation, provided its export contract faithfully carries the barriers and terminals the network enforces. Score each task against the matrix, prove parity on a certification network, and let each engine own the lane it is built for. Done that way, the choice stops being a matter of preference and becomes a matter of evidence.
Related
- Up to the parent topic: Upstream & Downstream Tracing Algorithms
- Up to the section: Topology & Tracing Workflows
- Python Automation for Upstream Water Valve Tracing
- Valve & Isolator Mapping Strategies
- Batch Processing Topology Errors Using ArcPy and GeoPandas
For authoritative reference, consult the NetworkX documentation and the ArcGIS Pro utility network trace documentation.