Skip to content

NetworkX Integration ​

This guide covers converting PyPtP networks to NetworkX graphs for analysis, visualization, and export to other formats.

Full Example

View the complete code: 07_NetworkX.py

Overview ​

Converting to NetworkX lets you:

  • Analyze topology (connectivity, paths, cycles)
  • Detect isolated sections
  • Visualize networks with matplotlib
  • Export to standard graph formats (GraphML, GEXF, JSON)
  • Apply graph algorithms (shortest path, centrality, clustering)

Converting Networks ​

Balanced Networks (VNF/Vision) ​

python
from pyptp.graph.networkx_converter import NetworkxConverter
from pyptp.IO.importers.vnf_importer import VnfImporter

# Load balanced network
vnf_importer = VnfImporter()
mv_network = vnf_importer.import_vnf("network.vnf")

# Convert to NetworkX graph
mv_graph = NetworkxConverter.graph_mv(mv_network)

Unbalanced Networks (GNF/Gaia) ​

python
from pyptp.IO.importers.gnf_importer import GnfImporter

# Load unbalanced network
gnf_importer = GnfImporter()
lv_network = gnf_importer.import_gnf("network.gnf")

# Convert to NetworkX graph
lv_graph = NetworkxConverter.graph_lv(lv_network)

Using the High-Level API ​

python
from pyptp import NetworkMV, NetworkLV

# Alternative: use the Network classes directly
mv_network = NetworkMV.from_file("network.vnf")  # Balanced model
lv_network = NetworkLV.from_file("network.gnf")  # Unbalanced model

# Then convert
mv_graph = NetworkxConverter.graph_mv(mv_network)
lv_graph = NetworkxConverter.graph_lv(lv_network)

Graph Structure ​

Every electrical object becomes a graph node, keyed by str(guid) (lowercase, without braces), with a type attribute holding the element class name. Sheets, texts and other drawing objects are not in the graph.

Graph Node TypePyPtP Element
Network nodeBusbar, junction
BranchCable, link, line, transformer, special transformer, three-winding transformer, reactance coil
ElementLoad, source, generator, battery, PV, etc.
SecondaryFuse, load switch, circuit breaker, measure field

Edges follow the electrical connection path from a network node to the object connected to it:

  • A branch side runs node - secondary - ... - branch. Secondaries on that side sit in series between the node and the branch, grouped by kind: first the fuses, then the load switches, circuit breakers and measure fields. Within a kind they keep the order in which they were added to the network. A side without secondaries connects the node to the branch directly.
  • An element runs node - secondary - ... - element in the same way.
  • A secondary always keeps its edge towards the branch or element it belongs to.
  • The edge towards the node exists only while that side is closed. An open side leaves the branch or element, together with its secondaries, as a separate fragment. A branch with both sides open is an isolated node, or an isolated fragment together with its secondaries.
  • A secondary that belongs to an object missing from the network stays in the graph without edges, and a warning is logged.
python
# Example graph structure for a simple network:
# NetworkNode1 ─── Fuse ─── Cable ─── NetworkNode2
#                                        │
#                                      Load

# Becomes this graph:
# Nodes: [NetworkNode1, Fuse, Cable, NetworkNode2, Load]
# Edges: [NetworkNode1-Fuse, Fuse-Cable, Cable-NetworkNode2, NetworkNode2-Load]

# Opening the cable at NetworkNode1 removes only the NetworkNode1-Fuse edge:
# Edges: [Fuse-Cable, Cable-NetworkNode2, NetworkNode2-Load]

A side counts as closed when at least one of its phase or auxiliary conductor switches is closed. Neutral and PE switches do not connect a side on their own. Balanced networks have one switch per side, unbalanced networks have one per conductor.

Branches answer the same question with side_closed(), and set_switches() opens or closes a side:

python
from pyptp.elements.element_utils import SIDE_NODE1

cable.general.side_closed(SIDE_NODE1)              # True while the graph has the node 1 edge
cable.general.set_switches(SIDE_NODE1, closed=False)
cable.general.switches_open()                      # True when neither side is closed

Ignoring Switch States ​

Pass respect_switch_states=False to connect every side regardless of its switch states, for example to analyse the topology as built rather than as operated:

python
import networkx as nx

as_built = NetworkxConverter.graph_mv(mv_network, respect_switch_states=False)
as_operated = NetworkxConverter.graph_mv(mv_network)

# Sections that differ between as operated and as built
operated_sections = {frozenset(section) for section in nx.connected_components(as_operated)}
built_sections = {frozenset(section) for section in nx.connected_components(as_built)}
changed = operated_sections - built_sections

This design makes it easy to:

  • Find all elements connected to a network node
  • Trace paths through the network, including the switchgear on the way
  • Identify equipment between any two points
python
print(f"MV Network: {mv_graph.number_of_nodes()} nodes, {mv_graph.number_of_edges()} edges")
print(f"LV Network: {lv_graph.number_of_nodes()} nodes, {lv_graph.number_of_edges()} edges")

Network Analysis ​

Connectivity Check ​

python
import networkx as nx

# Check if entire network is connected
is_connected = nx.is_connected(mv_graph)
print(f"Network is connected: {is_connected}")

Find Disconnected Sections ​

