Reading MATPOWER Input

MATPOWER's case format is the one the standard transmission test cases are published in: the IEEE cases, PEGASE, RTE, PGLib. A case file is a MATLAB function that assigns literal matrices to mpc.baseMVA, mpc.bus, mpc.gen and mpc.branch, and those four are what src/matpower.rs reads.

#![allow(unused)]
fn main() {
let net = gridoxide::matpower::read("case118.m")?;
let report = gridoxide::run_power_flow(
    net.buses, &net.lines, &net.transformers, &net.shunts,
    gridoxide::TapData::none(),
    PowerFlowOptions { init: PowerFlowInit::Flat, tol: 1e-8, ..Default::default() },
);
}
model = gridoxide.PowerFlowModel.from_matpower("case118.m", tol=1e-8)
model.solve(init="flat")
voltages = dict(zip(model.bus_numbers(), model.voltage_mag()))

From the command line, gridoxide solve case118.m. Behind the matpower feature, which has no dependency; the Python bindings always include it.

The problem it solves is MATPOWER's

The import follows runpf's rules, from MATPOWER's bustypes, ext2int and makeSbus/makeYbus, so that a case means here what it means there:

What the case saysWhat is solved
Bus type 3 or 2 with an online generatorReference or PV bus
Bus type 3 or 2 without onePQ bus; if no reference remains, the first PV bus becomes it
A generator on a PQ busA fixed Pg + jQg injection
The voltage a regulated bus holdsIts generators' Vg (the last online one), never the bus's Vm, which MATPOWER reads only as a starting value
The reference angleThe reference bus's Va
Bus type 4 (isolated)Left out with its branches and generators; reported at zero voltage
A branch with a ratio or a phase shiftA two-winding transformer: the π-model with the complex ratio τ·e^{jθ} on the from side, exactly network::branch_calc_param's model
Gs, BsA shunt admittance, per unit

Everything is per unit on the case's own baseMVA, so a mismatch tolerance is the same quantity MATPOWER measures. A PV bus also carries the summed Qmin and Qmax of its generators, used only if reactive limits are asked for.

Why not the PGM conversion

gridoxide.matpower (the Python package's converter) turns a case into PGM JSON, and that cannot carry a MATPOWER case exactly. PGM has no slack bus, only a source behind a short-circuit impedance, so the reference bus sits behind an impedance the case does not have and drifts off its setpoint by however much current it carries. PGM's transformer clock also holds a phase shift only in multiples of 30°, so the PEGASE and RTE phase shifters are rounded. Read directly, neither loss happens.

What it refuses

A case file is MATLAB code, and some cases finish with code that edits the matrices they just wrote: case33bw converts its loads from kW and its impedances from ohms that way. Reading the literals and skipping the code would solve a network 1000 times more heavily loaded, without a word. So an assignment to mpc.bus, mpc.gen, mpc.branch or mpc.baseMVA that is not a literal matrix is an error, and so is a version-1 case or a binary .mat file. benchmark-grids keeps plain-data copies of such cases under matpower-plain/.

How it is tested

tests/matpower_test.rs reads the transmission cases from case14 to case9241pegase and six distribution feeders, solves each from a flat start, and substitutes the solution into the power-flow equations rebuilt from the raw matrices with MATPOWER's own formulas. Every case lands below 1e-7 p.u., and flipping the sign of the phase shift in the importer fails case1354pegase by 0.44 p.u.