The PyPSA ladder¶
mathspec states PyPSA in one file, grown a rung at a time. Each rung here is that file projected onto what its network builds, shown as specsolve builds it beside the PyPSA code that builds the same network, and compared with PyPSA four ways. The PyPSA parity workflow regenerates every page's sources from the pinned mathspec on each run and fails on a diff.
objective — one number, both solves · structure — the same constraint and variable names, one block each · size — the same solver rows, columns and nonzeros · duals — every constraint's dual, per row · linopy lane — the two linopy models, label for label. ✔ identical · ≠ differs, with the recorded reason · ◌ not comparable yet.
| rung | objective | structure | size | duals | linopy lane |
|---|---|---|---|---|---|
| Rung 1 — transport | ✔ 7182.222222222221 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 45 rows · ≠ 16 vs 19 columns · ✔ 57 nonzeros | ✔ 45 rows | ✔ 9 equal · ≠ 4 recorded |
| Rung 2 — storage | ✔ 4456.659315422355 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 103 rows · ≠ 48 vs 51 columns · ✔ 166 nonzeros | ✔ 103 rows, 2 negated | ✔ 26 equal · ≠ 4 recorded |
| Rung 3 — expansion | ✔ 7633.908502024291 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it; tech_capacity_expansion_limit 5 vs 1+2+2 — one block per sense — ==, <=, >= — where PyPSA writes one row per labelled constraint whatever its sense; transmission_expansion_cost_limit 2 vs 1+1 — one block per sense — ==, <=, >= — where PyPSA writes one row per labelled constraint whatever its sense |
✔ 184 rows · ≠ 73 vs 76 columns · ✔ 328 nonzeros | ✔ 184 rows, 4 negated | ✔ 57 equal, 2 split · ≠ 4 recorded |
| Rung 4 — ramps | ✔ 8785.0 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 64 rows · ≠ 20 vs 23 columns · ✔ 92 nonzeros | ✔ 64 rows, 2 negated | ✔ 11 equal · ≠ 4 recorded |
| Rung 5 — global constraints | ✔ 10282.833333333332 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it; operational_limit 3 vs 1+1+1 — one block per sense — ==, <=, >= — where PyPSA writes one row per labelled constraint whatever its sense; primary_energy 3 vs 1+1+1 — one block per sense — ==, <=, >= — where PyPSA writes one row per labelled constraint whatever its sense |
✔ 102 rows · ≠ 44 vs 47 columns · ✔ 190 nonzeros | ✔ 102 rows, 2 negated | ✔ 22 equal, 2 split · ≠ 4 recorded |
| Rung 6 — KVL | ✔ 23962.0 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it; transmission_expansion_cost_limit 2 vs 1+1 — one block per sense — ==, <=, >= — where PyPSA writes one row per labelled constraint whatever its sense; transmission_volume_expansion_limit 2 vs 1+1 — one block per sense — ==, <=, >= — where PyPSA writes one row per labelled constraint whatever its sense |
✔ 123 rows · ≠ 42 vs 45 columns · ✔ 204 nonzeros | ✔ 123 rows | ✔ 19 equal, 2 split · ≠ 4 recorded |
| Rung 7 — commitment | ✔ 7775.0 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 116 rows · ≠ 44 vs 47 columns · ✔ 237 nonzeros | — integer model, no duals | ✔ 19 equal · ≠ 7 recorded |
| Rung 8 — modular and big-M | ✔ 15915.0 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 191 rows · ≠ 80 vs 83 columns · ✔ 379 nonzeros | — integer model, no duals | ✔ 35 equal · ≠ 4 recorded |
| Rung 9 — a multi-link with four output ports | ✔ 11714.4 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 92 rows · ≠ 32 vs 35 columns · ✔ 116 nonzeros | ✔ 92 rows | ✔ 7 equal · ≠ 4 recorded |
| Rung 10 — quadratic costs | ✔ 12587.437500000098 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 60 rows · ≠ 24 vs 27 columns · ✔ 80 nonzeros | ✔ 60 rows | ✔ 8 equal · ≠ 3 recorded |
Rung 11 — PyPSA's own ac_dc_meshed example, whole |
✔ 18441021.477729 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it; objective_constant 1 vs 0 — PyPSA carries a nonzero objective constant as a fixed variable of that name; the file states no constant, and the objective is compared net of it |
✔ 468 rows · ≠ 188 vs 190 columns · ✔ 1007 nonzeros | ✔ 468 rows | ✔ 18 equal · ≠ 4 recorded |
| Rung 12 — linearized unit commitment | ✔ 7775.0 |
✔ 21 constraints · 5 variables, name for name | ✔ 128 rows · ✔ 44 columns · ✔ 288 nonzeros | ≠ 128 rows, 1 negated, Generator-com-status-min_up_time_must_stay_up off by 490.0 — degenerate with PyPSA's duplicated cap — a unit held on has status pinned at 1 by this row from below and by the variable bound and the cap row from above, and HiGHS may put the dual on any of them; Generator-status-p-fixed-upper off by 1447.5 — PyPSA bounds the status at 1 on the variable and writes the cap row as well, so a binding cap's dual may sit on the bound and leave the row at zero; the file states the row alone (rung 12, the linearized relaxation) |
✔ 24 equal · ≠ 3 recorded |
| Rung 13 — transmission losses in tangent form | ✔ 10805.29588 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it; objective_constant 1 vs 0 — PyPSA carries a nonzero objective constant as a fixed variable of that name; the file states no constant, and the objective is compared net of it |
✔ 150 rows · ≠ 46 vs 48 columns · ✔ 290 nonzeros | ✔ 150 rows | ✔ 22 equal · ≠ 4 recorded |
| Rung 14 — two futures and a risk preference | ✔ 9267.386667 |
✔ 12 constraints · 6 variables, name for name | ✔ 87 rows · ✔ 37 columns · ✔ 148 nonzeros | ✔ 87 rows | ◌ cannot build yet: OracleCannotBuildError |
| Rung 15 — two investment periods | ✔ 12747.191096 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 80 rows · ≠ 31 vs 34 columns · ✔ 117 nonzeros | ✔ 80 rows | ✔ 13 equal · ≠ 4 recorded |
| Rung 16 — link delay | ✔ 5262.5 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 52 rows · ≠ 20 vs 23 columns · ✔ 67 nonzeros | ✔ 52 rows | ✔ 7 equal · ≠ 4 recorded |
| Rung 17 — process | ✔ 9730.0 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 70 rows · ≠ 25 vs 28 columns · ✔ 108 nonzeros | ✔ 70 rows | ✔ 19 equal · ≠ 4 recorded |
| Rung 18 — transformer | ✔ 13354.401472 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it; objective_constant 1 vs 0 — PyPSA carries a nonzero objective constant as a fixed variable of that name; the file states no constant, and the objective is compared net of it |
✔ 106 rows · ≠ 39 vs 41 columns · ✔ 170 nonzeros | ✔ 106 rows | ✔ 21 equal · ≠ 4 recorded |
| Rung 19 — transmission losses in secant form | ✔ 11000.926895 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it; objective_constant 1 vs 0 — PyPSA carries a nonzero objective constant as a fixed variable of that name; the file states no constant, and the objective is compared net of it |
✔ 150 rows · ≠ 46 vs 48 columns · ✔ 290 nonzeros | ✔ 150 rows | ✔ 20 equal · ≠ 4 recorded |
| Rung 20 — phase shifter | ✔ 16455.0 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 88 rows · ≠ 36 vs 39 columns · ✔ 128 nonzeros | ✔ 88 rows | ✔ 15 equal · ≠ 4 recorded |
| Rung 21 — a carrier's growth limit binds its non-generator builds | ✔ 8452.5 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 74 rows · ≠ 35 vs 38 columns · ✔ 134 nonzeros | ✔ 74 rows, 2 negated | ✔ 26 equal · ≠ 4 recorded |
| Rung 22 — transformer losses in tangent form | ✔ 10803.477135 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it; objective_constant 1 vs 0 — PyPSA carries a nonzero objective constant as a fixed variable of that name; the file states no constant, and the objective is compared net of it |
✔ 174 rows · ≠ 46 vs 48 columns · ✔ 338 nonzeros | ✔ 174 rows | ✔ 35 equal · ≠ 4 recorded |
| Rung 23 — transformer losses in secant form | ✔ 10981.999155 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it; Line-loss_secants-neg 4 vs 8 — one segment dimension for lines and transformers, where PyPSA places secants per component — the component with fewer secants takes the zero cut, loss >= 0, at the segments it lacks (rung 23); Line-loss_secants-pos 4 vs 8 — one segment dimension for lines and transformers, where PyPSA places secants per component — the component with fewer secants takes the zero cut, loss >= 0, at the segments it lacks (rung 23); objective_constant 1 vs 0 — PyPSA carries a nonzero objective constant as a fixed variable of that name; the file states no constant, and the objective is compared net of it |
≠ 142 vs 150 rows · ≠ 46 vs 48 columns · ≠ 274 vs 282 nonzeros | ✔ 142 rows | ✔ 25 equal · ≠ 6 recorded |
| Rung 24 — must stay down | ✔ 9007.5 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 65 rows · ≠ 28 vs 31 columns · ✔ 107 nonzeros | — integer model, no duals | ✔ 16 equal · ≠ 7 recorded |
| Rung 25 — committable links | ✔ 14013.0 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 235 rows · ≠ 95 vs 98 columns · ✔ 507 nonzeros | — integer model, no duals | ✔ 41 equal · ≠ 4 recorded |
| Rung 26 — committable processes | ✔ 15956.125 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 235 rows · ≠ 95 vs 98 columns · ✔ 507 nonzeros | — integer model, no duals | ✔ 42 equal · ≠ 4 recorded |
| Rung 27 — modular ramps | ✔ 45469.5 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 161 rows · ≠ 70 vs 73 columns · ✔ 354 nonzeros | — integer model, no duals | ✔ 59 equal · ≠ 4 recorded |
| Rung 28 — a start-up ramp alone | ✔ 83283.0 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 152 rows · ≠ 64 vs 67 columns · ✔ 306 nonzeros | — integer model, no duals | ✔ 35 equal · ≠ 13 recorded |
| Rung 29 — storage per investment period | ✔ 7438.461538 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 212 rows · ≠ 96 vs 99 columns · ✔ 356 nonzeros | ✔ 212 rows, 3 negated | ✔ 21 equal · ≠ 4 recorded |
| Rung 30 — security-constrained | ✔ 22113.333333 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 240 rows · ≠ 46 vs 49 columns · ✔ 412 nonzeros | ✔ 240 rows | ✔ 32 equal · ≠ 4 recorded |
| Rung 32 — storage that stands in one period only | ✔ 7230.486698 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 92 rows · ≠ 44 vs 47 columns · ✔ 143 nonzeros | ✔ 92 rows, 2 negated | ✔ 19 equal · ≠ 4 recorded |
| Rung 33 — maintenance | ✔ 7367.560185 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 179 rows · ≠ 103 vs 106 columns · ✔ 398 nonzeros | — integer model, no duals | ✔ 55 equal · ≠ 4 recorded |
| Rung 34 — maintenance of committable units | ✔ 6420.048611 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 503 rows · ≠ 277 vs 280 columns · ✔ 1153 nonzeros | — integer model, no duals | ✔ 131 equal · ≠ 4 recorded |
| Rung 35 — a global constraint for one investment period | ✔ 4886.764706 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 155 rows · ≠ 72 vs 75 columns · ✔ 266 nonzeros | ✔ 155 rows, 2 negated | ✔ 21 equal · ≠ 4 recorded |
| Rung 36 — quadratic costs on a process, a storage unit and a store | ✔ 19185.241281 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 84 rows · ≠ 36 vs 39 columns · ✔ 126 nonzeros | ✔ 84 rows, 2 negated | ✔ 26 equal · ≠ 3 recorded |
| Rung 37 — storage dispatch pinned | ✔ 6395.833333 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 76 rows · ≠ 32 vs 35 columns · ✔ 114 nonzeros | ✔ 76 rows, 2 negated | ✔ 25 equal · ≠ 4 recorded |
| Rung 38 — delays per investment period | ✔ 12918.75 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 104 rows · ≠ 40 vs 43 columns · ✔ 134 nonzeros | ✔ 104 rows | ✔ 10 equal · ≠ 4 recorded |
| Rung 39 — a negative relative growth adds nothing to a carrier's growth limit | ✔ 8600.0 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 44 rows · ≠ 22 vs 25 columns · ✔ 70 nonzeros | ✔ 44 rows, 1 negated | ✔ 13 equal · ≠ 4 recorded |
| Rung 40 — a global constraint takes its own constant and sense in each scenario | ✔ 15106.666667 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 2 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it; primary_energy 2 vs 1+1 — one block per sense — ==, <=, >= — where PyPSA writes one row per labelled constraint whatever its sense |
✔ 86 rows · ≠ 33 vs 37 columns · ✔ 140 nonzeros | ✔ 86 rows | ✔ 13 equal, 1 split · ≠ 4 recorded |
| Rung 41 — a unit's cost and a link's efficiency differ by scenario | ✔ 17964.0 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 2 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 96 rows · ≠ 40 vs 44 columns · ✔ 136 nonzeros | ✔ 96 rows | ✔ 7 equal · ≠ 4 recorded |
| Rung 42 — an extendable unit's capital cost and build cap differ by scenario | ✔ 5050.0 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 2 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 84 rows · ≠ 33 vs 37 columns · ✔ 116 nonzeros | ✔ 84 rows | ✔ 12 equal · ≠ 4 recorded |
Rung 43 — a component's sign turns its term in the bus balance around |
✔ 2125.0 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 80 rows · ≠ 36 vs 39 columns · ✔ 122 nonzeros | ✔ 80 rows, 2 negated | ✔ 22 equal · ≠ 4 recorded |
| Rung 44 — linearized commitment with the rows the integer file has | ✔ 7400.0 |
✔ 21 constraints · 8 variables, name for name | ✔ 191 rows · ✔ 100 columns · ✔ 378 nonzeros | — integer model, no duals | ✔ 27 equal · ≠ 3 recorded |
| Rung 45 — ramp limits per snapshot | ✔ 10996.0 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 83 rows · ≠ 28 vs 31 columns · ✔ 126 nonzeros | ✔ 83 rows, 2 negated | ✔ 16 equal · ≠ 4 recorded |
| Rung 46 — the output brought in | ✔ 101294.125 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 200 rows · ≠ 66 vs 69 columns · ✔ 387 nonzeros | — integer model, no duals | ✔ 36 equal · ≠ 7 recorded |
| Rung 47 — the relaxed file's ramps and signs | ✔ 12862.5 |
✔ 18 constraints · 5 variables, name for name | ✔ 179 rows · ✔ 72 columns · ✔ 353 nonzeros | ≠ 179 rows, 1 negated, Generator-status-p-fixed-upper off by 560.0 — PyPSA bounds the status at 1 on the variable and writes the cap row as well, so a binding cap's dual may sit on the bound and leave the row at zero; the file states the row alone (rung 12, the linearized relaxation) |
✔ 21 equal · ≠ 3 recorded |
| Rung 48 — a start and a stop cost what they cost | ✔ 7325.0 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 72 rows · ≠ 36 vs 39 columns · ✔ 118 nonzeros | — integer model, no duals | ✔ 11 equal · ≠ 7 recorded |
Rung 49 — a carrier's growth limit without multi_investment_periods |
✔ 335.0 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 42 rows · ≠ 17 vs 20 columns · ✔ 58 nonzeros | ✔ 42 rows | ✔ 12 equal · ≠ 4 recorded |
Rung 50 — a load that is not active |
✔ 7380.0 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 32 rows · ≠ 12 vs 15 columns · ✔ 40 nonzeros | ✔ 32 rows | ✔ 7 equal · ≠ 4 recorded |
| Rung 51 — a carrier's growth limit counts an asset in the first period it stands in only, not again after it retires | ✔ 3432.5 |
— PyPSA 1.3.0 solves this network wrongly, PyPSA/PyPSA#1938; the objective is held to the oracle | — | — | ◌ cannot build yet: PyPSA/PyPSA#1938 — PyPSA 1.3.0 gets this network wrong, so the objective is held to the oracle and no model is compared |
Rung 52 — a transmission_expansion_cost_limit row holds in every scenario |
✔ 15630.0 |
— PyPSA 1.3.0 solves this network wrongly, PyPSA/PyPSA#1939; the objective is held to the oracle | — | — | ◌ cannot build yet: PyPSA/PyPSA#1939 — PyPSA 1.3.0 gets this network wrong, so the objective is held to the oracle and no model is compared |
Rung 53 — a transmission_volume_expansion_limit row holds in every scenario under multi_investment_periods |
✔ 15005.0 |
— PyPSA 1.3.0 solves this network wrongly, PyPSA/PyPSA#1939; the objective is held to the oracle | — | — | ◌ cannot build yet: PyPSA/PyPSA#1939 — PyPSA 1.3.0 gets this network wrong, so the objective is held to the oracle and no model is compared |
Rung 54 — each scenario delays a link's and a process's flow by its own delay |
✔ 9190.0 |
— PyPSA 1.3.0 solves this network wrongly, PyPSA/PyPSA#1941; the objective is held to the oracle | — | — | ◌ cannot build yet: PyPSA/PyPSA#1941 — PyPSA 1.3.0 gets this network wrong, so the objective is held to the oracle and no model is compared |
| Rung 55 — a cycle of two transformers takes its Kirchhoff voltage row in every scenario | ✔ 13105.0 |
— PyPSA 1.3.0 solves this network wrongly, PyPSA/PyPSA#1942; the objective is held to the oracle | — | — | ◌ cannot build yet: PyPSA/PyPSA#1942 — PyPSA 1.3.0 gets this network wrong, so the objective is held to the oracle and no model is compared |
| Rung 56 — a security-constrained run over scenarios copies its rows into every scenario | ✔ 18205.0 |
— PyPSA 1.3.0 solves this network wrongly, PyPSA/PyPSA#1942; the objective is held to the oracle | — | — | ◌ cannot build yet: PyPSA/PyPSA#1942 — PyPSA 1.3.0 gets this network wrong, so the objective is held to the oracle and no model is compared |
Rung 57 — p_nom_set pins an extendable build on a network with scenarios |
✔ 4800.0 |
— PyPSA 1.3.0 solves this network wrongly, PyPSA/PyPSA#1942; the objective is held to the oracle | — | — | ◌ cannot build yet: PyPSA/PyPSA#1942 — PyPSA 1.3.0 gets this network wrong, so the objective is held to the oracle and no model is compared |
| Rung 58 — a committable unit takes its status rows in every scenario | ✔ 7430.0 |
— PyPSA 1.3.0 solves this network wrongly, PyPSA/PyPSA#1913; the objective is held to the oracle | — | — | ◌ cannot build yet: PyPSA/PyPSA#1913 — PyPSA 1.3.0 gets this network wrong, so the objective is held to the oracle and no model is compared |
| Rung 60 — efficiencies per snapshot | ✔ 130562.094184 |
≠ CVaR 0 vs 1 — the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it; CVaR-a 0 vs 1 — the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it; CVaR-theta 0 vs 1 — the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it |
✔ 133 rows · ≠ 52 vs 55 columns · ✔ 204 nonzeros | ✔ 133 rows, 1 negated | ✔ 28 equal · ≠ 4 recorded |
The four comparisons¶
Both sides start from one object, the network the rung's script builds. PyPSA solves it directly; specsolve solves the file attached to the tables prep.py makes of it.
| column | specsolve | PyPSA | identical means |
|---|---|---|---|
| objective | result.objective |
n.objective + n.objective_constant |
equal, relative 1e-9 |
| structure | len(result.activity(block)), len(result.primal(variable)) |
rows and columns of n.model per name, masked labels excluded |
one block per PyPSA name, equal count — a split counts as a difference |
| size | diagnostics() rows, columns, nonzeros |
n.model.solver_model rows, columns, nonzeros |
the model handed to HiGHS is the same size on both sides |
| duals | result.dual(block) |
n.model.constraints[name].dual |
every row's dual equal, absolute 1e-6 — against the negative where the file writes the row negated; an integer model has none |
| linopy lane | the test oracle, tests/linopy_lane |
n.optimize.create_model() |
label for label: coefficients, sense, right-hand side, bounds, integrality, objective terms |
Both sides solve one object, the network the rung's script builds — PyPSA directly, specsolve through the rung's own spec attached to the tables prep.py makes of it. A difference in structure, duals or the linopy lane is allowed only with a reason in differential/pypsa/deviations.yaml; the runner fails on one recorded nowhere and on a reason no rung needs. A rung the linopy lane cannot build yet names the blocker instead. Not compared: primals (an optimum need not be unique).
One kind of reason is checked rather than excused. Where the file states a row as PyPSA writes it negated — a storage balance with the charge on the left, a ramp written the other way about — the dual is the negative of PyPSA's, exactly, so the runner compares it against the negative at the same tolerance and the claim is under test. Those names are marked negated below; a difference surviving the negation is red.
Recorded deviations¶
| PyPSA name (comparison) | why specsolve differs | on rungs |
|---|---|---|
CVaR (linopy lane) |
the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it | Rung 1 — transport, Rung 2 — storage, Rung 3 — expansion, Rung 4 — ramps, Rung 5 — global constraints, Rung 6 — KVL, Rung 7 — commitment, Rung 8 — modular and big-M, Rung 9 — a multi-link with four output ports, Rung 10 — quadratic costs, Rung 11 — PyPSA's own ac_dc_meshed example, whole, Rung 13 — transmission losses in tangent form, Rung 15 — two investment periods, Rung 16 — link delay, Rung 17 — process, Rung 18 — transformer, Rung 19 — transmission losses in secant form, Rung 20 — phase shifter, Rung 21 — a carrier's growth limit binds its non-generator builds, Rung 22 — transformer losses in tangent form, Rung 23 — transformer losses in secant form, Rung 24 — must stay down, Rung 25 — committable links, Rung 26 — committable processes, Rung 27 — modular ramps, Rung 28 — a start-up ramp alone, Rung 29 — storage per investment period, Rung 30 — security-constrained, Rung 32 — storage that stands in one period only, Rung 33 — maintenance, Rung 34 — maintenance of committable units, Rung 35 — a global constraint for one investment period, Rung 36 — quadratic costs on a process, a storage unit and a store, Rung 37 — storage dispatch pinned, Rung 38 — delays per investment period, Rung 39 — a negative relative growth adds nothing to a carrier's growth limit, Rung 40 — a global constraint takes its own constant and sense in each scenario, Rung 41 — a unit's cost and a link's efficiency differ by scenario, Rung 42 — an extendable unit's capital cost and build cap differ by scenario, Rung 43 — a component's sign turns its term in the bus balance around, Rung 45 — ramp limits per snapshot, Rung 46 — the output brought in, Rung 48 — a start and a stop cost what they cost, Rung 49 — a carrier's growth limit without multi_investment_periods, Rung 50 — a load that is not active, Rung 60 — efficiencies per snapshot |
CVaR (structure) |
the file declares the tail's average on every run; PyPSA adds it only under a risk preference, and without one the objective prices it at zero and no row reads it | Rung 1 — transport, Rung 2 — storage, Rung 3 — expansion, Rung 4 — ramps, Rung 5 — global constraints, Rung 6 — KVL, Rung 7 — commitment, Rung 8 — modular and big-M, Rung 9 — a multi-link with four output ports, Rung 10 — quadratic costs, Rung 11 — PyPSA's own ac_dc_meshed example, whole, Rung 13 — transmission losses in tangent form, Rung 15 — two investment periods, Rung 16 — link delay, Rung 17 — process, Rung 18 — transformer, Rung 19 — transmission losses in secant form, Rung 20 — phase shifter, Rung 21 — a carrier's growth limit binds its non-generator builds, Rung 22 — transformer losses in tangent form, Rung 23 — transformer losses in secant form, Rung 24 — must stay down, Rung 25 — committable links, Rung 26 — committable processes, Rung 27 — modular ramps, Rung 28 — a start-up ramp alone, Rung 29 — storage per investment period, Rung 30 — security-constrained, Rung 32 — storage that stands in one period only, Rung 33 — maintenance, Rung 34 — maintenance of committable units, Rung 35 — a global constraint for one investment period, Rung 36 — quadratic costs on a process, a storage unit and a store, Rung 37 — storage dispatch pinned, Rung 38 — delays per investment period, Rung 39 — a negative relative growth adds nothing to a carrier's growth limit, Rung 40 — a global constraint takes its own constant and sense in each scenario, Rung 41 — a unit's cost and a link's efficiency differ by scenario, Rung 42 — an extendable unit's capital cost and build cap differ by scenario, Rung 43 — a component's sign turns its term in the bus balance around, Rung 45 — ramp limits per snapshot, Rung 46 — the output brought in, Rung 48 — a start and a stop cost what they cost, Rung 49 — a carrier's growth limit without multi_investment_periods, Rung 50 — a load that is not active, Rung 60 — efficiencies per snapshot |
CVaR-a (linopy lane) |
the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it | Rung 1 — transport, Rung 2 — storage, Rung 3 — expansion, Rung 4 — ramps, Rung 5 — global constraints, Rung 6 — KVL, Rung 7 — commitment, Rung 8 — modular and big-M, Rung 9 — a multi-link with four output ports, Rung 10 — quadratic costs, Rung 11 — PyPSA's own ac_dc_meshed example, whole, Rung 13 — transmission losses in tangent form, Rung 15 — two investment periods, Rung 16 — link delay, Rung 17 — process, Rung 18 — transformer, Rung 19 — transmission losses in secant form, Rung 20 — phase shifter, Rung 21 — a carrier's growth limit binds its non-generator builds, Rung 22 — transformer losses in tangent form, Rung 23 — transformer losses in secant form, Rung 24 — must stay down, Rung 25 — committable links, Rung 26 — committable processes, Rung 27 — modular ramps, Rung 28 — a start-up ramp alone, Rung 29 — storage per investment period, Rung 30 — security-constrained, Rung 32 — storage that stands in one period only, Rung 33 — maintenance, Rung 34 — maintenance of committable units, Rung 35 — a global constraint for one investment period, Rung 36 — quadratic costs on a process, a storage unit and a store, Rung 37 — storage dispatch pinned, Rung 38 — delays per investment period, Rung 39 — a negative relative growth adds nothing to a carrier's growth limit, Rung 40 — a global constraint takes its own constant and sense in each scenario, Rung 41 — a unit's cost and a link's efficiency differ by scenario, Rung 42 — an extendable unit's capital cost and build cap differ by scenario, Rung 43 — a component's sign turns its term in the bus balance around, Rung 45 — ramp limits per snapshot, Rung 46 — the output brought in, Rung 48 — a start and a stop cost what they cost, Rung 49 — a carrier's growth limit without multi_investment_periods, Rung 50 — a load that is not active, Rung 60 — efficiencies per snapshot |
CVaR-a (structure) |
the file declares each scenario's excess on every run; PyPSA adds it only under a risk preference, and without one no row reads it | Rung 1 — transport, Rung 2 — storage, Rung 3 — expansion, Rung 4 — ramps, Rung 5 — global constraints, Rung 6 — KVL, Rung 7 — commitment, Rung 8 — modular and big-M, Rung 9 — a multi-link with four output ports, Rung 10 — quadratic costs, Rung 11 — PyPSA's own ac_dc_meshed example, whole, Rung 13 — transmission losses in tangent form, Rung 15 — two investment periods, Rung 16 — link delay, Rung 17 — process, Rung 18 — transformer, Rung 19 — transmission losses in secant form, Rung 20 — phase shifter, Rung 21 — a carrier's growth limit binds its non-generator builds, Rung 22 — transformer losses in tangent form, Rung 23 — transformer losses in secant form, Rung 24 — must stay down, Rung 25 — committable links, Rung 26 — committable processes, Rung 27 — modular ramps, Rung 28 — a start-up ramp alone, Rung 29 — storage per investment period, Rung 30 — security-constrained, Rung 32 — storage that stands in one period only, Rung 33 — maintenance, Rung 34 — maintenance of committable units, Rung 35 — a global constraint for one investment period, Rung 36 — quadratic costs on a process, a storage unit and a store, Rung 37 — storage dispatch pinned, Rung 38 — delays per investment period, Rung 39 — a negative relative growth adds nothing to a carrier's growth limit, Rung 40 — a global constraint takes its own constant and sense in each scenario, Rung 41 — a unit's cost and a link's efficiency differ by scenario, Rung 42 — an extendable unit's capital cost and build cap differ by scenario, Rung 43 — a component's sign turns its term in the bus balance around, Rung 45 — ramp limits per snapshot, Rung 46 — the output brought in, Rung 48 — a start and a stop cost what they cost, Rung 49 — a carrier's growth limit without multi_investment_periods, Rung 50 — a load that is not active, Rung 60 — efficiencies per snapshot |
CVaR-theta (linopy lane) |
the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it | Rung 1 — transport, Rung 2 — storage, Rung 3 — expansion, Rung 4 — ramps, Rung 5 — global constraints, Rung 6 — KVL, Rung 7 — commitment, Rung 8 — modular and big-M, Rung 9 — a multi-link with four output ports, Rung 10 — quadratic costs, Rung 11 — PyPSA's own ac_dc_meshed example, whole, Rung 13 — transmission losses in tangent form, Rung 15 — two investment periods, Rung 16 — link delay, Rung 17 — process, Rung 18 — transformer, Rung 19 — transmission losses in secant form, Rung 20 — phase shifter, Rung 21 — a carrier's growth limit binds its non-generator builds, Rung 22 — transformer losses in tangent form, Rung 23 — transformer losses in secant form, Rung 24 — must stay down, Rung 25 — committable links, Rung 26 — committable processes, Rung 27 — modular ramps, Rung 28 — a start-up ramp alone, Rung 29 — storage per investment period, Rung 30 — security-constrained, Rung 32 — storage that stands in one period only, Rung 33 — maintenance, Rung 34 — maintenance of committable units, Rung 35 — a global constraint for one investment period, Rung 36 — quadratic costs on a process, a storage unit and a store, Rung 37 — storage dispatch pinned, Rung 38 — delays per investment period, Rung 39 — a negative relative growth adds nothing to a carrier's growth limit, Rung 40 — a global constraint takes its own constant and sense in each scenario, Rung 41 — a unit's cost and a link's efficiency differ by scenario, Rung 42 — an extendable unit's capital cost and build cap differ by scenario, Rung 43 — a component's sign turns its term in the bus balance around, Rung 45 — ramp limits per snapshot, Rung 46 — the output brought in, Rung 48 — a start and a stop cost what they cost, Rung 49 — a carrier's growth limit without multi_investment_periods, Rung 50 — a load that is not active, Rung 60 — efficiencies per snapshot |
CVaR-theta (structure) |
the file declares the tail's start on every run; PyPSA adds it only under a risk preference, and without one no row reads it | Rung 1 — transport, Rung 2 — storage, Rung 3 — expansion, Rung 4 — ramps, Rung 5 — global constraints, Rung 6 — KVL, Rung 7 — commitment, Rung 8 — modular and big-M, Rung 9 — a multi-link with four output ports, Rung 10 — quadratic costs, Rung 11 — PyPSA's own ac_dc_meshed example, whole, Rung 13 — transmission losses in tangent form, Rung 15 — two investment periods, Rung 16 — link delay, Rung 17 — process, Rung 18 — transformer, Rung 19 — transmission losses in secant form, Rung 20 — phase shifter, Rung 21 — a carrier's growth limit binds its non-generator builds, Rung 22 — transformer losses in tangent form, Rung 23 — transformer losses in secant form, Rung 24 — must stay down, Rung 25 — committable links, Rung 26 — committable processes, Rung 27 — modular ramps, Rung 28 — a start-up ramp alone, Rung 29 — storage per investment period, Rung 30 — security-constrained, Rung 32 — storage that stands in one period only, Rung 33 — maintenance, Rung 34 — maintenance of committable units, Rung 35 — a global constraint for one investment period, Rung 36 — quadratic costs on a process, a storage unit and a store, Rung 37 — storage dispatch pinned, Rung 38 — delays per investment period, Rung 39 — a negative relative growth adds nothing to a carrier's growth limit, Rung 40 — a global constraint takes its own constant and sense in each scenario, Rung 41 — a unit's cost and a link's efficiency differ by scenario, Rung 42 — an extendable unit's capital cost and build cap differ by scenario, Rung 43 — a component's sign turns its term in the bus balance around, Rung 45 — ramp limits per snapshot, Rung 46 — the output brought in, Rung 48 — a start and a stop cost what they cost, Rung 49 — a carrier's growth limit without multi_investment_periods, Rung 50 — a load that is not active, Rung 60 — efficiencies per snapshot |
Generator-com-status-min_up_time_must_stay_up (duals) |
degenerate with PyPSA's duplicated cap — a unit held on has status pinned at 1 by this row from below and by the variable bound and the cap row from above, and HiGHS may put the dual on any of them | Rung 12 — linearized unit commitment |
Generator-p-ramp_limit_down (duals, negated) |
the initial-snapshot block states −p on the left where PyPSA writes p[t] − p[t−1] ≥ −rd·p_nom — the same row negated, so its dual is the negative | Rung 3 — expansion, Rung 4 — ramps, Rung 12 — linearized unit commitment, Rung 29 — storage per investment period, Rung 45 — ramp limits per snapshot, Rung 47 — the relaxed file's ramps and signs |
Generator-shut_down (linopy lane) |
the cap is a row here, where PyPSA bounds the variable at 1 as well — a 0-1 binary, or a share in [0, 1] under the linearized relaxation | Rung 7 — commitment, Rung 12 — linearized unit commitment, Rung 24 — must stay down, Rung 28 — a start-up ramp alone, Rung 44 — linearized commitment with the rows the integer file has, Rung 46 — the output brought in, Rung 47 — the relaxed file's ramps and signs, Rung 48 — a start and a stop cost what they cost |
Generator-start_up (linopy lane) |
the cap is a row here, where PyPSA bounds the variable at 1 as well — a 0-1 binary, or a share in [0, 1] under the linearized relaxation | Rung 7 — commitment, Rung 12 — linearized unit commitment, Rung 24 — must stay down, Rung 28 — a start-up ramp alone, Rung 44 — linearized commitment with the rows the integer file has, Rung 46 — the output brought in, Rung 47 — the relaxed file's ramps and signs, Rung 48 — a start and a stop cost what they cost |
Generator-status (linopy lane) |
the cap is a row here, where PyPSA bounds the variable at 1 as well — a 0-1 binary, or a share in [0, 1] under the linearized relaxation | Rung 7 — commitment, Rung 12 — linearized unit commitment, Rung 24 — must stay down, Rung 28 — a start-up ramp alone, Rung 44 — linearized commitment with the rows the integer file has, Rung 46 — the output brought in, Rung 47 — the relaxed file's ramps and signs, Rung 48 — a start and a stop cost what they cost |
Generator-status-p-fixed-upper (duals) |
PyPSA bounds the status at 1 on the variable and writes the cap row as well, so a binding cap's dual may sit on the bound and leave the row at zero; the file states the row alone (rung 12, the linearized relaxation) | Rung 12 — linearized unit commitment, Rung 47 — the relaxed file's ramps and signs |
Line-loss_secants-neg (linopy lane) |
one segment dimension for lines and transformers, where PyPSA places secants per component — the component with fewer secants takes the zero cut, loss >= 0, at the segments it lacks (rung 23) | Rung 23 — transformer losses in secant form |
Line-loss_secants-neg (structure) |
one segment dimension for lines and transformers, where PyPSA places secants per component — the component with fewer secants takes the zero cut, loss >= 0, at the segments it lacks (rung 23) | Rung 23 — transformer losses in secant form |
Line-loss_secants-pos (linopy lane) |
one segment dimension for lines and transformers, where PyPSA places secants per component — the component with fewer secants takes the zero cut, loss >= 0, at the segments it lacks (rung 23) | Rung 23 — transformer losses in secant form |
Line-loss_secants-pos (structure) |
one segment dimension for lines and transformers, where PyPSA places secants per component — the component with fewer secants takes the zero cut, loss >= 0, at the segments it lacks (rung 23) | Rung 23 — transformer losses in secant form |
Link-p-ramp_limit_down (duals, negated) |
the file states the ramp as p[t-1] - p[t] <= rd * p_nom where PyPSA writes p[t] - p[t-1] >= -rd * p_nom — the same row negated, so its dual is the negative | Rung 3 — expansion, Rung 4 — ramps, Rung 45 — ramp limits per snapshot |
Link-shut_down (linopy lane) |
the cap is a row here, where PyPSA bounds the variable at 1 as well — a 0-1 binary, or a share in [0, 1] under the linearized relaxation | Rung 28 — a start-up ramp alone |
Link-start_up (linopy lane) |
the cap is a row here, where PyPSA bounds the variable at 1 as well — a 0-1 binary, or a share in [0, 1] under the linearized relaxation | Rung 28 — a start-up ramp alone |
Link-status (linopy lane) |
the cap is a row here, where PyPSA bounds the variable at 1 as well — a 0-1 binary, or a share in [0, 1] under the linearized relaxation | Rung 28 — a start-up ramp alone |
Process-shut_down (linopy lane) |
the cap is a row here, where PyPSA bounds the variable at 1 as well — a 0-1 binary, or a share in [0, 1] under the linearized relaxation | Rung 28 — a start-up ramp alone |
Process-start_up (linopy lane) |
the cap is a row here, where PyPSA bounds the variable at 1 as well — a 0-1 binary, or a share in [0, 1] under the linearized relaxation | Rung 28 — a start-up ramp alone |
Process-status (linopy lane) |
the cap is a row here, where PyPSA bounds the variable at 1 as well — a 0-1 binary, or a share in [0, 1] under the linearized relaxation | Rung 28 — a start-up ramp alone |
StorageUnit-energy_balance (duals, negated) |
the file states the balance with the charge on the left; PyPSA writes −soc there, the same row negated, so its dual is the negative | Rung 2 — storage, Rung 3 — expansion, Rung 5 — global constraints, Rung 21 — a carrier's growth limit binds its non-generator builds, Rung 29 — storage per investment period, Rung 32 — storage that stands in one period only, Rung 35 — a global constraint for one investment period, Rung 36 — quadratic costs on a process, a storage unit and a store, Rung 37 — storage dispatch pinned, Rung 43 — a component's sign turns its term in the bus balance around, Rung 60 — efficiencies per snapshot |
Store-energy_balance (duals, negated) |
the file states the balance with the level on the left; PyPSA writes −e there, the same row negated, so its dual is the negative | Rung 2 — storage, Rung 3 — expansion, Rung 5 — global constraints, Rung 21 — a carrier's growth limit binds its non-generator builds, Rung 29 — storage per investment period, Rung 32 — storage that stands in one period only, Rung 35 — a global constraint for one investment period, Rung 36 — quadratic costs on a process, a storage unit and a store, Rung 37 — storage dispatch pinned, Rung 39 — a negative relative growth adds nothing to a carrier's growth limit, Rung 43 — a component's sign turns its term in the bus balance around |
objective (linopy lane) |
PyPSA's objective carries its fixed objective_constant variable as a term; the file states no constant, and the objective value is compared net of it | Rung 1 — transport, Rung 2 — storage, Rung 3 — expansion, Rung 4 — ramps, Rung 5 — global constraints, Rung 6 — KVL, Rung 7 — commitment, Rung 8 — modular and big-M, Rung 9 — a multi-link with four output ports, Rung 11 — PyPSA's own ac_dc_meshed example, whole, Rung 13 — transmission losses in tangent form, Rung 15 — two investment periods, Rung 16 — link delay, Rung 17 — process, Rung 18 — transformer, Rung 19 — transmission losses in secant form, Rung 20 — phase shifter, Rung 21 — a carrier's growth limit binds its non-generator builds, Rung 22 — transformer losses in tangent form, Rung 23 — transformer losses in secant form, Rung 24 — must stay down, Rung 25 — committable links, Rung 26 — committable processes, Rung 27 — modular ramps, Rung 28 — a start-up ramp alone, Rung 29 — storage per investment period, Rung 30 — security-constrained, Rung 32 — storage that stands in one period only, Rung 33 — maintenance, Rung 34 — maintenance of committable units, Rung 35 — a global constraint for one investment period, Rung 37 — storage dispatch pinned, Rung 38 — delays per investment period, Rung 39 — a negative relative growth adds nothing to a carrier's growth limit, Rung 40 — a global constraint takes its own constant and sense in each scenario, Rung 41 — a unit's cost and a link's efficiency differ by scenario, Rung 42 — an extendable unit's capital cost and build cap differ by scenario, Rung 43 — a component's sign turns its term in the bus balance around, Rung 45 — ramp limits per snapshot, Rung 46 — the output brought in, Rung 48 — a start and a stop cost what they cost, Rung 49 — a carrier's growth limit without multi_investment_periods, Rung 50 — a load that is not active, Rung 60 — efficiencies per snapshot |
objective_constant (structure) |
PyPSA carries a nonzero objective constant as a fixed variable of that name; the file states no constant, and the objective is compared net of it | Rung 11 — PyPSA's own ac_dc_meshed example, whole, Rung 13 — transmission losses in tangent form, Rung 18 — transformer, Rung 19 — transmission losses in secant form, Rung 22 — transformer losses in tangent form, Rung 23 — transformer losses in secant form |
operational_limit (structure) |
one block per sense — ==, <=, >= — where PyPSA writes one row per labelled constraint whatever its sense | Rung 5 — global constraints |
primary_energy (structure) |
one block per sense — ==, <=, >= — where PyPSA writes one row per labelled constraint whatever its sense | Rung 5 — global constraints, Rung 40 — a global constraint takes its own constant and sense in each scenario |
solver model (structure) |
what the file states otherwise than PyPSA writes it — the objective_constant variable's column and row (rungs 11, 13), the build's floor and cap once rather than once per scenario (rung 14) | Rung 1 — transport, Rung 2 — storage, Rung 3 — expansion, Rung 4 — ramps, Rung 5 — global constraints, Rung 6 — KVL, Rung 7 — commitment, Rung 8 — modular and big-M, Rung 9 — a multi-link with four output ports, Rung 10 — quadratic costs, Rung 11 — PyPSA's own ac_dc_meshed example, whole, Rung 13 — transmission losses in tangent form, Rung 15 — two investment periods, Rung 16 — link delay, Rung 17 — process, Rung 18 — transformer, Rung 19 — transmission losses in secant form, Rung 20 — phase shifter, Rung 21 — a carrier's growth limit binds its non-generator builds, Rung 22 — transformer losses in tangent form, Rung 23 — transformer losses in secant form, Rung 24 — must stay down, Rung 25 — committable links, Rung 26 — committable processes, Rung 27 — modular ramps, Rung 28 — a start-up ramp alone, Rung 29 — storage per investment period, Rung 30 — security-constrained, Rung 32 — storage that stands in one period only, Rung 33 — maintenance, Rung 34 — maintenance of committable units, Rung 35 — a global constraint for one investment period, Rung 36 — quadratic costs on a process, a storage unit and a store, Rung 37 — storage dispatch pinned, Rung 38 — delays per investment period, Rung 39 — a negative relative growth adds nothing to a carrier's growth limit, Rung 40 — a global constraint takes its own constant and sense in each scenario, Rung 41 — a unit's cost and a link's efficiency differ by scenario, Rung 42 — an extendable unit's capital cost and build cap differ by scenario, Rung 43 — a component's sign turns its term in the bus balance around, Rung 45 — ramp limits per snapshot, Rung 46 — the output brought in, Rung 48 — a start and a stop cost what they cost, Rung 49 — a carrier's growth limit without multi_investment_periods, Rung 50 — a load that is not active, Rung 60 — efficiencies per snapshot |
tech_capacity_expansion_limit (structure) |
one block per sense — ==, <=, >= — where PyPSA writes one row per labelled constraint whatever its sense | Rung 3 — expansion |
transmission_expansion_cost_limit (structure) |
one block per sense — ==, <=, >= — where PyPSA writes one row per labelled constraint whatever its sense | Rung 3 — expansion, Rung 6 — KVL |
transmission_volume_expansion_limit (structure) |
one block per sense — ==, <=, >= — where PyPSA writes one row per labelled constraint whatever its sense | Rung 6 — KVL |
Not compared, deliberately: primals — an optimum need not be unique. Counted rather than compared: the rows built per block, on each rung's page, and over the whole ladder that every block is built by some rung, every mask is partially true somewhere and every parameter is fed somewhere. The runner fails on a gap not listed in differential/pypsa/untested.txt, and on a listed one a rung now closes; the rungs that would close them are mathspec#830.
Each rung's own model is the file cut to what the rung builds. The runner builds the whole file on the rung's network, cuts it to the rows and columns that build produced, and proves the cut builds the same model, coefficient for coefficient, before anything solves; every comparison above solves the cut.