Sensitivity analysis computes how changes in network injections or controls affect power flows and voltages. Itβs essential for market operations, congestion management, and system planning.
Overview
Sensitivity analysis calculates:
PTDF (Power Transfer Distribution Factors): How power transfers affect line flows
PSDF (Phase Shift Distribution Factors): Impact of phase shifters
Voltage sensitivities : How injections affect bus voltages (AC only)
DC Sensitivity Analysis
Basic Example
Compute how load changes affect line flows:
import pypowsybl as pp
network = pp.network.create_eurostag_tutorial_example1_network()
# Create DC analysis
analysis = pp.sensitivity.create_dc_analysis()
# Define sensitivity matrix: branches vs variables
analysis.add_branch_flow_factor_matrix(
branches_ids = [ 'NHV1_NHV2_1' , 'NHV1_NHV2_2' ],
variables_ids = [ 'LOAD' ]
)
# Run analysis
result = analysis.run(network)
# Get reference flows
print (result.get_reference_matrix())
# NHV1_NHV2_1 NHV1_NHV2_2
# reference_values 300.0 300.0
# Get sensitivity values
print (result.get_sensitivity_matrix())
# NHV1_NHV2_1 NHV1_NHV2_2
# LOAD -0.5 -0.5
Interpretation: A 1 MW increase in LOAD causes a 0.5 MW decrease in flow on each line.
Multiple Matrices
Create named matrices for different analyses:
analysis = pp.sensitivity.create_dc_analysis()
# Matrix 1: Lines vs Loads
analysis.add_branch_flow_factor_matrix(
branches_ids = [ 'NHV1_NHV2_1' , 'NHV1_NHV2_2' ],
variables_ids = [ 'LOAD' ],
matrix_id = 'm1'
)
# Matrix 2: Lines vs Generators
analysis.add_branch_flow_factor_matrix(
branches_ids = [ 'NHV1_NHV2_1' ],
variables_ids = [ 'GEN' ],
matrix_id = 'm2'
)
result = analysis.run(network)
# Retrieve specific matrices
print (result.get_reference_matrix( 'm1' ))
print (result.get_sensitivity_matrix( 'm1' ))
print (result.get_sensitivity_matrix( 'm2' ))
Zone-Based Analysis
Analyze power transfers between control zones (countries, regions).
Zone to Slack Sensitivity
Compute how injections from a zone affect flows:
n = pp.network.load( 'simple-eu.uct' )
# Create zone with all German generators
zone_de = pp.sensitivity.create_country_zone(n, 'DE' )
# Disable distributed slack
params = pp.loadflow.Parameters( distributed_slack = False )
sa = pp.sensitivity.create_dc_analysis()
sa.set_zones([zone_de])
# Calculate DE zone sensitivity on border line
sa.add_branch_flow_factor_matrix(
[ 'BBE2AA1 FFR3AA1 1' ],
[ 'DE' ],
'm'
)
results = sa.run(n, params)
print (results.get_sensitivity_matrix( 'm' ))
# BBE2AA1 FFR3AA1 1
# DE -0.45182
Zone to Zone Sensitivity (PTDF)
Calculate power transfer distribution factors:
zone_fr = pp.sensitivity.create_country_zone(n, 'FR' )
zone_de = pp.sensitivity.create_country_zone(n, 'DE' )
zone_be = pp.sensitivity.create_country_zone(n, 'BE' )
zone_nl = pp.sensitivity.create_country_zone(n, 'NL' )
sa = pp.sensitivity.create_dc_analysis()
sa.set_zones([zone_fr, zone_de, zone_be, zone_nl])
# Monitor border lines for various power transfers
sa.add_branch_flow_factor_matrix(
[ 'BBE2AA1 FFR3AA1 1' , 'FFR2AA1 DDE3AA1 1' ],
[ 'FR' , ( 'FR' , 'DE' ), ( 'DE' , 'FR' ), 'NL' ],
'm'
)
result = sa.run(n, params)
print (result.get_sensitivity_matrix( 'm' ))
# BBE2AA1 FFR3AA1 1 FFR2AA1 DDE3AA1 1
# FR -0.708461 0.291539
# FR -> DE -0.256641 0.743359
# DE -> FR 0.256641 -0.743359
# NL -0.225206 -0.225206
Zone-to-zone sensitivity ('FR', 'DE') means: increase FR injection and decrease DE injection by the same amount.
Working with Zones
Creating Zones
Country Zone
Empty Zone
From GLSK File
zone_de = pp.sensitivity.create_country_zone(n, 'DE' )
Zone Shift Keys
Control how injections are weighted within a zone:
from pypowsybl.sensitivity import ZoneKeyType
# Use generator target power (default)
zone = pp.sensitivity.create_country_zone(n, 'DE' ,
ZoneKeyType. GENERATOR_TARGET_P )
# Use generator maximum power
zone = pp.sensitivity.create_country_zone(n, 'DE' ,
ZoneKeyType. GENERATOR_MAX_P )
# Use load power
zone = pp.sensitivity.create_country_zone(n, 'DE' ,
ZoneKeyType. LOAD_P0 )
# View shift keys
print (zone.shift_keys_by_injections_ids)
# {'DDE1AA1_generator': 2500.0,
# 'DDE2AA1_generator': 2000.0,
# 'DDE3AA1_generator': 1500.0}
Modifying Zones
Move injections between zones:
zone_fr = pp.sensitivity.create_country_zone(n, 'FR' )
zone_de = pp.sensitivity.create_country_zone(n, 'DE' )
# Move a generator from DE to FR
zone_de.move_injection_to(zone_fr, 'DDE3AA1_generator' )
print (zone_fr.injections_ids)
# ['FFR1AA1_generator', 'FFR2AA1_generator',
# 'FFR3AA1_generator', 'DDE3AA1_generator']
print (zone_de.injections_ids)
# ['DDE1AA1_generator', 'DDE2AA1_generator']
X-Node Sensitivity
Calculate sensitivity to boundary nodes (X-nodes) in UCTE/CGMES networks:
n = pp.network.load( 'simple-eu-xnode.uct' )
# X-nodes appear as dangling lines
print (n.get_dangling_lines())
# pairing_key ucte_xnode_code
# id
# NNL2AA1 XXXXXX11 NNL2AA1_0 NNL2AA1_0
# Create zone for X-node
zone_x = pp.sensitivity.create_empty_zone( 'X' )
zone_x.add_injection( 'NNL2AA1 XXXXXX11 1' )
sa = pp.sensitivity.create_dc_analysis()
sa.set_zones([zone_x])
sa.add_branch_flow_factor_matrix([ 'BBE2AA1 FFR3AA1 1' ], [ 'X' ], 'm' )
result = sa.run(n)
print (result.get_sensitivity_matrix( 'm' ))
# BBE2AA1 FFR3AA1 1
# X 0.176618
AC Sensitivity Analysis
Perform more accurate AC analysis:
analysis = pp.sensitivity.create_ac_analysis()
# Same as DC for active power flows
analysis.add_branch_flow_factor_matrix(
branches_ids = [ 'NHV1_NHV2_1' , 'NHV1_NHV2_2' ],
variables_ids = [ 'LOAD' ]
)
result = analysis.run(network)
Voltage Sensitivity
Compute how voltage setpoints affect bus voltages:
analysis = pp.sensitivity.create_ac_analysis()
# Buses vs regulating equipment
analysis.add_bus_voltage_factor_matrix(
bus_ids = [ 'VLHV1_0' , 'VLLOAD_0' ],
target_voltage_ids = [ 'GEN' ]
)
result = analysis.run(network)
print (result.get_sensitivity_matrix())
# VLHV1_0 VLLOAD_0
# GEN 17.629602 7.89637
Interpretation: A 1 kV increase in GENβs target voltage increases VLHV1_0 voltage by 17.6 kV.
Post-Contingency Analysis
Calculate sensitivities after contingencies:
analysis = pp.sensitivity.create_dc_analysis()
# Define sensitivity matrix
analysis.add_branch_flow_factor_matrix(
branches_ids = [ 'NHV1_NHV2_1' , 'NHV1_NHV2_2' ],
variables_ids = [ 'LOAD' ],
matrix_id = 'm'
)
# Add contingency
analysis.add_single_element_contingency( 'NHV1_NHV2_1' )
result = analysis.run(network)
# Get post-contingency results
print (result.get_reference_matrix( 'm' , 'NHV1_NHV2_1' ))
# NHV1_NHV2_1 NHV1_NHV2_2
# reference_values NaN 600.0
print (result.get_sensitivity_matrix( 'm' , 'NHV1_NHV2_1' ))
# NHV1_NHV2_1 NHV1_NHV2_2
# LOAD 0.0 -1.0
Selective Contingency Analysis
Control which states to analyze:
analysis = pp.sensitivity.create_dc_analysis()
# Pre-contingency only
analysis.add_precontingency_branch_flow_factor_matrix(
branches_ids = [ 'NHV1_NHV2_1' , 'NHV1_NHV2_2' ],
variables_ids = [ 'LOAD' ],
matrix_id = 'precontingency'
)
# Specific post-contingency states
analysis.add_postcontingency_branch_flow_factor_matrix(
branches_ids = [ 'NHV1_NHV2_1' , 'NHV1_NHV2_2' ],
variables_ids = [ 'GEN' ],
contingencies_ids = [ 'NHV1_NHV2_1' ],
matrix_id = 'postcontingency'
)
analysis.add_single_element_contingency( 'NHV1_NHV2_1' )
result = analysis.run(network)
print (result.get_sensitivity_matrix( 'precontingency' ))
print (result.get_sensitivity_matrix( 'postcontingency' , 'NHV1_NHV2_1' ))
Advanced Factor Configuration
Full control over sensitivity factors:
analysis = pp.sensitivity.create_ac_analysis()
analysis.add_factor_matrix(
functions_ids = [ 'NHV1_NHV2_1' ],
variables_ids = [ 'LOAD' ],
contingency_context_type = pp.sensitivity.ContingencyContextType. NONE ,
contingencies_ids = [],
sensitivity_function_type = pp.sensitivity.SensitivityFunctionType. BRANCH_ACTIVE_POWER_2 ,
sensitivity_variable_type = pp.sensitivity.SensitivityVariableType. INJECTION_ACTIVE_POWER
)
result = analysis.run(network)
print (result.get_sensitivity_matrix())
# NHV1_NHV2_1
# LOAD 0.501398
Supported Function Types
BRANCH_ACTIVE_POWER_1, BRANCH_ACTIVE_POWER_2
BRANCH_REACTIVE_POWER_1, BRANCH_REACTIVE_POWER_2
BRANCH_CURRENT_1, BRANCH_CURRENT_2
BUS_VOLTAGE
Supported Variable Types
INJECTION_ACTIVE_POWER
INJECTION_REACTIVE_POWER
TRANSFORMER_PHASE
BUS_TARGET_VOLTAGE
HVDC_LINE_ACTIVE_POWER
AUTO_DETECT
GLSK Files
Load shift keys from UCTE GLSK files:
import datetime
n = pp.network.load( 'simple-eu.uct' )
# Method 1: Load all zones from file
zones = pp.sensitivity.create_zones_from_glsk_file(
n,
'glsk_sample.xml' ,
datetime.datetime( 2019 , 1 , 8 , 0 , 0 )
)
sa = pp.sensitivity.create_dc_analysis()
sa.set_zones(zones)
sa.add_branch_flow_factor_matrix([ 'BBE2AA1 FFR3AA1 1' ],
[ '10YCB-GERMANY--8' ], 'm' )
results = sa.run(n, params)
# Method 2: Granular control
glsk_document = pp.glsk.load( 'glsk_sample.xml' )
t_start = glsk_document.get_gsk_time_interval_start()
de_generators = glsk_document.get_points_for_country(
n, '10YCB-GERMANY--8' , t_start
)
de_shift_keys = glsk_document.get_glsk_factors(
n, '10YCB-GERMANY--8' , t_start
)
zone_de = pp.sensitivity.create_zone_from_injections_and_shift_keys(
'10YCB-GERMANY--8' , de_generators, de_shift_keys
)
Use Cases
Calculate PTDFs for:
Zonal pricing
Flow-based market coupling
Congestion rent allocation
Available transfer capacity (ATC)
Analyze:
Critical network elements
Reinforcement benefits
Renewable integration impact
N-1 security margins
Support:
Redispatch optimization
Congestion management
Remedial action schemes
State estimation validation
Use DC analysis for large-scale studies (much faster)
Limit the number of monitored branches
Use pre/post-contingency matrices selectively
Batch similar analyses together
Next Steps
Short-Circuit Analysis Calculate fault currents
Dynamic Simulation Run time-domain simulations