A production-grade Python toolkit for computing and analyzing network sensitivity factors in electricity markets and transmission planning.
Built by a power systems & market engineer with hands-on experience across ERCOT, MISO, PJM, SPP, and the Australian NEM.
Network sensitivity factors are at the heart of transmission planning, security-constrained economic dispatch (SCED), and locational marginal pricing (LMP). Every time a market operator clears energy — whether it's AEMO's NEM dispatch engine or PJM's real-time market — these linear approximations of power flow govern how generation shift propagates through the network.
This project computes, validates, and visualizes three foundational sensitivity matrices:
| Factor | Full Name | Core Question It Answers |
|---|---|---|
| PTDF | Power Transfer Distribution Factor | "If I shift 1 MW from bus A to bus B, what fraction flows on line k?" |
| LODF | Line Outage Distribution Factor | "If line k trips, what fraction of its pre-outage flow diverts onto line m?" |
| GSF / ISF | Generation Shift Factor / Injection Shift Factor | "If generator at bus i increases output by 1 MW (slack absorbs), what is the change in flow on line k?" |
Together, these factors power:
- Contingency Analysis (N-1, N-2) — rapid screening without full AC power flow
- Congestion Rent & FTR/CRR Valuation — financial transmission rights settlement
- Interface Limit Monitoring — enforcing flowgate constraints in real-time dispatch
- Integrated System Planning — REZ capacity expansion & transmission augmentation studies (e.g., AEMO ISP)
- Production Cost Modeling — PLEXOS and PROMOD constraint formulations
A power network is represented by its bus admittance matrix
Where:
-
$B_{bus} \in \mathbb{R}^{n \times n}$ — susceptance matrix (nodal) -
$B_f \in \mathbb{R}^{l \times n}$ — branch susceptance matrix -
$\theta \in \mathbb{R}^{n}$ — voltage angle vector -
$P_{inject} \in \mathbb{R}^{n}$ — net bus injections (generation − load)
The PTDF matrix
Where
represents the fraction of a 1 MW transaction from bus
Key properties:
-
$\sum_k \text{PTDF}_{k, i \to j} \neq 1$ (flows split across parallel paths) - PTDF is topology-dependent — it changes with network switching
- Used in zonal PTDFs for market clearing (e.g., AEMO's regional reference nodes)
When line
Computed analytically from the PTDF matrix:
Where
Key properties:
-
$\text{LODF}_{k,k} = -1$ (outaged line carries zero flow post-outage) - Enables N-1 contingency screening in
$O(l^2)$ vs. full power flow per contingency - Foundation of post-contingency flow calculation:
$F_m^{post} = F_m^{pre} + \text{LODF}_{m,k} \cdot F_k^{pre}$
The GSF for generator at bus
This is simply the
Relationship between factors:
Applications:
- SCED constraint formulation — each generator's impact on monitored flowgates
-
Congestion component of LMP —
$\lambda_k^{congestion} = \sum_m \mu_m \cdot \text{GSF}_{m,k}$ where$\mu_m$ is the shadow price on constraint$m$ - Redispatch analysis — identifying which generators relieve congestion most efficiently
power-sensitivity-factors/
│
├── data/
│ ├── raw/ # Raw network data (MATPOWER, PSS/E, CSV)
│ ├── processed/ # Cleaned bus/branch/gen tables
│ └── test_cases/ # IEEE 14-bus, 30-bus, 118-bus test cases
│
├── src/
│ ├── network/
│ │ ├── __init__.py
│ │ ├── admittance.py # Y_bus, B_bus construction
│ │ ├── dc_powerflow.py # DC power flow solver
│ │ └── topology.py # Network graph utilities
│ │
│ ├── sensitivity/
│ │ ├── __init__.py
│ │ ├── ptdf.py # PTDF computation engine
│ │ ├── lodf.py # LODF computation (full & sparse)
│ │ ├── gsf.py # GSF / ISF computation
│ │ └── contingency.py # N-1 / N-2 screening using LODF
│ │
│ ├── market/
│ │ ├── lmp_decomposition.py # Energy + congestion + loss components
│ │ ├── ftr_valuation.py # Financial transmission rights
│ │ └── flowgate_monitor.py # Real-time interface monitoring
│ │
│ └── visualization/
│ ├── network_plot.py # Networkx / plotly network diagrams
│ ├── heatmaps.py # PTDF/LODF matrix heatmaps
│ └── congestion_map.py # Geospatial congestion overlays
│
├── notebooks/
│ ├── 01_theory_and_derivation.ipynb # Mathematical walkthrough
│ ├── 02_ieee14_ptdf_demo.ipynb # PTDF on IEEE 14-bus
│ ├── 03_n1_contingency_screening.ipynb # LODF-based contingency analysis
│ ├── 04_lmp_decomposition.ipynb # Congestion pricing example
│ └── 05_nem_interface_monitoring.ipynb # Australian NEM flowgate case study
│
├── tests/
│ ├── test_ptdf.py
│ ├── test_lodf.py
│ ├── test_gsf.py
│ └── validate_against_powerworld.py # Cross-validation with commercial tools
│
├── docs/
│ ├── theory.md # Extended mathematical documentation
│ ├── market_applications.md # How factors are used in market operations
│ └── aemo_case_study.md # NEM-specific application notes
│
├── requirements.txt
├── environment.yml
└── README.md
git clone https://github.com/shankar/<repo-name>.git
cd power-sensitivity-factors
# Using pip
pip install -r requirements.txt
# Or using conda (recommended for power systems stack)
conda env create -f environment.yml
conda activate power-sensitivitynumpy>=1.24
scipy>=1.10 # sparse matrix operations (critical for large networks)
pandas>=2.0
networkx>=3.0 # network topology
pandapower>=2.13 # power system modeling & validation
matplotlib>=3.7
plotly>=5.15 # interactive network visualization
pypower # MATPOWER-compatible DC/AC power flowimport numpy as np
from src.network.admittance import build_Bbus
from src.sensitivity.ptdf import compute_ptdf
# Load IEEE 14-bus test case
from pypower.case14 import case14
ppc = case14()
# Build susceptance matrices
Bf, Bbus = build_Bbus(ppc)
# Compute full PTDF matrix [branches x buses]
ptdf = compute_ptdf(Bf, Bbus, ref_bus=0)
print(f"PTDF shape: {ptdf.shape}") # (20, 14) for IEEE 14-bus
print(f"PTDF[line_1, bus_2→bus_5]: {ptdf[0, 1] - ptdf[0, 4]:.4f}")from src.sensitivity.lodf import compute_lodf
# LODF matrix [branches x branches]
lodf = compute_lodf(ptdf, ppc['branch'])
# Post-contingency flow on line m when line k trips
line_k = 5 # outaged line index
pre_flow = np.array([...]) # pre-contingency flows in MW
post_flow = pre_flow + lodf[:, line_k] * pre_flow[line_k]
print(f"Post-contingency flow on line 3: {post_flow[2]:.2f} MW")from src.sensitivity.contingency import n1_screening
results = n1_screening(
base_flows=pre_flow,
lodf=lodf,
ratings=ppc['branch'][:, 5], # thermal ratings in MVA
threshold=0.95 # flag at 95% of rating
)
violations = results[results['overload_pct'] > 100]
print(violations[['outaged_line', 'overloaded_line', 'overload_pct']])The PTDF matrix visually reveals electrical coupling between transactions and branches. High-magnitude cells indicate strong sensitivity — a 1 MW shift on that bus pair significantly loads that branch.
📓 See
notebooks/02_ieee14_ptdf_demo.ipynbfor full visualization
| Network Size | Full AC Power Flow (per contingency) | LODF Screening |
|---|---|---|
| 14-bus (20 lines) | ~400 power flows | 1 matrix multiply |
| 118-bus (186 lines) | ~34,716 power flows | 1 matrix multiply |
| 2000-bus (3,000 lines) | ~9M power flows | Sparse matrix ops |
LODF-based screening provides exact N-1 results under DC assumptions at a fraction of the computational cost — which is precisely why real-time market engines (ERCOT SCED, AEMO's dispatch engine) rely on them.
All sensitivity matrices are cross-validated against:
- PYPOWER — open-source MATPOWER port
- pandapower — for DC power flow cross-checks
- Analytical hand-calculations — on 3-bus and 5-bus toy networks
AEMO monitors interconnector flows (e.g., Heywood, QNI, Basslink) using sensitivity-based constraint formulations. The Market Constraint Equations in NEMDE (NEM Dispatch Engine) are linear constraint rows of the form:
Σ (GSF_k,i × P_i) ≤ RHS_k for each monitored element k
This project includes a case study replicating NEM-style interface constraint formulation using publicly available AEMO network data.
📓 See
notebooks/05_nem_interface_monitoring.ipynb
The congestion component of an LMP at bus
Where
Financial Transmission Rights settle on the difference in LMPs weighted by the FTR MW quantity. Under the PTDF framework:
The project includes utilities for simulating FTR auction value under different congestion scenarios.
- The
$B_{bus}$ matrix inversion uses sparse LU factorization (scipy.sparse.linalg.splu) for large networks — critical for real-world systems with thousands of buses - Reference bus removal must be handled consistently — an off-by-one error here produces silently wrong PTDF values
- For near-radial networks, some LODF denominators approach zero (radial line has PTDF ≈ 1) — these are handled with a configurable threshold
For networks > 500 buses, dense matrix operations become prohibitive. The lodf.py module offers both:
compute_lodf_dense()— for small networks and testingcompute_lodf_sparse()— scipy sparse, suitable for real ISO-scale networks
This implementation follows the from-bus injection positive convention consistent with MATPOWER and pandapower. Some commercial tools (PowerWorld, PSS/E) may use different sign conventions — always verify before cross-validation.
- AC sensitivity factors (Q-V sensitivity, voltage sensitivity matrix)
- Multi-period PTDF for time-varying topology (switching, maintenance outages)
- LODF extension to N-2 contingencies (cascading failure screening)
- Integration with NEMOSIS / AEMO data API for real NEM network data
- PLEXOS-compatible constraint export (LP file format)
- Interactive Plotly dashboard for congestion visualization
- Stott, B., Jardim, J., Alsaç, O. (2009). DC Power Flow Revisited. IEEE Transactions on Power Systems.
- Wood, A.J., Wollenberg, B.F., Sheblé, G.B. — Power Generation, Operation and Control (3rd Ed.)
- Ott, A. (2010). Experience with PJM Market Operation, System Design, and Implementation — IEEE Trans. Power Systems
- AEMO — Integrated System Plan 2024, Constraint Formulation Guidelines
- Kirschen, D., Strbac, G. — Fundamentals of Power System Economics
- Christie, R.D., Wollenberg, B.F., Wangensteen, I. (2000). Transmission Management in the Deregulated Environment
MIT License — see LICENSE for details.