python
# Get all connected components
components = list(nx.connected_components(mv_graph))
print(f"Number of separate sections: {len(components)}")

# Largest component
largest = max(components, key=len)
print(f"Largest section has {len(largest)} nodes")

Path Analysis ​

python
# Find shortest path between two nodes
source_node = "node_guid_1"
target_node = "node_guid_2"

if nx.has_path(mv_graph, source_node, target_node):
    path = nx.shortest_path(mv_graph, source_node, target_node)
    print(f"Path length: {len(path)} nodes")
else:
    print("No path exists between nodes")

Network Statistics ​

python
# Degree distribution (connections per node)
degrees = dict(mv_graph.degree())
avg_degree = sum(degrees.values()) / len(degrees)
print(f"Average connections per node: {avg_degree:.2f}")

# Find nodes with most connections
max_degree_node = max(degrees, key=degrees.get)
print(f"Most connected node: {max_degree_node} ({degrees[max_degree_node]} connections)")

# Network diameter (longest shortest path)
if nx.is_connected(mv_graph):
    diameter = nx.diameter(mv_graph)
    print(f"Network diameter: {diameter}")

Querying by Element Type ​

Each graph node stores its element type:

python
# Get element type for a node
node_type = mv_graph.nodes[node_guid].get('type')

# Find all cables
cables = [n for n, d in mv_graph.nodes(data=True) if d.get('type') == 'CableMV']

# Find all transformers
transformers = [n for n, d in mv_graph.nodes(data=True)
                if d.get('type') in ('TransformerMV', 'ThreewindingTransformerMV')]

# Count elements by type
from collections import Counter
type_counts = Counter(d.get('type') for n, d in mv_graph.nodes(data=True))
for element_type, count in type_counts.most_common():
    print(f"{element_type}: {count}")

Cycle Detection ​

python
# Find all cycles (loops in network)
cycles = nx.cycle_basis(mv_graph)
print(f"Number of loops: {len(cycles)}")

# Networks with loops may need special handling for load flow

Visualization ​

Basic Plot ​

python
import matplotlib.pyplot as plt
import networkx as nx

plt.figure(figsize=(12, 8))
nx.draw(mv_graph, with_labels=True, node_size=300, font_size=8)
plt.title("Network Topology")
plt.savefig("network_graph.png", dpi=150)
plt.show()

Custom Layout ​

python
# Spring layout (force-directed)
pos = nx.spring_layout(mv_graph, k=2, iterations=50)

# Or use spectral layout for cleaner visualization
pos = nx.spectral_layout(mv_graph)

nx.draw(mv_graph, pos, with_labels=True)

Export Formats ​

NetworkX supports many export formats for interoperability:

GraphML (XML-based) ​

python
nx.write_graphml(mv_graph, "network.graphml")

GEXF (Gephi format) ​

python
nx.write_gexf(mv_graph, "network.gexf")

JSON (Web-friendly) ​

python
from networkx.readwrite import json_graph
import json

data = json_graph.node_link_data(mv_graph)
with open("network.json", "w") as f:
    json.dump(data, f, indent=2)

Adjacency List ​

python
nx.write_adjlist(mv_graph, "network.adjlist")

Complete Example ​

python
"""NetworkX Usage Examples.

Demonstrates how to convert electrical networks to NetworkX graphs
for analysis and visualization.

Graph Structure:
- Every network object (nodes, branches, elements, secondaries) becomes a graph NODE
- Edges follow the connection path: node - secondaries - branch/element
- An open side has no edge towards its node (pass respect_switch_states=False to ignore this)
- Each graph node has a 'type' attribute with the element class name
"""

from pyptp.graph.networkx_converter import NetworkxConverter
from pyptp.IO.importers.gnf_importer import GnfImporter
from pyptp.IO.importers.vnf_importer import VnfImporter

# Convert MV network to NetworkX graph
vnf_importer = VnfImporter()
mv_network = vnf_importer.import_vnf("PATH_TO_VNF")
mv_graph = NetworkxConverter.graph_mv(mv_network)

# Convert LV network to NetworkX graph
gnf_importer = GnfImporter()
lv_network = gnf_importer.import_gnf("PATH_TO_GNF")
lv_graph = NetworkxConverter.graph_lv(lv_network)

# Basic statistics
print(f"MV Network: {mv_graph.number_of_nodes()} nodes, {mv_graph.number_of_edges()} edges")
print(f"LV Network: {lv_graph.number_of_nodes()} nodes, {lv_graph.number_of_edges()} edges")

# Example: Use NetworkX algorithms
import networkx as nx

# Check if network is connected
is_connected = nx.is_connected(mv_graph)
print(f"Network is connected: {is_connected}")

# Query element types from the graph
# Each node has a 'type' attribute with the element class name
for node, attrs in mv_graph.nodes(data=True):
    print(f"Node {node}: type={attrs.get('type')}")

# Find all nodes of a specific type
cables = [n for n, d in mv_graph.nodes(data=True) if d.get('type') == 'CableMV']
print(f"Found {len(cables)} cables")

# Find shortest path between nodes
# path = nx.shortest_path(mv_graph, source_node, target_node)

# Export to various NetworkX formats
# nx.write_gexf(mv_graph, "network.gexf")
# nx.write_graphml(mv_graph, "network.graphml")