Skip to content

Start the rectangular AC model from a power flow solved on the projected cone point (IndACConeStart = 2) - #213

Merged
erikfilias merged 3 commits into
masterfrom
feat/ac-cone-power-flow-start
Oct 11, 2026
Merged

erikfilias merged 3 commits into
masterfrom
feat/ac-cone-power-flow-start

Conversation

@erikfilias

@erikfilias erikfilias commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Problem

IndACConeStart = 1 (#212) starts the rectangular model from the rank-one projection of the W-space cone. Where the cone is tight the projection is close to an AC operating point and the start is good. Where the cone is loose it is not: the angle differences of its branches do not close around loops, and the projected voltages with the cone's dispatch violate the nodal balances by hundreds of MW. On 11 June of the Nordic calendar sample, the day whose cone has the largest loop mismatch (1.4 pu of voltage), Ipopt started from the projection with a primal infeasibility of 144 and spent its first 300 iterations repairing it, with the objective climbing to 1,357 MEUR on the load-shedding penalties before it fell. The flat start was feasible by iteration 100. The cone start was slower than the flat one on that day and on 21 May, and faster only on 19 February (entries 153 to 155 of the study log).

Solution

IndACConeStart = 2: the cone, its projection, and then an AC power flow per load level from the projected point before the model is handed to the solver. _PowerFlowFromProjection in openTEPES_ModelFormulationElectricity.py:

  1. Network. The admittance matrix of the AC branches in service in the period, with the tap on the sending side and the charging of the lines that are always in service; the charging of a switched or candidate line is the constant the balance uses. The shunts enter with the susceptance and state of the cone point.
  2. Injections. The active and reactive power the units, the loads, the HVDC links and the switched charging put into the AC network at the cone point, read from the cone's branch flows through the balance, so that every term the balance carries is in them.
  3. Buses. A bus with a reactive-capable unit in service, or a condenser, holds the projected voltage magnitude with its reactive power free. The reference bus of each connected part of the network holds magnitude and angle. Every other bus keeps the cone's reactive injection.
  4. Slack. One distributed slack per connected part takes up the active power the AC losses change. A first power flow finds its sign; the slack is then shared among the buses whose units have room that way, in proportion to that room, and the power flow is solved again from the point found. A unit at its minimum cannot back off and one at its maximum cannot add.
  5. Newton. _NewtonPV: Newton-Raphson in polar form with PV buses and the slack variables, a step halved until the mismatch falls, dense, numpy only. A load level that does not converge from the projection is tried from a flat profile at the free buses, and keeps the projection if that fails too.
  6. Start. The voltages, |V|², the four branch flow variables (the rectangular formulas of the flow constraints), the shunt injections, the outputs of the units that carry the slack and their second block, and the reactive outputs at the regulated buses are set from the solution, each within its bounds. What a bus's units cannot take within their bounds goes to unserved energy or reactive power not served. A flow beyond its rating or a voltage outside its band is put at the bound, which is where the solver would put it, and counted.
  7. Report. The worst balance residual at the start, in MW and Mvar, with the bus and load level of the worst active one, is printed and kept in mTEPES.pACStartResidual[(p, sc, st)].

The option is validated in InputData (0, 1 or 2; 1 and 2 need IndACPowerFlow = 3), declared as a non-negative integer in DataConfiguration, and named by ReportConfiguration.

Observed behaviour

9n_AC, first two hours, IndACPowerFlow = 3, Ipopt: the three starts reach the same total cost, 0.0367991 MEUR, in 96 (flat), 107 (projection) and 104 (power flow) iterations; the power flow converges in its one load level with a slack of 13 MW and leaves the balances at a residual of 0.000 MW.

695-bus Nordic case, 11 June, 24 coupled hours with storage, Ipopt with MUMPS (data/cases/2026-06-11_calendar-ac-voltband-rect, -cone and -pf in the study repository). The cone took Gurobi 16 seconds and gave the bound 27.940062 MEUR, 5.5 per cent below the exact cost; the power flow took 8 seconds, converged in all 24 load levels, and the balances hold at its point. That point is outside the limits the cone's dispatch ignored: 276 branch-hours carry more than their rating, one 400 kV line by 1.25 GW, 24 bus-hours sit outside the voltage band, and the regulated buses need reactive power beyond capability that goes to reactive power not served. Put at their bounds, those flows leave the balance violated by 1.25 GW at that line. Ipopt still starts far better from it: the primal infeasibility falls below 1e-3 at iteration 27, against 317 from the projection and 59 from the flat start, and the run ends at the optimal exit in fewer iterations than either.

Start Iterations Ipopt seconds Exit Operation cost of generation
Flat 623 838 acceptable level 29.577277 MEUR
W-space cone, projection 838 965 acceptable level 29.577332 MEUR
W-space cone, power flow 559 767 optimal 29.577332 MEUR

The three solutions are the same: losses, hydro, branch-hours at the rating, every zonal price and the water values agree, and both power flow checks of the study reproduce the point exactly.

21 May, the other day where the projection start lost (gap 4.3 per cent): the cone needed the presolve-off retry, the power flow converged in all 24 load levels in 5 seconds with 194 branch-hours put at their rating, and Ipopt was feasible at iteration 20 against 131 from the projection and 59 from the flat start.

Start Iterations Ipopt seconds Exit Operation cost of generation
Flat 680 1161 acceptable level 27.051087 MEUR
W-space cone, projection 860 1360 acceptable level 27.051135 MEUR
W-space cone, power flow 620 1066 optimal 27.051134 MEUR

Again the same solution, reproduced exactly by both checks.

The two days where the projection was neutral or won, 15 January (gap 0.13 per cent) and 19 February (gap 7.2 per cent), run the same way. Iterations and Ipopt seconds, with the iteration at which the primal infeasibility first falls below 1e-3:

Day Flat Projection Power flow
15 January 467 it, 732 s, feasible at 78 423 it, 878 s, feasible at 119 347 it, 584 s, feasible at 19, optimal exit
19 February 612 it, 811 s, feasible at 83 447 it, 618 s, feasible at 26 480 it, 669 s, feasible at 24
21 May 680 it, 1161 s, feasible at 59 860 it, 1360 s, feasible at 131 620 it, 1066 s, feasible at 20, optimal exit
11 June 623 it, 838 s, feasible at 59 838 it, 965 s, feasible at 317 559 it, 767 s, feasible at 27, optimal exit

On all four days the power flow start takes 9 to 26 per cent fewer iterations than the flat start and reaches the same solution, which both checks reproduce exactly. It beats the projection on three days; on 19 February, where the projection was already feasible at iteration 26, it is 7 per cent slower than the projection and still faster than flat. The early feasibility is the mechanism, and the flows it leaves beyond the ratings, 4 to 276 branch-hours, do not undo it. A fifth day, the coupled development day of 17 February, gives 348 iterations and 568 s to the optimal exit against 533 and 825 to the acceptable level from the flat start.

Tests

tests/test_ac_input.py: test_cone_start_reaches_the_flat_start_optimum now runs the three starts and asserts, for the power flow start, that the balance residual at the start is below 0.01 MW; test_cone_start_power_flow_converges_with_a_distributed_slack checks _NewtonPV on a two-bus network, the load bus at its injection and the slack equal to the load plus the loss.

Files

openTEPES_ModelFormulationElectricity.py (_PowerFlowFromProjection, _NewtonPV, _dSbus_dV, the call from ACConeWarmStart), openTEPES_InputData.py (the option's values and validation), openTEPES_DataConfiguration.py (the parameter's domain), openTEPES.py (the configuration report), doc/md/InputData.md, CHANGELOG.md, tests/test_ac_input.py.

…ted cone point (IndACConeStart = 2)

The projection of a loose cone violates the nodal balances, and Ipopt spends its first
iterations repairing that. With IndACConeStart = 2 an AC power flow is solved per load
level from the projected point, with the cone's dispatch, the projected voltage held at
the buses with a reactive-capable unit in service and a distributed slack per
synchronous area weighted by the room of the units. The voltages, branch flows, shunt
injections, unit outputs and reactive outputs are set from it within their bounds, and
the balance residual at the start is reported. On the 11 June day of the Nordic case
Ipopt is feasible at iteration 27 instead of 317 and reaches the optimal exit in 559
iterations against 623 from a flat start and 838 from the projection.
A shunt out of service has its injection bounded at zero, and the value scaled from the
cone point could land a rounding error outside it, which Pyomo reported at every shunt and
load level.
…ound by the power flow start

The 75 shunts of the Nordic case with no susceptance have their injection bounded at zero
and were reported as 1,800 values at a bound per day.
@erikfilias
erikfilias merged commit 1747f88 into master Oct 11, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant