Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
606fbee
Add graph theory partitioners and conservation mode
omari91 Feb 19, 2026
4a2f49a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 19, 2026
3b04ea7
Address review comments
omari91 Feb 20, 2026
d2a8178
Merge branch 'IEE-TUGraz:main' into main
omari91 Feb 20, 2026
7a459cd
Remove empty roadmap link from feature template
MarcoAnarmo Feb 23, 2026
c83ebc4
Add graph copy helper
omari91 Feb 20, 2026
d33cf5a
feature enhancement: copy graph visualization
omari91 Feb 20, 2026
83b75b0
Add property-based partitioning tests
omari91 Feb 20, 2026
3a1df80
Add CLI helpers
omari91 Feb 20, 2026
88684d8
Add PTDF/Kron diagnostics
omari91 Feb 20, 2026
bf4eaca
feature update
omari91 Feb 20, 2026
15c98cc
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 20, 2026
5c46e48
Add tests to cover CLI and aggregation helpers
omari91 Feb 20, 2026
61292c7
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 20, 2026
f024ddb
Fix CSV loading: remove hardcoded quotechar and add comment support
omari91 Feb 26, 2026
846ae13
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 26, 2026
942027d
feature enhancement: copy graph visualization
omari91 Feb 20, 2026
bff3201
Add CLI helpers
omari91 Feb 20, 2026
245cf7a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 20, 2026
f72a7b2
Add tests to cover CLI and aggregation helpers
omari91 Feb 20, 2026
a0f93f0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 20, 2026
e28016c
Include debug scripts for Zenodo data loading
omari91 Feb 26, 2026
943c1f6
docs: cleanup .hypothesis and add example library with European netwo…
omari91 Feb 26, 2026
1088e29
chore: formatting and pypsa related test failure
omari91 Feb 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ body:
value: |
Thank you for suggesting a feature! Please consider whether it fits NPAP's scope (network partitioning & aggregation).

