ADR-0008: Power flow is a PyPSA run mode over an electrical network in the canonical model; MEME never chains jobs¶
- Status: accepted (amended 2026-10-24)
- Date: 2026-10-24
Context¶
Calliope and AdOpT-NET0 are transport models: energy balances per node, arcs
with a capacity and an efficiency, no Kirchhoff voltage law. PyPSA has what
they lack, an electrical network with pf() and lpf(). The run that adds
information after a Calliope plan is therefore not a second optimization but
a power flow of the planned dispatch on the physical grid: bus voltages, line
and transformer loading, convergence per snapshot.
MEME could not express that run. The canonical Transmission carries
capacity, efficiency, distance and loss but no impedance, rating or voltage
level, so every arc became a controllable PyPSA Link and power_flow was
deliberately not claimed. Every experiment.mode was an optimization and the
generated driver only ever called the optimize family. The bundle held the
solved network and the log, not per-bus or per-branch results.
A branch (feature/power-flow, 2026-10-07) closed the gap with a top-level
power_flow block beside model and experiment: a complete second network
(buses, lines, transformers, fixed injections) that the emitter wrote
instead of the model, with run.py forced into a power-flow mode by the
block's presence. That design hands one framework's result to another
through a private side channel: the block is not part of the canonical
model, bypasses its validation, duplicates the node and technology
vocabulary, and makes the consumer's chaining of Calliope into PyPSA a
hidden MEME feature.
Decision¶
A power flow is an ordinary run of the PyPSA target, configured entirely through the canonical model and experiment, and the API consumer is responsible for the chaining: one call plans on Calliope, a second call hands PyPSA the resulting dispatch. MEME holds no state between the two and reads no result of one job as the input of another.
The canonical model gains three electrical fields, all optional and all power-flow inputs:
node.electrical.v_nom— the nominal voltage (kV) of the node's AC bus.transmission.electrical— makes an arc an AC branch:kindline(series impedancer,xin Ω over the arc) ortransformer(r,xper unit of its rating, optionaltap_ratio), ans_nomrating in MVA that defaults to the arc's installed capacity, andnum_parallel. An electrical arc is bidirectional, a line connects two nodes of equalv_nom.technology.dispatch— the fixed active-power output per timestep (MW) the power flow injects for a supply, conversion or storage technology (PyPSAp_set); demand keepsdemand_profile. It is overridable per node.
The experiment gains the mode power_flow with its own typed block,
experiment.power_flow: method (newton_raphson, the default, seeded by a
linear solve; or linear), slack (the supply technology that balances the
network; required), distribute_slack and tolerance. The mode maps onto
the existing power_flow capability, which only PyPSA claims; every other
target rejects the mode through the generic mode gate. A payload that
carries dispatch in an optimization mode is rejected on every target,
with a pointer to operation.equals_pu, because there the solver decides
dispatch. Electrical fields in an optimization mode, or on a target without
the capability, are reported as unused in a warning and the arcs are emitted
as transport links, so one model serves the planning call and the power-flow
call. In mode power_flow nothing may be a decision: expandable
capacities, trade, constraints, emission limits and scenarios are rejected.
The PyPSA emitter writes the AC network from these fields: the electrical
carrier's buses get carrier AC and v_nom, lines and transformers get
their own tables, generators their p_set and control (Slack for the
named technology, PQ otherwise), storage units and conversion links their
p_set; series dispatch is materialized like every other series. The
driver's power_flow branch runs lpf() then pf() (or lpf() alone),
exports the network, and writes a power_flow/ folder in the run output:
convergence.csv, per-bus voltage magnitude, angle, active and reactive
power, per-line and per-transformer flows and loading, generator dispatch,
and a summary.json. A snapshot that does not converge fails the run (exit
code 3) with the tables still written for diagnosis; the log carries
power flow converged k/n snapshots.
Consequences¶
- The consumer's workflow is two explicit API calls with two payloads it
owns: a planning payload for Calliope (or PyPSA) and a power-flow payload
for PyPSA whose
dispatchseries it fills from the planning result. MEME stays a stateless translator and the canonical model stays the single entry point; nothing arrives through a side channel. - The electrical fields are honest about their reach: the capability matrix
shows
power_flowfor PyPSA only, the warning names every arc and node whose electrical parameters an optimization run ignores, and the validation matrix freezes the verdicts. - The power flow is active-power only in this version: loads at unity power
factor, generators
PQwith zero reactive set-point, the slack bus holding voltage. Reactive power set-points,PVvoltage control, shunt elements and a line-type catalogue are deliberately out of scope; each would be an additive field with its own gate. - Network-constrained optimization (PyPSA Lines with impedance inside
optimize, security-constrained dispatch) is not part of this decision. Optimization modes keep emitting transport links even when electrical parameters are present; claiming KVL inside an optimization would be a new capability with its own record. - The rejected branch stays rejected: a top-level network block would reappear as soon as a consumer wanted a shape the canonical model cannot express. The answer to that is a canonical field with a gate, as here.
Amendments¶
- 2026-10-24. The rejection of
dispatchin optimization modes is superseded by ADR-0009: every power-flow input is ignored with a warning wherever no power flow runs, on Calliope and AdOpT-NET0 and on PyPSA's optimization modes alike, so one payload serves the planning call and the power-flow call. The mode gate is unchanged. ADR-0009 also adds reactive power, voltage control, shunts and fixed transfers to the network described here.