Quick Start#

This guide demonstrates a complete NPAP workflow using a voltage-aware (VA) power network. By the end, you’ll understand how to load data, partition a network, aggregate it, and visualize the results.

The NPAP Workflow#

        flowchart LR
    A[Load Data] --> B[Aggregate Parallel Edges]
    B --> C[Group by Voltage]
    C --> D[Partition]
    D --> E[Aggregate]
    E --> F[Visualize]

    style A fill:#2993B5,stroke:#1d6f8a,color:#fff
    style B fill:#0fad6b,stroke:#076b3f,stroke-dasharray: 5 5,color:#fff
    style C fill:#0fad6b,stroke:#076b3f,stroke-dasharray: 5 5,color:#fff
    style D fill:#2993B5,stroke:#1d6f8a,color:#fff
    style E fill:#2993B5,stroke:#1d6f8a,color:#fff
    style F fill:#2993B5,stroke:#1d6f8a,color:#fff
    

Dashed boxes indicate optional steps

Complete Example: Voltage-Aware Network#

This example shows the full workflow for a multi-voltage power system with transmission lines, transformers, and DC links.

import npap
from npap import AggregationMode
import networkx as nx

# =============================================================================
# Step 1: Initialize the Manager
# =============================================================================

manager = npap.PartitionAggregatorManager()

# =============================================================================
# Step 2: Load Voltage-Aware Data
# =============================================================================

# The VA loader handles multi-voltage networks with transformers and DC links
graph = manager.load_data(
    strategy="va_loader",
    node_file="buses.csv",           # Bus/substation data
    line_file="lines.csv",           # AC transmission lines
    transformer_file="transformers.csv",  # Transformers between voltage levels
    converter_file="converters.csv", # AC/DC converters
    link_file="dc_links.csv"         # HVDC links
)

print(f"Loaded network: {graph.number_of_nodes()} nodes, {graph.number_of_edges()} edges")

# =============================================================================
# Step 3: Aggregate Parallel Edges (if present)
# =============================================================================

# Power networks often have parallel transmission lines
if isinstance(graph, nx.MultiDiGraph):
    manager.aggregate_parallel_edges(
        edge_properties={
            "x": "equivalent_reactance",  # Parallel reactance formula: 1/(1/x1 + 1/x2)
            "r": "equivalent_reactance",  # Same for resistance
            "p_max": "sum",               # Sum the capacities
        },
        default_strategy="average",
        warn_on_defaults=False
    )
    print(f"After parallel edge aggregation: {manager.get_current_graph().number_of_edges()} edges")

# =============================================================================
# Step 4: Partition the Network
# =============================================================================

# VA-geographical partitioning respects voltage levels and AC islands
partition_result = manager.partition(
    strategy="va_geographical_kmedoids_haversine",
    n_clusters=50
)

print(f"Created {partition_result.n_clusters} clusters")
print(f"Strategy used: {partition_result.strategy_name}")

# Inspect cluster assignments
for cluster_id, nodes in list(partition_result.mapping.items())[:3]:
    print(f"  Cluster {cluster_id}: {len(nodes)} nodes")

# =============================================================================
# Step 5: Visualize Partitioned Network
# =============================================================================

# Plot the network colored by cluster assignment
manager.plot_network(
    style="clustered",
    title="Partitioned VA Network"
)

# =============================================================================
# Step 6: Aggregate the Network
# =============================================================================

# Aggregate using the GEOGRAPHICAL mode (averages coordinates, sums loads)
aggregated_graph = manager.aggregate(mode=AggregationMode.GEOGRAPHICAL)

print(f"Aggregated network: {aggregated_graph.number_of_nodes()} nodes, "
      f"{aggregated_graph.number_of_edges()} edges")

# =============================================================================
# Step 7: Visualize Aggregated Network
# =============================================================================

manager.plot_network(
    graph=aggregated_graph,
    style="voltage_aware",
    title="Aggregated Network"
)

Understanding the Data Files#

buses.csv (Nodes)#

id,lat,lon,voltage,p_load,q_load
bus_001,47.0667,15.4333,380,100.0,30.0
bus_002,47.1234,15.5678,380,150.0,45.0
bus_003,48.2089,16.3726,220,200.0,60.0

lines.csv (AC Transmission Lines)#

from,to,x,r,p_max,length
bus_001,bus_002,0.0123,0.0045,500,125.5
bus_002,bus_004,0.0234,0.0089,400,98.2

transformers.csv#

from,to,x,r,s_nom,tap_ratio
bus_002,bus_003,0.05,0.01,400,1.0

converters.csv (Optional)#

id,bus,p_max
conv_1,bus_010,1000
conv_2,bus_020,1000

Simple Example: NetworkX Graph#

For simpler use cases without CSV files:

import npap
import networkx as nx

# Create a graph programmatically
G = nx.DiGraph()

# Add nodes with geographic coordinates
G.add_node("bus_1", lat=47.0, lon=15.0, load=100.0)
G.add_node("bus_2", lat=47.1, lon=15.1, load=150.0)
G.add_node("bus_3", lat=47.2, lon=15.0, load=200.0)
G.add_node("bus_4", lat=47.0, lon=15.2, load=120.0)

# Add edges with electrical properties
G.add_edge("bus_1", "bus_2", x=0.01, p_max=500)
G.add_edge("bus_2", "bus_3", x=0.02, p_max=400)
G.add_edge("bus_3", "bus_4", x=0.015, p_max=450)
G.add_edge("bus_4", "bus_1", x=0.018, p_max=300)

# Initialize manager and load
manager = npap.PartitionAggregatorManager()
manager.load_data("networkx_direct", graph=G)

# Partition
partition = manager.partition("geographical_kmeans", n_clusters=2)

# Aggregate
aggregated = manager.aggregate(mode=npap.AggregationMode.GEOGRAPHICAL)

# Visualize
manager.plot_network(style="voltage_aware")

Custom Aggregation Profile#

For more control over how properties are aggregated:

from npap import AggregationProfile

# Define custom aggregation rules
profile = AggregationProfile(
    topology_strategy="simple",
    node_properties={
        "lat": "average",           # Geographic center
        "lon": "average",
        "p_load": "sum",            # Total load
        "q_load": "sum",
        "voltage": "average",       # Average voltage
    },
    edge_properties={
        "x": "equivalent_reactance",  # Parallel impedance formula
        "r": "equivalent_reactance",
        "p_max": "sum",               # Total capacity
        "length": "sum",              # Total length
    },
    default_node_strategy="sum",
    default_edge_strategy="average"
)

aggregated = manager.aggregate(profile=profile)

Key Points#

  1. Manager Pattern: All operations go through PartitionAggregatorManager

  2. Strategy Selection: Choose strategies based on your data and requirements

  3. Voltage Awareness: Use va_loader and va_geographical_* or va_electrical_* strategies for multi-voltage networks

  4. AC Island Handling: NPAP automatically detects and respects AC island boundaries (if ac_island attribute is present)

  5. Parallel Edges: Aggregate them before partitioning

Next Steps#