Before submitting, please check [existing issues](https://github.com/IEE-TUGraz/NPAP/issues) and the [roadmap](https://github.com/IEE-TUGraz/NPAP#roadmap) to see if it's already planned.
Before submitting, please check [existing issues](https://github.com/IEE-TUGraz/NPAP/issues) to see if it's already planned.

- type: textarea
id: description
Expand Down
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,10 @@ Thumbs.db
# Claude files
CLAUDE.md
.claude/

# Hypothesis cache
.hypothesis/

# NPAP Examples data and results
examples/data/
examples/output/
1 change: 1 addition & 0 deletions docs/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This section contains the complete API reference for NPAP.
interfaces
partitioning
aggregation
visualization
exceptions

Core Module
Expand Down
16 changes: 16 additions & 0 deletions docs/api/partitioning.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,22 @@ Electrical Distance Partitioning
:show-inheritance:
:no-index:

LMP Partitioning
----------------

.. automodule:: npap.partitioning.lmp
:members:
:show-inheritance:
:no-index:

Adjacent-Node Agglomerative Clustering
--------------------------------------

.. automodule:: npap.partitioning.adjacent
:members:
:show-inheritance:
:no-index:

Voltage-Aware Partitioning
--------------------------

Expand Down
18 changes: 18 additions & 0 deletions docs/api/visualization.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Visualization
=============

.. currentmodule:: npap.visualization

Visualization helpers for NPAP.

.. autosummary::
:toctree: generated
:nosignatures:

PlotStyle
PlotPreset
PlotConfig
NetworkPlotter
plot_network
export_figure
clone_graph
37 changes: 33 additions & 4 deletions docs/user-guide/aggregation.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ NPAP provides predefined aggregation modes for common use cases.
|------|-------------|----------|
| `SIMPLE` | Sum all numeric properties | Basic reduction |
| `GEOGRAPHICAL` | Average coordinates, sum loads | Spatial analysis |
| `DC_KRON` | Kron reduction for DC networks | DC network analysis |
| `DC_PTDF` | PTDF-driven Kron reduction | DC electrical analysis |
| `DC_KRON` | Kron reduction | DC electrical research |
| `CUSTOM` | User-defined profile | Advanced use |

### SIMPLE Mode
Expand Down Expand Up @@ -82,6 +83,25 @@ Configuration:
- Edge `x`: `average`
- Default: `average`

### DC_PTDF Mode

For DC-specific reductions use this mode to preserve PTDF-derived reactances.

```python
aggregated = manager.aggregate(mode=AggregationMode.DC_PTDF)
```

Configuration:
- Topology: `electrical`
- Physical: `ptdf_reduction`
- Physical properties: `x` (reactance is handled via PTDF/Kron reduction)
- Node properties: `lat`, `lon` → `average`
- Edge `p_max`: `sum`

The aggregated graph carries a `reduced_ptdf` matrix on `aggregated.graph`,
which you can read via `aggregated.graph["reduced_ptdf"]` to inspect the
reduced PTDF used during the reduction.

### CUSTOM Mode

For full control, create an {py:class}`~npap.AggregationProfile`:
Expand Down Expand Up @@ -278,12 +298,21 @@ edge_properties={

Physical aggregation strategies preserve electrical laws during network reduction.

### Kron Reduction (Planned)
### Kron Reduction Mode

```{note}
Kron reduction is planned for a future release.
Kron reduction is now available via `AggregationMode.DC_KRON`. It uses the
`kron_reduction` physical strategy on an electrical topology so the aggregated
reactances reflect a Kron-reduced susceptance matrix of the cluster
representatives.

```python
aggregated = manager.aggregate(mode=AggregationMode.DC_KRON)
```

The aggregated graph stores the reduced Laplacian under
`aggregated.graph["kron_reduced_laplacian"]`, which you can inspect for
downstream DC studies or debugging.

## Handling Defaults

When a property isn't explicitly mapped, NPAP uses the default strategy:
Expand Down
77 changes: 75 additions & 2 deletions docs/user-guide/available-strategies.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,56 @@ Combines electrical distance (PTDF approach) with voltage level and AC island co

**Required attributes**: Nodes: `voltage`, `ac_island` | Edges: `x` (reactance)

### Graph-theory Partitioning

Graph-theory strategies rely on matrix-based clustering or community detection rather than physical distances.

| Strategy | Algorithm | Description |
|----------|-----------|-------------|
| `spectral_clustering` | Spectral clustering (precomputed adjacency) | Ideal for networks with loose geographic signals; adapts to the graph Laplacian. |
| `community_modularity` | Greedy modularity communities | Detects naturally modular regions without any tunable `n_clusters`. |

Spectral clustering expects `n_clusters` as an argument and splits the adjacency matrix via Eigen-decomposition, while the community strategy returns the modularity-maximizing partition automatically.

### Adjacent-node Agglomerative Clustering

`adjacent_agglomerative` merges groups only via edges that already exist in the graph. It is useful for topology-aware workflows that should not combine disconnected buses even when their attributes look similar.

**Required node attributes**: depends on `AdjacentAgglomerativeConfig.node_attribute`; any numeric attribute can guide the merging order.

**Configuration**: use {py:class}`~npap.partitioning.adjacent.AdjacentAgglomerativeConfig` to choose the attribute that scores merges and to keep AC islands separate via `ac_island_attr`. When the attribute is omitted, merges fall back to any adjacent pair in deterministic order.

```python
from npap.partitioning import AdjacentAgglomerativeConfig
from npap import PartitionAggregatorManager

config = AdjacentAgglomerativeConfig(node_attribute="load", ac_island_attr="ac_island")
manager = PartitionAggregatorManager()
partition = manager.partition("adjacent_agglomerative", n_clusters=4, config=config)
```

### LMP Partitioning

The `lmp_similarity` strategy groups buses with similar locational marginal prices (LMPs, typically provided by an OPF or market simulation) and optionally favours directly connected nodes through the `adjacency_bonus` configuration parameter. Nodes with different `ac_island` values are always separated by a large `infinite_distance` to keep islands apart.

**Required node attributes**: `lmp` (or a custom attribute specified via `price_attribute`)

**Configuration**: Use {py:class}`~npap.partitioning.lmp.LMPPartitioningConfig` to override the attribute names, adjacency bonus, or infinite-distance penalty.

```python
from npap.partitioning import LMPPartitioningConfig
from npap import PartitionAggregatorManager

config = LMPPartitioningConfig(
price_attribute="locational_price",
adjacency_bonus=0.2,
infinite_distance=1e5,
)

manager = PartitionAggregatorManager()
partition = manager.partition("lmp_similarity", n_clusters=3, config=config)
```

### Choosing a Partitioning Strategy

```{mermaid}
Expand Down Expand Up @@ -204,7 +254,9 @@ Predefined {py:class}`~npap.AggregationMode` for common use cases:
|------|----------|-----------------|------------------------------------|
| `SIMPLE` | simple | sum all | sum all |
| `GEOGRAPHICAL` | simple | avg coords, sum loads | sum capacity, equivalent reactance |
| `DC_KRON` | electrical | Kron reduction | Kron reduction |
| `DC_PTDF` | electrical | PTDF reduction | PTDF-derived reactance values |
| `DC_KRON` | electrical | Kron reduction | Kron reduction |
| `CONSERVATION` | electrical | Equivalent reactance + transformer preservation | Preserves transformer count & impedance |
| `CUSTOM` | user-defined | user-defined | user-defined |

```python
Expand All @@ -214,6 +266,23 @@ from npap import AggregationMode
aggregated = manager.aggregate(mode=AggregationMode.GEOGRAPHICAL)
```

### PTDF Reduction Mode

`AggregationMode.DC_PTDF` wires the `"ptdf_reduction"` physical strategy into an
electrical topology, so the reduced graph stores PTDF-consistent reactances for
each aggregated edge and exposes a `reduced_ptdf` matrix in `aggregated.graph`.
This mode keeps `x` coupled to the physical reduction step instead of applying
statistical averaging.

### Kron Reduction Mode

`AggregationMode.DC_KRON` applies the `"kron_reduction"` strategy to an
electrical topology. The strategy eliminates nodes that belong to each cluster
and keeps only the representative nodes, so aggregated reactances follow a
Kron-reduced susceptance matrix. The resulting Laplacian is stored in
`aggregated.graph["kron_reduced_laplacian"]` for debugging or downstream DC
studies.

### Custom Aggregation Profile

For full control over the aggregation step, create an {py:class}`~npap.AggregationProfile`:
Expand Down Expand Up @@ -260,6 +329,10 @@ aggregated = manager.aggregate(profile=profile)

See [Aggregation](aggregation.md) for detailed documentation.

### Transformer Conservation Mode

`AggregationMode.CONSERVATION` wires the new `"transformer_conservation"` physical strategy into an electrical topology, so transformer reactance (`x`) and resistance (`r`) are calculated using parallel impedance rules before statistical aggregation steps run. This mode is the foundation for the voltage-aware workflows on this page.

## Key Classes

### Main Entry Point
Expand Down Expand Up @@ -289,7 +362,7 @@ See [Aggregation](aggregation.md) for detailed documentation.
| Enum | Values |
|------|--------|
| {py:class}`~npap.EdgeType` | `LINE`, `TRAFO`, `DC_LINK` |
| {py:class}`~npap.AggregationMode` | `SIMPLE`, `GEOGRAPHICAL`, `DC_KRON`, `CUSTOM` |
| {py:class}`~npap.AggregationMode` | `SIMPLE`, `GEOGRAPHICAL`, `DC_PTDF`, `DC_KRON`, `CUSTOM`, `CONSERVATION` |

### Exceptions

Expand Down
8 changes: 8 additions & 0 deletions docs/user-guide/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

installation
quick-start
workflows
available-strategies
```

Expand Down Expand Up @@ -148,6 +149,13 @@ Different strategies require specific node and edge attributes:
| Electrical | `ac_island` | `x` (reactance) |
| Voltage-Aware | `lat`, `lon`, `voltage`, `ac_island` | `x`, `type` |

### Cloning Loaded Graphs

If you want to experiment with variations of a loaded network without re-running the loaders,
use `PartitionAggregatorManager.copy_graph()` to obtain a deep copy of the current graph. The copy
preserves every node/edge attribute but is entirely separate from the manager’s internal storage,
so you can test different aggregation paths or visualizations without modifying the source.

## Error Handling

NPAP provides a comprehensive exception hierarchy:
Expand Down
37 changes: 37 additions & 0 deletions docs/user-guide/partitioning/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ flowchart TD
| Geographical with AC islands | `geographical_kmedoids_haversine` |
| Electrical behavior grouping | `electrical_kmedoids` |
| Multi-voltage network | `va_geographical_kmedoids_haversine` |
| Congestion-aware pricing | `lmp_similarity` |
| Topology-preserving merges | `adjacent_agglomerative` |
| Unknown cluster count | `geographical_dbscan_*` or `geographical_hdbscan_*` |

### Performance Comparison
Expand Down Expand Up @@ -160,6 +162,41 @@ config = ElectricalDistanceConfig(
)
```

### Adjacent-node Agglomerative Configuration

Use {py:class}`~npap.partitioning.adjacent.AdjacentAgglomerativeConfig` when you need
clusters to grow only along explicit edges. The optional `node_attribute`
parameter determines the values AgglomerativeClustering compares as it chooses
which adjacent clusters to merge next, while `ac_island_attr` keeps islands
separate even when DC links exist.

```python
from npap.partitioning import AdjacentAgglomerativeConfig

config = AdjacentAgglomerativeConfig(
node_attribute="load",
ac_island_attr="ac_island",
)
```

Pass the config via `config=` when calling `PartitionAggregatorManager.partition`.

### LMP Partitioning Configuration

LMP partitioning relies on locational marginal prices (LMPs) to highlight congestion-aware clusters. Use {py:class}`~npap.partitioning.lmp.LMPPartitioningConfig` when your price column is named differently or when you want to tweak the adjacency/infinite-distance handling.

```python
from npap.partitioning import LMPPartitioningConfig

config = LMPPartitioningConfig(
price_attribute="locational_price",
adjacency_bonus=0.2, # neighbours with similar prices merge quicker
infinite_distance=1e5, # separate distinct AC islands
)
```

Apply the config via the `config` keyword when calling `PartitionAggregatorManager.partition("lmp_similarity", ...)`.

## Working with Partition Results

### Inspecting Results
Expand Down
Loading