Load-Case OPF Analysis

Script: scripts/acopf_loadcase_analysis.py

Demonstrates two successive AC-OPF analyses on the SimBench LV rural1 network: reactive-power minimisation at a single operating point, followed by voltage-deviation minimisation for representative load cases.


Purpose

Power system operators assess grid behaviour under standardised loading scenarios called load cases (e.g. high load, low wind). This script shows how to:

  • Fix a snapshot from a time-series profile and run an AC-OPF that minimises reactive power exchange.
  • Sweep through SimBench load cases, scale loads and generation accordingly, and solve the OPF to find the reactive dispatch that minimises voltage deviations from 1 p.u.

Workflow

Step 1 — Reactive-power minimisation (single snapshot)

load network (1-LV-rural1--0-sw)
select profile index 1190
set PV reactive-power envelope from 0.95 power factor
fix sgen active power (max_p_mw = min_p_mw = p_mw)
pp.runpp() → reference power flow
deep-copy network → constrain ext_grid P to pp result, Q to ±0.05 MVAR
ACOPF.add_OPF() + add_reactive_power_flow_objective()
solve → minimise Σ qsG²
print initial and optimised sgen set-points

The external grid active power is pinned to the pandapower result, restricting the ext_grid to a small reactive-power band (±0.05 MVAR). This forces the AC-OPF to dispatch reactive power from the PV generators to satisfy the network reactive balance, and the objective drives their reactive output towards zero.

Step 2 — Voltage-deviation minimisation per load case

for case in ["lW", "hL"]:
    deep-copy profile-loaded network
    scale loads and generation with loadcase factors
    set slack voltage to loadcase value
    ACOPF.add_voltage_deviation_objective()  (no add_OPF())
    solve → minimise Σ_b (v[b] - 1)²
    print ext_grid dispatch and line 0 active power

add_OPF() is intentionally omitted for the case loop. The optimiser is free to dispatch unlimited reactive power from all generators; it finds the reactive set-point that best flattens the voltage profile. This is an exploratory analysis, not an operational planning problem.


Load cases

Standard SimBench load-case identifiers used in this script:

Key Meaning Load Wind PV
lW Low wind 10 % 100 % 80 %
hL High load 100 % varies varies

Access all available cases for any network:

import simbench as sb
net = sb.get_simbench_net("1-LV-rural1--0-sw")
print(net.loadcases)

Usage

Requires IPOPT:

conda activate potpourri_env
python scripts/acopf_loadcase_analysis.py

!!! note Step 1 may report infeasible with IPOPT on snapshot index 1190. At this high-PV operating point the tight ext_grid reactive-power limit (±0.05 MVAR) conflicts with the network's reactive balance requirements. Step 2 converges reliably.


Example output

=== Step 1: reactive-power minimisation ===
  Converged: True
  Objective sum(qsG^2): 0.000017
  sgen P [pu] / Q [pu] after OPF:
    sgen 0: psG=0.00766  qsG=-0.00253
    sgen 1: psG=0.00784  qsG=-0.00259
    sgen 2: psG=0.00190  qsG=-0.00063
    sgen 3: psG=0.00230  qsG=-0.00076

=== Step 2: voltage-deviation per load case ===
  Case lW: converged=True  obj_v_deviation=0.000000
    ext_grid 0: P=-0.01192  Q=0.25487 [pu]
    line 0 pLfrom = 0.00012 [pu]
    sgen Q dispatch [pu]:
      sgen 0: qsG=-0.01683
      ...
  Case hL: converged=True  obj_v_deviation=0.000003
    ext_grid 0: P=0.02304  Q=-0.15417 [pu]
    line 0 pLfrom = 0.00116 [pu]

Key API calls

Call Effect
ACOPF(net) Build AC model; runs pp.runpp() internally
add_OPF() Add voltage bounds, Q limits, line loading limits
add_reactive_power_flow_objective() Minimise Σ qsG² → model.obj_reactive
add_voltage_deviation_objective() Minimise Σ (v−1)² → model.obj_v_deviation
solve(solver="ipopt") Solve and write results to net.res_*