PowerIO guide
PowerIO reads power system data into typed values, writes those values in the formats other tools read, and builds the sparse matrices and graph data that solvers consume. The Rust crates are the implementation, and the Python package, PowerIO.jl, and the C ABI expose the same operations under the same names.
use powerio::{PioValue, parse};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let module = parse("case9.m")?; // PioModule<PioValue>
match module.value() {
PioValue::BalancedNetwork(network) => {
println!("{} buses", network.buses().len());
}
other => println!("{}", other.type_name()),
}
Ok(())
}
import powerio
module = powerio.parse("case9.m") # PioModule[BalancedNetwork]
using PowerIO
module_ = parse("case9.m") # PioModule{BalancedNetwork}
feeder = parse("IEEE13Nodeckt.dss") # PioModule{MulticonductorNetwork}
A parse returns a module, which is one typed value together with the data that explains it: the source descriptions, the source map, the reader’s diagnostics, the derivation history, and, while the process runs, the source bytes themselves. Which value you get depends on what the source declares:
| Source | Value |
|---|---|
| MATPOWER, PSS/E, XIIDM, CGMES, UCTE-DEF, and the other balanced formats | BalancedNetwork |
| OpenDSS, PowerModelsDistribution JSON, BMOPF JSON | MulticonductorNetwork |
| a PyPSA directory whose inputs vary by snapshot | TimeSeries<BalancedNetwork> |
| a GridFM Parquet dataset | ScenarioSet<BalancedNetwork> |
| a DOE GO Challenge 3 problem file | AcScucInstance |
| that problem file beside its solution file | AcScucSolution |
| a DeepMind OPFData file | AcOpfSolution |
a geographic layer document or a PowerWorld .pwd display | GeoLayer |
parse reads a grid exchange format into a module, and emit writes a
module out in one. The other two operations, serialize and deserialize,
write and read PowerIO IR, the JSON document that stores a complete module for
another PowerIO consumer to read back. PowerIO IR is not a grid exchange
format, so parse does not accept it; deserialize does.
Whichever format you use, writing an unchanged module back to its own format reproduces the source bytes byte for byte, and writing to another format keeps what that format can represent and reports each loss as a diagnostic with a stable code. Converting from one kind of value to another is an explicit operation that adds an entry to the module history; nothing converts as a side effect.
To install PowerIO and run a first conversion, start with Getting started. Core concepts defines the module, the value types, diagnostics, and sources. Formats and fidelity says what each reader keeps and each writer reports, and Rust, Python, Julia, and C shows each operation in all four languages.
Getting started
Install
Rust:
cargo add powerio # parsing, emission, PowerIO IR
cargo add powerio -F matrix # and sparse matrices, sensitivities, graph data
Use Rust 1.88 or newer. Add the gridfm feature when your application reads
or writes GridFM Parquet directories: cargo add powerio -F gridfm.
Python:
pip install powerio # parsing, emission, PowerIO IR
pip install 'powerio[all]' # and SciPy matrices, NetworkX graphs, Polars for GridFM
Julia:
using Pkg; Pkg.add("PowerIO")
The powerio command:
cargo install powerio-cli
For C and C++, build the shared library from a checkout and include the checked in header.
git clone https://github.com/eigenergy/powerio
cd powerio
cargo build -p powerio-capi --release --features arrow,matrix,gridfm,dist,prob
# target/release/libpowerio_capi.{so,dylib,dll}; header powerio-capi/include/powerio.h
Parse, inspect, emit
Download case9.m
to your working directory for these examples. In a repository checkout, the
same file is tests/data/case9.m; pass that path when running from the root.
Rust. The example is a complete program; it imports only parse, emit, and
the PioValue enum, and ? hands any failure to main.
use powerio::{PioValue, emit, parse};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let module = parse("case9.m")?;
let PioValue::BalancedNetwork(network) = module.value() else {
panic!("expected a balanced network");
};
println!("{} buses", network.buses().len());
for finding in module.diagnostics() {
eprintln!("{}: {}", finding.code(), finding.message());
}
emit(&module, "matpower", "copy.m")?; // the source bytes, unchanged
let result = emit(&module, "psse", "case9.raw")?;
for finding in result.diagnostics() {
eprintln!("{}", finding.code()); // what PSS/E cannot carry
}
Ok(())
}
Python:
import powerio
module = powerio.parse("case9.m")
network = module.value # BalancedNetwork
for finding in module.diagnostics:
print(finding.code, finding.message)
powerio.emit(module, "matpower", "copy.m") # the source bytes, unchanged
result = powerio.emit(module, "psse") # text in memory
result.text
result.diagnostics
Julia:
using PowerIO
module_ = parse("case9.m")
net = module_.value # BalancedNetwork
length(net.buses) # 9
module_.diagnostics
emit(module_, "matpower", "copy.m") # the source bytes, unchanged
result = emit(module_, "psse") # text in memory
result.diagnostics
Command line:
powerio convert case9.m --to psse -o case9.raw # findings on stderr
powerio summary case9.m # counts, bases, and findings as JSON
Keep a module for later
Use serialize to save the value with its diagnostics and history, then
deserialize to restore it in any PowerIO binding:
powerio::serialize(&module, "case9.pio.json")?;
let restored = powerio::deserialize("case9.pio.json")?;
powerio.serialize(module, "case9.pio.json")
restored = powerio.deserialize("case9.pio.json")
serialize(module_, "case9.pio.json")
restored = deserialize("case9.pio.json")
PowerIO IR stores the typed data and source metadata. Original input bytes stay in memory, so an unchanged parsed module can echo them, while a restored module produces fresh output. Inspect the returned emission diagnostics when exporting to another tool.
Where next
- What each source parses to and what a module contains: Core concepts.
- Balanced transmission cases: Transmission networks.
- Conductor level distribution cases: Distribution networks.
- Matrices, signs, units, and index mappings: Matrices and graphs.
- What each reader keeps and each writer reports: Formats and fidelity.
- The same operations in each language: Rust, Python, Julia, and C.
Core concepts
PowerIO borrows its structure from compiler infrastructure. Source text and the data a program computes with are different representations, and you move between them through a small set of explicit, checked operations. The electrical content is described in ordinary power system terms, and the same types and operations appear in Rust, C, Python, Julia, PowerIO IR, and the MCP server.
The module
PioModule<T> contains one typed value and the data that explains it:
PioModule<T>
├── value: T
├── diagnostics
├── producer
├── sources and source map
├── history
└── extensions
In Rust you reach the value through module.value() and the diagnostics
through module.diagnostics(). Python and Julia expose both as properties,
value and diagnostics, and C exposes borrowed accessors because its values
are opaque. Diagnostics belong to the module rather than to the network or
solution inside it.
While the process runs, a module can keep the bytes it was parsed from, which is how writing it back to its own format reproduces them byte for byte. Those bytes are not part of serialized PowerIO IR, and editing the value drops them, so the next same format write serializes the edited value instead of the old bytes.
Values
PioValue is the closed set of types a module can contain at the dynamic
boundary, meaning automatic parsing, PowerIO IR, the C, Python, and Julia
bindings, and the MCP server.
BalancedNetwork
MulticonductorNetwork
OperatingPoint<BalancedNetwork>
OperatingPoint<MulticonductorNetwork>
TimeSeries<T>
ScenarioSet<T>
DcPfInstance, AcPfInstance, DcOpfInstance, AcOpfInstance,
McAcPfInstance, McAcOpfInstance, AcScucInstance
DcPfSolution, AcPfSolution, DcOpfSolution, AcOpfSolution, SocwrOpfSolution,
McAcPfSolution, McAcOpfSolution, AcScucSolution
GeoLayer
| Type | Meaning |
|---|---|
BalancedNetwork | A self contained balanced case: equipment identities, terminals, physical parameters, ratings, limits, costs, and the source’s operating assignment. |
MulticonductorNetwork | The conductor resolved distribution model. |
OperatingPoint<N> | A possibly partial alternate electrical assignment over fixed equipment. It makes no claim of completeness or power flow feasibility. |
TimeSeries<T> | Values of one type ordered in time. |
ScenarioSet<T> | Named alternatives of one type, optionally with probabilities and with no time order. |
*Instance | The complete input of one named calculation: fixed inputs, unknowns, bounds, objectives, horizon, contingencies, and formulation choices. |
*Solution | The result of one calculation: computed quantities, termination, residuals, multipliers, and the objective or bound. |
GeoLayer | Coordinates and routes for the elements of a case, as a document of its own. |
The two network types are peers, and neither is a subtype of the other.
BalancedNetwork is where MATPOWER, PSS/E, XIIDM, CGMES, and the other
balanced formats meet; MulticonductorNetwork is where OpenDSS,
PowerModelsDistribution JSON, and BMOPF meet. To get a balanced positive
sequence equivalent from a multiconductor network you call an explicit
transformation, which reports each assumption it made and each thing it lost.
A BalancedNetwork parsed from PSS/E and one parsed from MATPOWER are the
same type with the same meanings. Each format has a documented profile, and
data outside that profile is reported and stays in the retained source; it is
not folded into the model. There is no universal network format. The balanced
model does not absorb multiconductor data, other energy carriers, or
calculation data.
A Rust application can put its own type in a module, PioModule<MyType>, and
get the same source, diagnostic, and history behavior. That type stays
outside the dynamic boundary until PowerIO adds it to PioValue, the IR
schema, and the bindings, so until then it cannot pass through PowerIO IR or
reach the other languages.
Operating points
An operating point can override demand, setpoints, dispatch, voltages, injections, equipment service status, switch positions, transformer taps, phase shifts, and the corresponding multiconductor controls. Any quantity it leaves out resolves to the network’s own assignment, which is why an operating point can be partial.
It cannot change equipment identities or terminals, physical parameters, ratings, costs, commitment or reserve structure, the horizon, or the equipment set. If your scenario changes any of those, it contains a network or a calculation instance instead of an operating point.
Topology is calculated from the declared terminals, equipment service status, and switch positions, which together give the energized connectivity. Tap and phase shift changes affect the equations and leave the connectivity alone.
Collections
TimeSeries<T> and ScenarioSet<T> nest without flattened names, so
TimeSeries<OperatingPoint<BalancedNetwork>>,
ScenarioSet<TimeSeries<BalancedNetwork>>, and the other combinations keep
their structural type. An operating point entry refers to the shared base
network instead of copying its tables, so a series of operating points
contains one network and one sparse set of overrides per point. A series of
networks or instances contains complete values when their physical or
calculation data differs.
Indexing a collection returns the contained value or a view rooted in the owning module; nothing reparses and no complete network is copied.
Calculation instances and solutions
Network data, calculation inputs, and results are separate types. A MATPOWER
case parses to a BalancedNetwork, and you construct a DcPfInstance,
AcPfInstance, DcOpfInstance, or AcOpfInstance from it. A solver takes
the instance and returns the corresponding solution; PowerIO itself never
solves.
SocwrOpfSolution is a PowerModels SOCWR relaxation together with its
objective lower bound. It is not an AcOpfSolution unless voltage recovery
and AC residual checks support that claim.
Diagnostics
Every operation reports diagnostics. Each Diagnostic has a stable dotted
code, a severity (error, warning, remark, or note), a message, and,
where available, a target, source byte spans, related diagnostics, and a
suggested action. Branch on the code rather than on the rendered message; the
code is the part that stays stable.
A successful operation keeps its diagnostics on the returned module or result. A failed operation returns or raises the language’s PowerIO error, which contains the same diagnostics.
Sources, formats, and destinations
A Source owns one or more named immutable byte buffers read from a file, a
directory, or memory. In Rust and C you build it yourself, since those
languages need an explicit owner for the bytes. Python and Julia take a path,
an open file, or a bytes value directly, because the interpreter already owns
them; Rust, Python, Julia, and C explains the split. parse
detects the format from the source name and content unless you declare one.
emit writes one format and returns an EmitResult. The result contains one
artifact per file produced, each with its name and either its bytes (for a
memory destination) or its path after a write; the layout, which is one file
or a directory; the fidelity, which is either an exact echo of retained
source bytes or fresh canonical output; and the emission diagnostics, which
report each loss. A single file format produces one artifact, and a directory
format such as PyPSA CSV, GridFM, or CGMES produces one per file.
resolve_format maps an accepted spelling of a format to its canonical token
and reports the conventional file suffix, the output layout, and whether a
fresh writer exists. It describes formats only; values are named by
PioValue and its counterparts in each language.
PowerIO IR
serialize writes a module as PowerIO IR and deserialize reads it back
with its types, diagnostics, sources, history, and extensions intact. The
document has an integer generation that changes only when the serialized
representation changes. PowerIO IR is absent from grid exchange format
discovery, so parse does not accept it. PowerIO IR
defines the document and its generation rule.
Derived data
Sparse matrices, dense solver rows, factorizations, and caches are analysis data computed from a value. They keep element mappings back into that value, they are not stored in a module, and their representation can change without changing any public meaning.
Every transformation declares its input and output types and returns
diagnostics. Multiconductor to balanced conversion moves to a less detailed
representation under stated assumptions, while constructing an instance from
a network moves to a more specific one. Format conversion is parse followed
by emit at the same level.
LLVM and MLIR lessons lists the decisions PowerIO shares
with LLVM and MLIR and the ones it does not.
Transmission networks
A balanced network is the positive sequence transmission model. All of the balanced formats in the format table parse to it, so one reader and one writer per format cover all the conversions between them.
using PowerIO
module_ = parse("case118.m") # PioModule{BalancedNetwork}
net = module_.value
length(net.buses) # 118
net.branches[1] # buses, branches, generators, loads, and the other tables
The network keeps what the source says: the element inventory, terminal connections, impedances, ratings, generator capability bounds and cost curves, and the source’s operating assignment. Ratings and costs stay on the network as reusable data, and which bounds a calculation enforces is decided when you construct an instance, so one parsed case serves power flow, DC OPF, and AC OPF without reparsing (see Calculation instances and solutions).
A few conventions hold across the accessors. Bus identifiers are the source’s
own; dense zero based indices exist only in matrix results, which include the
mapping. Powers are MW and MVAr as the source gives them, and angles are
degrees; to_normalized derives a per unit, radian, in service only copy
when a solver wants one. A branch tap of 0 means 1, and a rate_a of
0 means unrated.
Sources that describe more than the balanced calculation view, such as XIIDM, CGMES, and PSS/E RAW 35, also fill in the detailed connectivity: substations, voltage levels, connectivity nodes, terminals, switches, operational limit groups, and tap changer controls. A writer whose format can represent those writes them, and one whose format cannot reports what it left out.
emit(module_, "matpower", "copy.m") # the source bytes, unchanged
result = emit(module_, "psse") # fresh PSS/E text
for finding in result.diagnostics
println(finding.code, ": ", finding.message)
end
From a library you call parse, keep the module, and call emit; on the
command line, powerio convert does both in one call.
Building matrices from a balanced network has its own chapter, Matrices and graphs.
Distribution networks
A multiconductor network is the conductor level distribution model, and
OpenDSS, PowerModelsDistribution engineering JSON, and BMOPF JSON all parse to
it. When you need a calculation, construct an McAcPfInstance or
McAcOpfInstance from it explicitly (see
Calculation instances and solutions).
using PowerIO
feeder = parse("IEEE13Nodeckt.dss") # PioModule{MulticonductorNetwork}
net = feeder.value
net.lines[1] # terminal maps and the line code reference
net.linecodes[1] # per length impedance matrices, SI units
feeder.diagnostics # what the reader kept, assumed, or refused
The model identifies individual conductors throughout. Buses have ordered
terminals and explicit grounding, lines and switches map conductors between
terminal sets, transformers list their windings with connection kinds, and
loads and generators attach per terminal. Impedance and shunt matrices are in
SI units, per unit length, on line codes. An element the reader has no typed
slot for stays verbatim in the untyped table and is reported.
The OpenDSS profile is the static circuit, meaning the element definitions and their electrical data. Load shapes, solve commands, monitors, and other calculation instructions are outside it; they stay in the retained source, are reported as uninterpreted, and survive a same format write byte for byte.
emit(feeder, "dss", "copy.dss") # the source bytes, sidecars included
result = emit(feeder, "pmd") # fresh PMD JSON
result.text
result.diagnostics
Nothing converts between the multiconductor and balanced models implicitly. The balanced positive sequence equivalent is an explicit transformation that reports its assumptions, such as voltage bases per zone, phase aggregation, and switch merging, and refuses what it cannot represent:
use powerio::transform::{to_balanced, to_balanced_report};
let report = to_balanced_report(&feeder)?; // readiness, assumptions, losses, diagnostics
let balanced = to_balanced(&feeder)?; // PioModule<BalancedNetwork>
Python exposes the same pair as module.to_balanced_report() and
module.to_balanced(). The transformation has no C entry point in 0.11, so
PowerIO.jl does not bind it.
Multiconductor admittance matrices build directly from the multiconductor
network through powerio_matrix::calc_multiconductor_admittance_matrix,
which is Rust only in 0.11.
BMOPF schema versions
PowerIO v0.11.0 supports draft BMOPF 0.2, subject to Task Force review. The schema version is separate from the PowerIO release and from PowerIO IR generation 2. Producer provenance records the proposal revision and schema digest. Previously emitted schema identifiers remain readable aliases.
An unqualified bmopf-json emission preserves an unchanged source byte for
byte. An explicit schema version always re-encodes the selected version:
powerio convert feeder.json --to bmopf-json@0.1.0 -o legacy.json
powerio convert feeder.json --to bmopf-json@0.2.0 -o proposal.json
The same format strings work with Rust powerio::emit, Python powerio.emit,
C pio_emit, Julia emit and the PowerIO MCP emission tool. Rust also exposes
BmopfSchemaVersion and BmopfEmitOptions for the distribution writer.
Legacy output relocates proposed-only equipment and transformer fields into
extras, with diagnostics. A consumer that ignores those extensions cannot be
assumed to calculate the same network. Proposal output uses the declared tables
and preserves winding ratings, taps, neutral impedances and current-limit data.
Unequal bus phase bounds remain individual values through parsing, generation-2
IR and explicit BMOPF output. Uniform values use v_min/v_max; unequal values
use v_min_phase/v_max_phase in the typed Rust model. A present scalar takes
precedence over the corresponding vector. PMD voltage arrays instead follow
terminal order and use engineering voltage units.
The C and Julia bus views expose unequal limits as
phase_to_ground_voltage_min_v and phase_to_ground_voltage_max_v, while
voltage_min_v and voltage_max_v represent uniform bounds. C spans borrow
from the network’s owner and remain valid for the lifetime of that handle.
The unreleased ABI 7 layout and Julia definitions are updated together.
Parsing, structural schema validity, semantic consistency and computational support are separate checks. Contradictory versions, invalid dimensions, unknown electrical references and inconsistent bounds produce error diagnostics. The native multiconductor admittance builder supports ideal grounded-WYE transformer coupling and rejects leakage, other winding connections, floating neutrals, core shunts or tap decisions that need a different formulation.
The BMOPF proposal schema is pinned to
fe8671a,
with SHA-256 74d6c6de3637d52e42a26c4cb0584f51df70d69f360b236cf5e23afaf7669462.
Fresh 0.2.0 output places the immutable retrieval URL in meta.$schema and
records the canonical identity, proposal status, digest and revision under
meta.provenance.powerio_bmopf. Existing provenance is preserved; a name
collision uses a numbered producer entry. Reading the former canonical or
version-directory identifiers remains supported without fetching remote data.
Energy prices
Draft BMOPF 0.2 uses energy_cost_rate in $/kWh. Generator entries follow phase
order, as do voltage-source entries. Neutral terminals have no price entry. PowerIO retains source prices in VoltageSource.energy_cost_rate and
in generation-2 IR. C and Julia expose energy_cost_rate_per_kwh; Python’s
voltage-source records expose energy_cost_rate.
The reader also accepts the deprecated per-phase cost spelling. Explicit
0.1.0 output keeps generator cost and relocates source prices to
extras.voltage_source, with a diagnostic. Only a consumer that reads that
overlay can use the retained source prices. PowerIO stores the coefficients;
the selected solver determines whether and how they enter its objective.
Time series and scenarios
Some sources declare more than one value, and PowerIO keeps the structure they declare. An ordered sequence of values is a time series, a set of named alternatives is a scenario set, and the element type says what varies from one entry to the next.
| Source | Parses to |
|---|---|
| PyPSA CSV directory, snapshot axis with varying inputs | TimeSeries<BalancedNetwork> |
| PyPSA CSV directory, fixed network with electrical assignments per snapshot | TimeSeries<OperatingPoint<BalancedNetwork>> |
Egret JSON with system.time_keys in the scalar profile | TimeSeries<BalancedNetwork> |
GridFM Parquet dataset (the gridfm feature) | ScenarioSet<BalancedNetwork> |
A time point keeps the source’s exact label and, when the source gives one, an interval duration; PowerIO imposes no calendar meaning on labels. Scenario identifiers are the source’s own strings, and you look scenarios up by identifier rather than by position. An operating point entry refers to its shared base network instead of copying the network tables. Core concepts says what an operating point can and cannot change.
using PowerIO
dataset = parse("gridfm_case14/") # PioModule{ScenarioSet{BalancedNetwork}}
scenarios = dataset.value
keys(scenarios) # the scenario identifiers
one = scenarios["7"] # BalancedNetwork
series_module = parse("pypsa_folder/") # PioModule{TimeSeries{BalancedNetwork}}
series = series_module.value
length(series) # the number of declared time points
first_hour = series[1] # one based, like every Julia axis
Indexing returns the contained entry or a view rooted in the owning module; nothing reparses and no complete network is copied. If an entry needs diagnostics, history, emission, or serialization of its own, wrap it in a module.
Only the facade reads the Egret time series. powerio::parse returns the
series, while the component crate’s powerio_tx::parse reads only the
static profile and refuses a document with system.time_keys.
Data outside a source’s profile, such as PyPSA investment periods, stochastic scenarios, and unit commitment coupling, or Egret reserve and contingency data, stays in the retained source, is reported as uninterpreted, and survives a same format emission. Formats and fidelity says where each profile ends.
Calculation instances and solutions
An instance is the complete input for one named calculation, and a solution
is the result of one; the solution shares the instance it solves. PowerIO has
seven instance types, from DC power flow through multiconductor AC OPF to AC
security constrained unit commitment, and eight solution types, because
SocwrOpfSolution is an SOCWR relaxation of an AcOpfInstance and is not
labeled an AC OPF solution.
DcPfInstance DcPfSolution
AcPfInstance AcPfSolution SocwrOpfSolution
DcOpfInstance DcOpfSolution
AcOpfInstance AcOpfSolution
McAcPfInstance McAcPfSolution
McAcOpfInstance McAcOpfSolution
AcScucInstance AcScucSolution
A source parses to an instance or a solution only when it declares that calculation:
| Source | Parses to |
|---|---|
| DOE GO Challenge 3 problem file | AcScucInstance |
| that problem file beside its solution file | AcScucSolution |
| DeepMind OPFData JSON | AcOpfSolution |
| BMOPF JSON | MulticonductorNetwork; construct the instance explicitly |
A MATPOWER case or a PowerModels file stays a network, because it has ratings and costs that power flow, DC OPF, and AC OPF can all use and nothing in the file commits you to one calculation. The GO Challenge 3 data format also serves later challenges, but only its Challenge 3 problem file defines a calculation PowerIO has a type for; optional fields outside that formulation are retained in the source and reported.
GO Challenge 3 splits its problem and solution across two files. Put both in one directory and parse the directory:
solution = powerio.parse("scenario_002")
A directory with only the problem file returns an AcScucInstance. A
solution file on its own is refused, because it contains neither the
component definitions nor the time axis. The returned module retains both
files and its diagnostics.
A DC instance says which branch susceptance formula it uses. In Rust you
select it with with_branch_susceptance_formula(formula) and read it with
branch_susceptance_formula(); the PowerIO IR document stores it in the
approximation field as one of series_susceptance,
tap_adjusted_reactance, or reactance_only. Matrices and
graphs defines the three.
An instance contains or shares its network. emit writes that network in a
grid exchange format and reports the calculation fields the format has no
place for, while serialize preserves the complete instance:
using PowerIO
scuc = parse("scenario_002") # PioModule{AcScucInstance}
emit(scuc, "matpower", "scenario_002.m") # diagnostic: scheduling data omitted
serialize(scuc, "scenario_002.pio.json")
Solvers consume instances; PowerIO never solves. The instance is the mathematical input only. The choice of equations, B-theta or PTDF for DC OPF and polar or SOC for AC OPF, belongs to the solver and does not create another instance type.
A solution lists its values by stable element identifier, along with the
termination claim and residuals that PowerIO computes itself rather than
taking on trust. An OPFData solution exposes the instance it solves, and
residual checks run against that instance’s network. Emitting a solution
writes the bus voltages, generator dispatch, and branch terminal flows the
target format supports and reports the objectives, multipliers, termination
data, and residuals it left out. An SOCWR result is never written as an AC
power flow solution; emit writes only its instance network and reports that
the W-space values and the objective lower bound were omitted.
Matrices and graphs
powerio-matrix calculates sparse matrices and graph data from parsed
networks. Source bus identifiers are not dense indices, so each result comes
with the element mapping you need to read its rows and columns, and the dense
[0, n) index space exists only inside a result that says which bus each
index is.
powerio-prob owns the calculation instances and builds no matrices itself;
the matrix crate turns those instances into sparse operators. The files of the
DC OPF bundle are defined in DC OPF bundle, and the Rust
API is documented in the
crate reference.
Capabilities
| matrix | shape | calculation | notes |
|---|---|---|---|
MATPOWER Bp (FDPF) | \(n \times n\) | calc_bprime_matrix | -Im(Y_bus) after the makeB Bp edits |
MATPOWER Bpp (FDPF) | \(n \times n\) | calc_bdoubleprime_matrix | -Im(Y_bus) after the makeB Bpp edits |
| \(\Re(Y_{\mathrm{bus}})\), \(-\Im(Y_{\mathrm{bus}})\) | \(n \times n\) | calc_admittance_matrix | full admittance, keeps taps and shifts |
| LACPF (linear AC power flow) block | \(2n \times 2n\) | calc_lacpf_matrix | \(\begin{bmatrix}G & -B \\ -B & -G\end{bmatrix}\), flat start, indefinite |
| PowerModels DC incidence \(A\) | \(m \times n\) | DcOperators::calc_incidence_matrix | row \(e\) has \(+1\) at the from bus, \(-1\) at the to bus |
| DC branch susceptances \(b\) | \(m\) | DcOperators::calc_branch_susceptances | one signed susceptance per in service branch |
| DC branch flow matrix \(B_f\) | \(m \times n\) | DcOperators::calc_branch_flow_matrix | \(B_f = \operatorname{diag}(b)A\) |
| DC bus susceptance \(B\) | \(n \times n\) | DcOperators::calc_bus_susceptance_matrix | \(B = A^\mathsf{T}\operatorname{diag}(b)A\) |
| DC branch phase shift injection | \(m\) | DcOperators::calc_branch_phase_shift_injection | \(b .* shift\) |
| DC bus phase shift injection \(p_{shift}\) | \(n\) | DcOperators::calc_bus_phase_shift_injection | \(p_{shift} = A^\mathsf{T}(b .* shift)\) |
| DC bus injection \(p_{bus}\) | \(n\) | DcOperators::calc_bus_injection_dc | \(p_{bus} = -Bv_a + p_{shift}\) |
| weighted bus factor \(L\) | \(n \times n\) | calc_weighted_laplacian | \(L = C \operatorname{diag}(w) C^\mathsf{T}\); internal solver data |
| solver branch flow matrix | \(m \times n\) | calc_solver_branch_flow_matrix | positive solver susceptance magnitudes times \(C^\mathsf{T}\); internal solver data |
| PTDF | \(m \times n\) | calc_ptdf | routes through Auto solver selection; calc_ptdf_lodf_with_options exposes the choice |
| LODF | \(m \times m\) | calc_lodf | routes through Auto solver selection; option based builds can prune small output entries |
| AC power flow Jacobian | \(2n \times 2n\) | calc_power_flow_jacobian | polar or rectangular voltage coordinates |
| multiconductor admittance | conductor by conductor | calc_multiconductor_admittance_matrix | from a MulticonductorNetwork; Rust only in 0.11 |
| adjacency | \(n \times n\) | calc_adjacency_matrix | sparse graph adjacency |
| petgraph graph | n/a | IndexedNetwork::to_petgraph | UnGraph<usize, usize> |
PTDF and LODF need a linear solve, and calc_ptdf, calc_lodf,
calc_ptdf_lodf, and the option based calc_ptdf_lodf_with_options all go
through the same solver selection. You pick the path with
SensitivitySolver, the solver field on SensitivityOptions. Dense
forces the dense grounded factorization. Sparse factors the grounded DC bus
susceptance matrix once with a sparse Cholesky and reuses that factorization
for every right hand side. Auto picks dense up to a reduced dimension of 512
(with a memory ceiling) and sparse above it. The sparse path avoids forming
the \((n-r) \times (n-r)\) dense inverse, though the PTDF and LODF outputs
themselves can still be large. It also needs positive finite internal factor
weights w = -b, so the grounded matrix L = -B is positive definite once
reference coverage has been checked; the dense path can handle nonsingular
indefinite cases. Every connected component must contain at least one
reference bus. The DC OPF bundle (\(A\), \(b\), \(L\), costs, bounds,
thermal limits, \(C_g\)) is prepared from a DcOpfInstance and documented
in DC OPF bundle.
Bp and Bpp are the fast decoupled power flow matrices from MATPOWER
makeB. Solvers reduce Bp to PV+PQ buses for active power mismatch to voltage
angle updates, and reduce Bpp to PQ buses for reactive power mismatch to
voltage magnitude updates. PowerIO exports the full \(n \times n\) matrices so
you can apply your own bus type reduction.
The public DC calculations live on DcOperators: calc_incidence_matrix,
calc_branch_susceptances, calc_bus_susceptance_matrix,
calc_branch_flow_matrix, calc_branch_phase_shift_injection,
calc_bus_phase_shift_injection, calc_bus_injection_dc, and
calc_branch_flow_dc. A calc_* name means the call computes a new result;
a plain noun is a stored field or a borrowed accessor. The incidence matrix
follows PowerModels, branches by buses, with \(+1\) at the from bus and
\(-1\) at the to bus, and the phase shift injection is a separate result.
The sensitivity and DC OPF builders use a transposed incidence factor
internally, and that factor stays internal rather than becoming a second
public incidence API.
GridFM datasets
Reading and writing GridFM needs the gridfm cargo feature; the CLI and the
Python wheel are built with it. The export is a Parquet dataset under
<case>/raw/ with bus_data, gen_data, branch_data, and y_bus_data. A
single parsed case writes one scenario. A scenario batch stacks the rows of
snapshots that share the same element set, with the scenario column as the
key; to build one you need unique scenario ids, one system base, and bus,
branch, and generator row IDs that match across the snapshots. Column names
and units follow the
GridFM data kit output schema.
Reading a GridFM dataset recovers everything its balanced tables hold: bus
types, voltages and limits; nodal load and shunt totals; generator dispatch,
bounds, and quadratic costs; branch parameters and terminal flows; and
base_mva. Dense bus indices, nodal demand, and a fixed quadratic cost triple
are how the GridFM tables are laid out, so reading them reports no conversion
loss.
Writing a richer network into GridFM reports each projection the format forces: bus renumbering, several load or shunt records aggregated into one, equipment and metadata left out, costs that do not fit the fixed quadratic columns, and terminal flows evaluated from bus voltages when the source has no branch solution. A GridFM dataset read and written back without an edit in between produces none of these findings.
Conventions
-
Weighted bus solver factors. Stored nonzero off-diagonal entries are negative; diagonals are nonnegative and positive for buses incident to a positive weight branch. For \(L = C \operatorname{diag}(w) C^\mathsf{T}\) with nonnegative branch weights \(w\), \(L_{ii} = \sum_j \lvert L_{ij} \rvert\). This is the M-matrix form an SDDM (symmetric diagonally dominant M-matrix) or Cholesky solver expects once the grounded matrix is positive definite; a consumer can recover an edge weight as \(-L_{ij} > 0\).
-
Bus indexing. Source bus IDs are preserved on the model as a newtype and need not be contiguous.
IndexedNetwork::bus_index(id)maps them into dense zero based indices in \([0,n)\), returningNonefor an unknown source ID. The matrix builders turn that intoError::UnknownBusat the point they need a bus that is not in the network. -
Taps and shifts. \(\mathrm{tap} = 0\) means \(\mathrm{tap} = 1\) (
Branch::calc_effective_tap). MATPOWERBpclears bus shunts and line charging, sets tap magnitudes to one, and keeps phase shifts. MATPOWERBppkeeps bus shunts, line charging, and tap magnitudes while clearing phase shifts. \(Y_{\mathrm{bus}}\) keeps both tap magnitudes and phase shifts. -
Branch shunt admittance is stored per unit.
Branch::chargingis the stored per terminal admittance when present:g_fr,b_fr,g_to, andb_toare already per unit on the system base.Branch::bis the legacy MATPOWERBR_Btotal projection for formats that carry only one charging value. Matrix builders useBranch::calc_terminal_charging(), so terminal values feed \(Y_{\mathrm{bus}}\) even when the legacy total is zero or stale. -
FDPF scheme.
Schemeselects between the two MATPOWER fast decoupled forms.Xbclears resistance forBp;Bxclears resistance forBpp. The default isBx. -
Zero impedance branches.
BuildOptions::skip_zero_impedancecontrols the builders whose branch denominator can be zero. The defaultfalsereturnsError::ZeroImpedance;trueskips the branch and records the skipped source branch rows inMatrixStatsasskipped_zero_impedanceandskipped_zero_impedance_branches. Full AC admittance builders use \(r^2 + x^2\); DC incidence and reactance only FDPF forms use \(x\). The gridfm export still zeros its admittance and flow columns for these rows and recordsdropped_zero_impedanceingridfm_meta.json. -
Reference coverage.
IndexedNetwork::check_reference_coverageverifies that every in service island has a reference bus. -
Branch susceptance formulas.
BranchSusceptanceFormulaselects the branch susceptance vector \(b\) and, for formulas that include phase shifts, the phase shift injection. In the PowerModels form,DcOperators::calc_incidence_matrixreturns the \(m \times n\) branch by bus matrix \(A_{pm}\). Each branch row has \(+1\) at the from bus and \(-1\) at the to bus. The signed matrix combines with \(b\) to form the direct PowerModels operators \(B = A_{pm}^\mathsf{T} \operatorname{diag}(b) A_{pm}\) and \(B_f = \operatorname{diag}(b) A_{pm}\). The phase shift injection is \(p_{shift} = A_{pm}^\mathsf{T}(b \circ shift)\), so \(p_{bus}=-B\theta+p_{shift}\). \(b\) is negative for an inductive branch, the PowerModels series susceptance sign.Solver preparation retains the \(n \times m\) bus by branch factor \(A_s=A_{pm}^\mathsf{T}\). It uses the positive factor weight \(w=-b\), so its sparse matrix is \(L=A_s \operatorname{diag}(w) A_s^\mathsf{T}=-B\). The two orientations have separate names so that a transposition cannot slip across a language boundary unnoticed.
The default
SeriesSusceptanceuses \(b = -x/(r^2 + x^2)\), which takes the whole series impedance into account, plus the phase shift injection vectorp_shift. A tap does not scale it. It reduces to \(b = -1/x\) when the branch has no resistance.TapAdjustedReactancereproduces MATPOWER’smakeBdc: \(b = -1/(x\tau)\) for a transformer with tap ratio \(\tau\), plusp_shift.ReactanceOnlyis the textbook \(b = -1/x\) with resistance, taps, and shifts ignored. The resulting \(-B\) matches MATPOWERBpunderScheme::Xbwhen phase shifts are zero.
Output
Matrices are written as Matrix Market files or kept in memory. A symmetric
matrix is stored as its lower triangle with the symmetric header and 1-based
indices (io::mtx::emit_mtx). The sensitivities command writes
<case>_ptdf.mtx, <case>_lodf.mtx, and <case>_sensitivity_meta.json. Pick
the branch susceptance formula with
--formula series-susceptance|tap-adjusted-reactance|reactance-only, the
PTDF/LODF solve path with --solver dense|sparse|auto, and drop entries whose
absolute value is at or below a threshold with --drop-tolerance <value>. On
the sparse path the CLI writes the retained Matrix Market coordinates through
temp files rather than holding the full sparse output in memory; the Rust
calc_ptdf_lodf_with_options API still returns CsMat values and is meant
for outputs that fit in memory. The metadata file lists the requested solver,
the solver path used, matrix dimensions, nonzero counts, the tolerance, and
how many entries were dropped. The dcopf subcommand writes
its matrix family together with a JSON manifest.
The standard case solver property fixture lives at
powerio-matrix/tests/fixtures/solver_matrix_stats.json. It holds bprime,
bdoubleprime, and ybus_imag stats for case9, case14, case30, case57, and
case118: n, nnz, min diagonal, M-matrix sign pattern, diagonal dominance
margin, zero impedance skips, row sum checks, SPD checks, and a condition
estimate when the solver input is SPD.
IndexedNetwork::to_petgraph returns the network as an undirected
petgraph graph, one node per bus and one edge per
in service branch. The connectivity report and the radial check are built on
it, and you can hand the returned graph straight to other petgraph algorithms.
Formats and fidelity
The format table in the repository README lists each format with its token and its read and write support. This page covers the numeric conventions, the independent checks each reader and writer passes, and, format by format, what a reader keeps and what a writer reports.
Conventions
PowerIO’s numeric conventions follow MATPOWER and PowerModels.jl. For each quantity the table gives the reference implementation and the matching PowerIO code:
| Quantity | Convention | Reference | powerio |
|---|---|---|---|
| Bus type codes | \(1 = \mathrm{PQ}\), \(2 = \mathrm{PV}\), \(3 = \mathrm{ref}\), \(4 = \mathrm{isolated}\) | MATPOWER idx_bus | network::BusType |
| Impedance, susceptance | per unit on baseMVA, never rescaled | MATPOWER idx_brch (BR_B already per unit) | matpower |
| Branch terminal admittance | MATPOWER BR_B splits half to each end; richer sources use canonical g_fr/b_fr/g_to/b_to; one-value targets receive the total susceptance projection | PowerModels matpower.jl; MATPOWER idx_brch | network::BranchCharging, Branch::calc_terminal_charging |
| Tap ratio | 0 means a line (treated as 1); nonzero is a transformer | MATPOWER idx_brch TAP | Branch::calc_effective_tap |
| Phase shift, angle | degrees in the model; PowerModels JSON carries radians | PowerModels make_per_unit! | powermodels-json |
| Angle limits | angmin/angmax default ±360 (unconstrained) | MATPOWER idx_brch ANGMIN/ANGMAX | Branch::has_angle_limits |
| pandapower/PyPSA impedance | line r/x are converted between per unit and ohms with \(Z_{\mathrm{base}} = V_{\mathrm{kV}}^2 / \mathrm{baseMVA}\); pandapower line charging is capacitance per km (c_nf_per_km, converted via \(2\pi f \ell Z_{\mathrm{base}}\)); PyPSA line b is siemens | pandapower PPC conversion, PyPSA static components | pandapower-json, pypsa-csv |
dcline Pt/Qf/Qt | sign flips vs MATPOWER | PowerModels matpower.jl | powermodels-json |
| Generator cost | \(c_2 p^2 + c_1 p\) maps to \(q = 2c_2\), \(c = c_1\); coefficients high order first | MATPOWER idx_cost, egret matpower_parser | GenCost::calc_quadratic |
source_id | ["bus", id] for bus-tied elements | PowerModels matpower.jl | powermodels-json |
| PSLF shunts | EPC pu_mw/pu_mvar are per unit on sbase; Shunt stores MW/MVAr at \(V = 1\) | paired EPC/RAW case checks | pslf |
| DOE GO Challenge 3 | an input/problem data file parses to AcScucInstance; one Source containing that file and its matching output/solution data file parses to AcScucSolution; instance.network() returns the shared BalancedNetwork | pinned GO-3 data model, C3DataUtilities, and GOC3Benchmark.jl D1/D2/D3 files | powerio::parse, powerio::emit |
| Surge angles | Surge JSON carries voltage angles, phase shifts, and angle limits in radians; BalancedNetwork stores degrees | Rust Surge round trip tests | surge-json |
| DeepMind OPFData JSON | DeepMind OPFData carries p.u. powers and radian angles; BalancedNetwork stores the solved snapshot in MW/MVAr and degrees, with zero based links mapped to one based bus IDs | Paper Appendix A, the PyG loader, the smallest complete official fixture, and size independent FullTop and N-1 property tests | opfdata-json |
| UCTE-DEF units and signs | ohm, microsiemens, kV, MW, MVAr, and ampere on the node voltage level; generation and its limits are negative for an injection and the reader negates them; a current limit becomes rate_a as \(\sqrt{3}, U I / 1000\) MVA; no system base, so the balanced view uses 100 MVA at 50 Hz | PowSybl Core UcteImporter and UcteNode.fix | ucte |
| IEEE CDF shunts and taps | bus G/B are per unit on the title card MVA base and Shunt stores MW/MVAr at \(V = 1\); the tap bus is the from bus and the final turns ratio is the MATPOWER TAP; the phase shifter angle keeps its sign | MATPOWER cdf2mpc, PowSybl IeeeCdfBusReader and IeeeCdfBranchReader, the vendored 14 and 30 bus cases against case14.m and case30.m | ieee-cdf |
egret’s own MATPOWER parser uses the same reductions (bus type as
matpower_bustype, polynomial coefficients reversed to a {degree: coefficient}
map, piecewise to [[mw, cost], ...], impedances left per unit), which is why a
MATPOWER case taken through powerio to egret JSON matches egret’s direct import.
Validation
The harness script evals/validation/run_validation.sh checks powerio against
five independent tools, and each classic text reader and writer runs under an
oracle. The conversion matrix covers MATPOWER, PSS/E, and egret sources against
all five legacy text targets, each PowerWorld output is read back and bridged
to PowerModels JSON, and the PMread leg covers the PowerModels JSON read side.
pandapower JSON and PyPSA CSV folders have dedicated import validators, because
pandapower has its own JSON schema and PyPSA is a directory format; both
validate the write direction only, since the pandapower JSON and PyPSA readers
have no external oracle. DOE GO Challenge 3 has a separate pinned reference
job, described below. Surge JSON and the remaining source and target pairs
(PowerModels JSON and PowerWorld sources into the targets other than
PowerModels) rest on the Rust round trip suite.
- PowerModels.jl (
validate_powermodels.jl,validate_psse.jl,core_json.jl) reads MATPOWER, PowerModels JSON, and PSS/E. The MATPOWER to PowerModels JSON path is checked field by field after per unit normalization, and the others by element counts and demand, generation, and shunt totals. - egret (
validate_egret.py) is the oracle for egret output, which PowerModels cannot read. It loads powerio’s egret JSON withegret.data.model_data.ModelDataand compares counts, totals, and generator cost curves. - ExaPowerIO.jl (
validate_exapowerio.jl) reads MATPOWER through powerio’s C ABI and compares value for value. - pandapower (
validate_pandapower.py,validate_pandapower_converter.py) cross checks the MATPOWER parse and \(Y_{\mathrm{bus}}\), then imports powerio’s pandapower JSON output back into pandapower and compares counts and \(Y_{\mathrm{bus}}\). - PyPSA (
validate_pypsa.py) imports powerio’s PyPSA CSV folder output and checks counts, totals, line r/x/b rebased from ohms on the bus0 voltage, and transformer r/x/tap_ratio/s_nom rebased from the transformers_nombase. A mismatch in the line and transformer split fails the case.
The conversion matrix
evals/validation/validate_matrix.py converts each source to each legacy text
target and checks the electrical core of the output (bus, branch, and generator
counts and the per unit demand, generation, and shunt totals) against the
source’s own core as an independent oracle reads it. The diagonal is checked
byte exact, meaning that writing a case back to its own format reproduces the
file. Sources are the real native files where they exist (the vendored PSS/E
.raw and egret .json) and representative MATPOWER cases otherwise: basic
(case9), shunts and transformers (case14, case30), size (case118,
case2869pegase), HVDC with a mixed piecewise and polynomial gencost
(t_case9_dcline), and a piecewise cost case (pglib_opf_case5_pjm).
All 65 legacy text cells pass (13 source cases × 5 targets). Each writer preserves the core regardless of fidelity tier, which is why the core is the invariant checked across the whole matrix; cost, HVDC, and angle limits are tier specific and are covered by the dedicated checks above and the Rust suite. The pandapower JSON and PyPSA CSV validators run alongside this matrix and are reported as separate legs.
Running it
cargo build --release -p powerio-capi
python3.11 -m venv .venv
.venv/bin/python -m pip install --upgrade pip maturin -r evals/validation/requirements.txt
env VIRTUAL_ENV=$PWD/.venv .venv/bin/maturin develop --release
julia --project=evals/validation -e 'using Pkg; Pkg.instantiate()'
bash evals/validation/run_validation.sh
The oracle tools (PowerModels.jl, egret, ExaPowerIO.jl, pandapower, PyPSA) are
declared only in evals/validation/Project.toml and
evals/validation/requirements.txt, and no PowerIO release artifact depends on
them. evals/validation/run_validation.sh expects the Python oracles to import
in the selected Python 3.11+ environment, so a missing PyPSA, pandapower, or
egret import is a setup failure.
Format notes
Each loss produces a coded diagnostic. A reader itemizes what it keeps only in
the retained source, with the table and the count of affected rows; a writer
reports what the target cannot represent, and emit returns those findings
with the result. A code spells out the format and the reason, as in
READ.CGMES.RECORD_UNMAPPED, READ.CGMES.FIELD_UNMAPPED,
READ.XIIDM.FIELD_UNMAPPED, or EMIT.PSSE.FIELD_DROPPED for RAW and RAWX, so
there is no generic parse warning that hides the cause.
XIIDM and JIIDM
PowerIO reads PowSybl IIDM 1.0 through 1.17 in the XML (xiidm) and JSON
(jiidm) encodings and writes 1.17 in either; the
IIDM versions table lists what each version changed. One
element mapping serves both encodings, so a JIIDM document reads to the same
network as the XIIDM document of the same network, and fresh JIIDM follows
PowSybl’s JSON layout: plural array fields for repeated elements, typed
scalars, and each element’s attributes in the order PowSybl’s sequential JSON
reader consumes them.
The mapping covers substations, voltage levels, bus breaker and node breaker
topology, busbar sections, switches, lines, tie and boundary lines, loads,
generators, batteries, shunts, static VAR compensators, two and three winding
transformers, tap changers and controls, operational limits, reactive limits,
HVDC converters, aliases, properties, and PowSybl active power control. Areas,
one level of nested XIIDM networks, nonlinear shunt section models, physical DC
equipment, and VSC/LCC converters also parse and survive fresh emission. An
unknown extension subtree stays available for byte exact same format emission
and produces a diagnostic, but it does not become an unnamed field on the
network. Fresh emission preserves detailed connectivity when the source has it
and allocates any missing local node numbers without changing stable PowerIO
identities. A three winding transformer whose ratedU0 differs from ratedU1
keeps that leg impedance base for fresh emission.
XIIDM gives electrical quantities in physical units and has no system MVA base, so the balanced calculation view uses 100 MVA as its internal normalization and does not report a missing source value. Fresh XIIDM or JIIDM emission reports a network base other than 100 MVA, because the target has nowhere to put that normalization; impedance and admittance conversion still uses the network’s actual base, so the physical electrical values come out unchanged.
CGMES
PowerIO reads CGMES 2.4.15 on CIM16 and CGMES 3.0 on CIM100. CGMES 2.4.15 uses
the namespace http://iec.ch/TC57/2013/CIM-schema-cim16# with the ENTSO-E
extension namespace http://entsoe.eu/CIM/SchemaExtension/3/1# (IEC TS
61970-600-1/-2:2017); CGMES 3.0 uses http://iec.ch/TC57/CIM100# and
http://iec.ch/TC57/CIM100-European# (IEC 61970-600-1/-2:2021). Both use the
IEC 61970-552 CIMXML instance syntax, in which rdf:ID defines a record,
rdf:about extends one, and md:FullModel heads each profile document. EQ is
required, and SSH, SV, and boundary profile data are used when present. SSH
assignments take precedence over SV observations, so an SV shunt section count
that differs from the SSH assignment is reported and not kept. A set with
TopologicalNode data reads one bus per TopologicalNode, and that data wins
even for node breaker equipment, so source TopologicalNode identities survive.
A set without TopologicalNode data still reads when its declared profile URIs
describe node breaker equipment, using the same test PowSybl makes in
CgmesModelTripleStore.computeIsNodeBreaker:
each CGMES 2.4.15 document that declares EquipmentCore also declares
EquipmentOperation (and each EquipmentBoundary document declares
EquipmentBoundaryOperation), or the EQ is CGMES 3.0 CoreEquipment with
ConnectivityNode records. The buses are then the connected components of the
ConnectivityNode graph joined by switches that are closed and in service, which
is the graph PowSybl’s
NodeMapping
and
SwitchConversion
hand to IIDM.
A switch is open when SSH Switch.open says so, otherwise when EQ
Switch.normalOpen says so, and otherwise closed. SV SvStatus.inService, or
failing that SSH Equipment.inService, decides service status, which CGMES
defines as availability for topology processing (PowSybl 7.3 reads only the
switch position, and since the official sets contain no closed switch out of
service, both rules agree there). A terminal is connected unless SSH
ACDCTerminal.connected is false; a disconnected terminal leaves its equipment
on the bus of its ConnectivityNode with the equipment out of service, which is
where PowSybl inserts a fictitious open switch. Each bus takes the nominal
voltage of its nodes’ VoltageLevel (a Bay resolves to its VoltageLevel), a node
in a Line container takes the base voltage of attached conducting equipment or
transformer ends, and a node no terminal references gets no bus. A bus is named
after a BusbarSection on it, or else after its first ConnectivityNode. Its mRID
is the UUIDv5, under PowerIO’s CGMES namespace, of the sorted ConnectivityNode
mRIDs it joins, so the same nodes always yield the same mRID and no source
TopologicalNode mRID is invented; the bus_breaker_buses table stays empty and
calculated_buses lists the nodes of each bus.
READ.CGMES.TOPOLOGY_CALCULATED, a remark, gives the bus, node, and switch
counts and the identity rule; READ.CGMES.CONNECTIVITY_INSUFFICIENT, an error,
says which data is missing when a set has neither TopologicalNode records nor
calculable connectivity, such as a bus branch EQ without TP.
The calculated topology has limits. SvVoltage observations reference
TopologicalNodes, so bus voltages keep their defaults and
READ.CGMES.RECORD_UNMAPPED counts the observations. No TopologicalIsland
supplies an angle reference, so the reference bus comes from
referencePriority, an external injection, or the largest machine. A closed
switch between two voltage levels joins its nodes into one bus placed in the
first node’s level, as PowSybl merges those levels. A 2.4.15 junction terminal
in EQ_BD has no ConnectivityNode and is read as disconnected when there is no
TP_BD. Finally, PowSybl’s bus view omits a component with no busbar section and
fewer than two feeders while PowerIO keeps each component as a bus, so bus
counts differ by those components. Each document’s Model.modelingAuthoritySet
is read for the boundary and state variable authority checks, reported, and not
kept; fresh output writes PowerIO’s own modeling authority.
A source can be an XML profile directory, a directory of profile ZIP files, or
one ZIP containing the profiles. The mapping covers hierarchy, AC and DC
equipment, detailed connectivity, current, active power, and apparent power
operating limits, tap changers and controls, reactive limits, and the operating
and solution values in SSH and SV. Diagram, geography, dynamics, and
unrecognized CIM classes are counted in diagnostics. A field on a recognized
class that the mapping does not consume gets a grouped
READ.CGMES.FIELD_UNMAPPED diagnostic with the class, field, count, and sample
identities.
Fresh output is a deterministic CGMES 3.0 EQ, TP, SSH, and SV profile set. Imported UUID mRIDs survive for mapped equipment, terminals, tap changers, operational limit sets, hierarchy, and topology records, and a missing mRID becomes a UUIDv5 derived from the component type and stable identity. Tap changer tables, tap controls and table points, reactive curve points, and individual limit values keep their electrical values and relationships but get deterministic subordinate mRIDs on fresh emission. Operational limit helper objects that PowerIO generates do not become source metadata on readback. Limit type objects keep the PATL or TATL name common CGMES importers require, while generated limit set objects omit an unrepresented display name. A third party subordinate identity or field without a typed record still gets a diagnostic. The source neutral limit model keeps permanent and temporary limits, so fresh output uses PATL and TATL; parsing reports a PATLT or TCT substitution and any fractional duration rounded to whole seconds.
CGMES input keeps each source Substation, including distinct substations
joined by a transformer. XIIDM and JIIDM emission joins output container groups
only when the IIDM rule that a transformer belongs to one substation requires
it, and it reports that hierarchy change; the transformer stays a transformer
with the same electrical data. Distinct BaseVoltage identities at the same kV
also get a precise collapse diagnostic before fresh output uses one record
keyed by voltage. Busbar VoltageLimit records are combined with the enclosing
voltage level into the most restrictive valid low and high voltage range; an
inconsistent pair is diagnosed and ignored, and fresh emission writes the
resulting VoltageLevel fields instead of recreating the individual
VoltageLimit records. CGMES uses physical units and has no system MVA base,
so the balanced calculation view uses 100 MVA as its internal normalization and
does not report a missing source value.
PSS/E
PowerIO reads RAW revisions 32 through 35 and RAWX revision 35, and RAW and
RAWX share one electrical mapping. RAW 32 records end before the bus voltage
limits (NVHI, NVLO, EVHI, EVLO), the load INTRPT field, the
transformer VECGRP field, and the winding CNXA field that revision 33
added, so the reader lays each record out by the header revision, defaults
those fields, and reports a revision 32 record that ends before its last typed
field as READ.PSSE.VALUE_DEFAULTED with the record’s byte range. RAW 34 maps
its substation section. RAW 35 and RAWX 35 map and freshly emit substations,
nodes, switches, busbar sections, and equipment terminal references. Fresh RAW
34/35 and RAWX output preserves AC line and transformer names, and RAWX
terminal rows use the exact type, buses, and local identifier chosen for their
electrical equipment row. When a source neutral connectivity node has no PSS/E
number, fresh RAWX allocates a positive number within its substation before
resolving exact regulation targets, and reports the default.
An explicit RAW revision outside 32 through 35, an invalid system base or frequency, or a nonfinite record value is rejected. Fresh output accepts only revisions 33 through 35 and returns an error when detailed connectivity cannot form valid RAWX tables. A RAW 32 module keeps its source text like any other revision, but no emission target names revision 32, so writing it back as PSS/E produces fresh revision 33 text and its unmodeled sections survive only in the retained source.
Generator IREG/NREG, switched shunt SWREG/NREG, and transformer
CONT/NODE resolve to exact terminal references, including an explicit
target on the same bus. Each winding of a three winding transformer keeps its
control mode, regulated terminal, limits, tap position and range, and number of
tap positions. A positive COD enables automatic adjustment, a negative COD
keeps the same mode with automatic adjustment disabled, and zero is fixed;
|COD| = 4 controls a DC line quantity on a two winding transformer, and
|COD| = 5 controls asymmetric active power flow. Unsupported RAWX tables,
including multiterminal DC, FACTS, GNE, induction machine, multisection line,
zone, owner, and interarea transfer records, remain only in byte exact same
format emission and produce counted diagnostics. Unknown RAWX tables and
caseid fields get the same retained source diagnostic, and fresh output
diagnoses detailed records and fields that RAWX cannot carry. Three winding
transformers are kept as typed records, and the indexed view lowers each one as
a star into \(Y_{\mathrm{bus}}\)/connectivity; two terminal DC lines map to
the neutral HVDC model. A switched shunt keeps its steady state susceptance
BINIT as the shunt b along with its mode, voltage band, regulated bus, and
step blocks, and a two winding transformer’s magnetizing susceptance survives a
round trip through MAG2. The reader converts CW 1/2/3, CZ 1/2/3, and CM
1/2 into the neutral tap ratio, system base impedance, and magnetizing
admittance, and fresh output uses the electrically equivalent canonical
CW = CZ = CM = 1 representation.
UCTE-DEF
PowerIO reads UCTE-DEF revisions 2003.09.01 and 2007.05.01 and writes
2007.05.01 under the token ucte (alias uct, extension .uct). The column
layout is the one PowSybl Core’s
UcteRecordParser
reads for both revisions; a 2003 file leaves the element name columns blank.
The reader maps the ##N nodes to buses named by their 8 character node code,
with a bus id equal to the node’s position in the block and the base kV taken
from the voltage level digit (750, 380, 220, 150, 120, 110, 70, 27, 330, or 500
kV). Each ##Z country is a ControlArea area named by its ISO code, and the
cross border nodes (country letter X) form one CrossBorder area named XX,
so a tie line keeps both ends and the cross border node’s own load and
generation. Node types PQ, PU, and UT map to PQ, PV, and reference; type QT (Q
and angle constant) reads as PQ with a warning. A node’s load and generation
become one load and one generator, with the PowSybl consistency rules applied
and reported: a missing set point reads as zero, a missing limit as 9999,
inverted limits are swapped, a set point outside its limits moves the limit,
and a voltage regulating node with no voltage reference reads as PQ.
##L lines are branches on the voltage level base with the total susceptance
split evenly. Busbar couplers (status 2 and 7) are switches, an equivalent
element (status 1 and 9) keeps that mark in its extras, and a reactance under
0.05 ohm reads as 0.05 ohm, as PowSybl reads it. A coupler whose two node codes
are equal is ignored with a diagnostic, following PowSybl Core’s
UcteImporter,
and a line joining two voltage levels is refused, as PowSybl refuses it. ##T
transformers are branches from the regulated winding (node 2) to the non
regulated winding (node 1), whose voltage level carries the impedance; the
rated voltages set the tap, and the magnetizing admittance sits on the
regulated side. ##R phase regulation multiplies the tap by
\(1 + n’ \delta u / 100\) and becomes a voltage control when it has a target,
while angle regulation applies the PowSybl asymmetrical or symmetrical formulas
to the tap and the phase shift and becomes a disabled active flow control. A
record with both folds the phase regulation’s ratio into the angle formula, as
PowSybl does, instead of multiplying the two results. Both regulations stay in
the branch extras so that fresh UCTE output can write them back as they were
read. ##TT special descriptions stay in the transformer extras and ##E
exchange schedules stay in the retained source only; both are reported with
READ.UCTE.RETAINED_SOURCE_ONLY. UCTE uses physical quantities and has no
system MVA base, so the balanced calculation view uses 100 MVA as its internal
normalization and does not report a missing source value. Each finding points
at its record through a source span.
Fresh output writes nodes grouped by country in bus order. A bus keeps its name
when it is a UCTE node code; otherwise it receives
<country><spot><level><busbar>: the country letter of its area’s ISO name,
else the area number’s entry in the UCTE country table in ISO order; the bus id
in base 36 as the five character spot; the voltage level digit nearest its base
kV (380 kV when the bus has none); and busbar 1, bumped on a collision. Each
derived code is reported with EMIT.UCTE.VALUE_SUBSTITUTED. A base kV that is
not a UCTE level is written under the nearest level with a warning; ohm, kV,
MW, and ampere values stay physical, so reading the file back expresses them
per unit on that level. A phase shift becomes a one step symmetrical angle
regulation, its step solved against the phase regulation written beside it, and
a voltage control with a tap range becomes a phase regulation; a line joining
two voltage levels is written as a transformer. UCTE requires a node’s
generation bounds to contain its dispatch, so the writer widens an inconsistent
interval and reports the substituted bounds. An out of service generator
contributes no dispatch but still supplies the node’s plant type letter, which
keeps that source classification stable on readback. Shunts, HVDC, storage,
static VAR compensators, three winding transformers, costs, capability columns,
angle limits, voltage bands and angles, rate B and C, remote regulation, and a
frequency other than 50 Hz are reported as dropped.
PowerWorld
.aux is read and written, .pwb binary cases are read only, and a .pwd
display file parses to a GeoLayer
(Geographic and display data). .aux has no system base,
so the reader defaults to 100 MVA. No third party .aux reader exists, so the
writer is validated by PowerIO’s own read back plus a PowerModels JSON bridge.
The .pwb layouts are reverse engineered; the decode evidence and coverage
matrix are maintainer notes at
powerio-tx/src/format/powerworld/FORMAT.md.
PSLF
.epc is read and written. The reader maps the static power flow core: buses,
lines, two and three winding transformers, generators, loads, fixed shunts,
controlled shunts at their initial g/b, and limited two terminal DC records.
Three winding transformers are kept as typed records, and the indexed view
lowers each one as a star into \(Y_{\mathrm{bus}}\)/connectivity. Unsupported
sections stay in the retained source text and produce diagnostics.
MATPOWER
Canonical MATPOWER output, for a case that did not start as MATPOWER, omits
dcline; the byte exact echo path keeps it when the case was read from MATPOWER.
Storage is written as an mpc.storage block.
egret
egret output writes HVDC as dc_branch, the element its reader already reads,
so the power, voltage, and loss fields survive a round trip; a dcline cost
curve and storage are the only things dropped. The reader takes the power flow
ModelData subset (numeric bus ids, scalar values). A document whose
system.time_keys vary the scalar profile parses to
TimeSeries<BalancedNetwork> through powerio::parse, whereas the component
crate’s powerio_tx::parse reads only the static profile and refuses it.
pandapower JSON
The pandapower JSON writer lays the power flow core out as split oriented
pandapowerNet tables. Line ohms are referred to the from bus voltage, as
pandapower’s build_branch reads them, and a bus with baseKV 0 writes vn_kv
set to \(1\) (warned) so the per unit impedances survive. A branch with a
tap, a shift, or terminals on two voltage levels becomes a trafo row with
tap_changer_type = "Ratio"; because pandapower’s magnetizing model is
inductive only, its MATPOWER charging b goes out as one bus shunt per terminal
(warned, \(Y_{\mathrm{bus}}\) exact). The file is labeled with f_hz set to
\(50\) and c_nf_per_km compensated, so a 60 Hz source keeps its exact
\(Y_{\mathrm{bus}}\). A reference bus without a generator gets an ext_grid
row, which reads back as a Ref generator. The writer also warns on dropped
HVDC, storage, capability columns, angle limits, rate B/C, nonfinite values
(written as JSON null), and costs poly_cost cannot carry. The reader models
ratio, ideal, and pandapower 2.x tap changers, off nominal
vn_hv_kv/vn_lv_kv, lv side taps, and shunt vn_kv scaling; ZIP load
composition, line shunt conductance, magnetizing branches, tabular tap
changers, reactive cost coefficients, and any other table with rows warn with
row counts.
PyPSA CSV folders
PyPSA CSV folders are canonicalized directory outputs rather than byte exact
text conversions. The mapping covers static buses, generators, loads, lines
(ohms on the bus0 voltage, as PyPSA computes them), transformers (rebased
between the system base and the transformer s_nom), shunts, storage units,
and base MVA. The reader maps links to HVDC with a warning, requires v_nom
and balanced CSV quoting, and warns on stores, nonzero g, and each CSV it
does not read (time series, carriers). The writer keys tables by bus name,
falling back to the numeric id when names collide (warned), and warns on
dropped HVDC, q limits, mbase, transformer angle limits, rate B/C, isolated
buses, nonfinite p limits, and slackless or normalized networks. Nonnumeric bus
names read back as dense synthetic ids with the originals on Bus.name.
DOE GO Challenge 3 JSON
DOE GO Challenge 3 JSON is a grid exchange format “for Challenge 3 and beyond,”
so the format name is broader than any one calculation type. PowerIO recognizes
its Challenge 3 input/problem data file and returns AcScucInstance. The
instance has the declared time points and durations, initial commitment and
dispatch, time varying bounds, costs and reserves, energy windows,
contingencies, and one shared BalancedNetwork, which instance.network()
returns. One directory or memory Source containing both the input/problem
data file and its matching output/solution data file returns AcScucSolution;
an output/solution data file on its own is rejected, because it has neither the
component definitions nor the time axis. Problem data is parse only. A complete
AcScucSolution emits the official output/solution data file, including bus
voltage, shunt step, device commitment, dispatch and reserves, AC line status,
transformer tap, phase shift and status, and DC line terminal power fields. The
pinned GO-3 model validates PowerIO’s small problem and output documents and
all of the D1/D2/D3 input/problem data files, and C3DataUtilities reports no
data, ignored, or solution errors for those documents. The older pinned GO-3
model and D1/D2/D3 files still have network.bus.con_loss_factor, a field that
version 1.1.1 of the data format removed; PowerIO keeps the original source and
reports one bounded diagnostic instead of treating it as an electrical network
or AC SCUC field. Optional bus location labels and incomplete coordinate pairs
stay in Bus.extras, and optional consumer descriptions, voltage setpoints,
and nameplate capacities stay in Load.extras; each reports
READ.GOC3.OPTIONAL_FIELD_UNTYPED. A producer description has no generator
metadata field to go to and reports READ.GOC3.RETAINED_SOURCE_ONLY.
Surge JSON
PowerIO reads and writes the versioned surge-json network document. The
reader maps buses, loads, fixed shunts, branches, generators, storage, and HVDC
links into BalancedNetwork, keeps the original source for same format echo,
and warns about source sections that stay only in the retained document. The
writer emits a canonical Surge network body for the supported power flow core,
and richer MATPOWER generator capability or ramp columns and unsupported cost
shapes are reported in the emission diagnostics. An HVDC link has the terminal
voltage setpoints, the reactive limits, and the loss model on its converter
terminals; a Surge link has no terminal reactive flow, no cost curve, and no
received power (the reader derives it from the setpoint and the loss model), so
those are warned. A link with converter or control detail beyond the neutral
converter this writer emits (firing angles, converter transformer taps,
commutation impedance, a DC voltage schedule) is warned on the way in.
DeepMind OPFData JSON
The DeepMind OPFData reader takes one raw JSON document from a FullTop or N-1
release into the balanced transmission model. Topology, limits, loads, shunts,
and quadratic costs come from grid; solved bus voltages, generator dispatch,
and branch flows come from solution. Powers and ratings are converted from
per unit, angles from radians, link indices from zero based to one based, and
flow columns from [pt, qt, pf, qf] into the canonical terminal order.
Original bus IDs and names, areas and zones, and frequency are absent, and the
solver’s initial generator values differ from the solved snapshot, so a
conversion to another format reports those facts. The adapter works from the
feature widths and the row and link counts in each file rather than from a case
name registry or expected element counts, so the same path covers all published
grid families (14 through 13,659 buses) and both FullTop and N-1 examples;
generator and branch outages appear as absent rows and links and are validated
against that example’s solution topology. The published releases are derived
from PGLib-OPF cases, but the reader does not use PGLib case names or a case
registry, so a document from another source is accepted when it follows the
same object layout, feature column order, units, and link rules. The paper’s
Appendix A is the published format definition and the PyTorch Geometric loader
is the executable reference. No separate JSON Schema or format version marker
is published, so a document that departs from that layout is rejected by the
reader’s shape and topology checks. An unrecognized object field stays in the
retained source and produces a projection warning, so same format echo still
works. The raw source echoes byte exactly; there is no canonical writer, .pt
cache reader, archive reader, downloader, or batch directory API.
IEEE Common Data Format
IEEE Common Data Format (ieee-cdf, alias cdf) is read only. The reader
takes the title card MVA base and date; the bus records (number, name, area,
loss zone, type, solved voltage and angle, load, generation, base kV, desired
voltage, MVAr or voltage limits, shunt G and B, remote controlled bus); the
branch records (tap and Z bus, circuit, type, R, X, B, the three MVA ratings,
control bus and side, turns ratio, phase shift angle, tap limits, step size,
and the controlled quantity limits); and the interchange records (area number,
slack bus, export, tolerance, code, and name). The tap bus is the from bus and
a nonzero turns ratio marks a transformer. A blank branch type reads as a
transmission line, as the PowSybl reader reads it; a type 1 through 4 branch
without a ratio reads as unity; and types 2, 3, and 4 get a regulating
transformer control block whose ntp derives from the step size. A type 1 bus
reads as PQ with a fixed reactive generator and the limit columns as its
voltage band, and every type 2 or 3 bus, along with any bus with nonzero
generation, gets a generator. The format has no active power limits, no machine
base, and no voltage limits, so pmin 0 MW, pmax 9999 MW, mbase equal to
the system base, and vmax/vmin 1.1/0.9 p.u. are assumed and reported as
READ.IEEE_CDF.VALUE_DEFAULTED. Loss zone names, tie lines, the branch area
and loss zone columns, alternate swing bus names, and the title originator,
year, and season survive in the retained source only
(READ.IEEE_CDF.RETAINED_SOURCE_ONLY).
A record cut before a mandatory field reads that field as zero and reports
READ.IEEE_CDF.RECORD_TRUNCATED with the record’s span. A header item count,
terminator, misplaced record, zero impedance branch, or undeclared bus
reference is READ.IEEE_CDF.SOURCE_MALFORMED, and a type or side code outside
the documented set is READ.IEEE_CDF.VALUE_SUBSTITUTED. A title card without a
positive MVA base, or a record whose bus numbers or numeric fields cannot be
decoded, ends the read with a spanned PARSE.IEEE_CDF.MALFORMED. The column
ranges follow PowSybl Core’s
IeeeCdfBusReader
and
IeeeCdfBranchReader,
which read the public archive files; those files place the last two branch
limits one column to the left of the 1973 table, and the reader accepts both
layouts. A .txt or .cdf file whose first card is a CDF title card is
detected without a declared format. Fresh output of this format is not
required, so there is no writer; emit to ieee-cdf is refused as read only,
and a case converts to any writable format instead. The PowSybl gate reads
every public IEEE case with PyPowSybl’s own CDF importer, compares its bus,
branch, generator, load, and shunt counts and its load and generation totals
with the PowerIO parse, then reloads fresh MATPOWER written from each case.
GridFM Parquet datasets
GridFM Parquet datasets (behind the gridfm feature, following the
GridFM data kit output schema)
parse to a scenario set of balanced networks over one shared element identity
map. Each scenario recovers the complete native balanced table data: bus types,
voltages, and limits; nodal load and shunt totals; generator dispatch, bounds,
and the cp0/cp1/cp2 polynomial as given; branch
r/x/b/tap/shift/rate_a/angle limits and pf/qf/pt/qt terminal flows; and
baseMVA. Dense bus indices, nodal demand records, and line classification for
a unit tap with zero shift are GridFM source facts and do not produce reader
diagnostics.
Writing a richer network reports the projections into GridFM’s fixed tables:
source bus renumbering, several loads or shunts combined at one bus, metadata
and equipment with no column, and costs outside the fixed quadratic
representation. If a branch has no solution in the source, the writer evaluates
pf/qf/pt/qt from the stored bus voltages and reports that derived value.
Generated component identities give PowerIO stable references and are not
treated as source metadata. A native GridFM network writes without findings.
Matrices and graphs describes the dataset the writer produces.
Both directions need the gridfm cargo feature, which the CLI and the Python
wheel include.
IIDM versions
PowerIO reads every IIDM serialization version PowSybl has published and writes
1.17. A document of an older version is read with that version’s rules and
reported with READ.XIIDM.VERSION_COMPATIBILITY, and a namespace naming a
version outside this table is refused with PARSE.XIIDM.VERSION_UNSUPPORTED.
The table lists, per version, what the reader does differently from 1.17, and
each row is checked against the PowSybl fixture of the same network in
tests/data/xiidm/powsybl.
| Version | Read | What the version states differently |
|---|---|---|
| 1.0 | XIIDM | iTesla namespace. Busbar sections carry the calculated bus v and angle. Three winding transformers have no ratedU0 (leg 1 rated voltage is the impedance base), no leg 1 tap changer, and no phase tap changers. Tie lines state both half lines inline with _1/_2 suffixes and ucteXnodeCode. Shunts state bPerSection, maximumSectionCount, and currentSectionCount and never regulate. Static VAR compensators spell voltageSetPoint and reactivePowerSetPoint. Batteries spell p0 and q0. Ratio tap changers state targetV. Loading limits sit directly on the equipment as the selected DEFAULT group. A switch closing a bus or node onto itself is discarded, as PowSybl does. No minimumValidationLevel. |
| 1.1 | XIIDM | PowSybl namespace. Calculated buses under node breaker topology. Three winding transformer ratedU0, leg 1 tap changers, and phase tap changers. |
| 1.2 | XIIDM | fictitious on every identifiable, targetDeadband on tap changers, ratedS, and shunt voltage regulation. |
| 1.3 | XIIDM | Shunt linear and nonlinear models with sectionCount, aliases, and boundary line generation. |
| 1.4 | XIIDM | Alias types. |
| 1.5 | XIIDM | Active and apparent power limits. |
| 1.6 | XIIDM | Voltage levels outside substations and VSC regulating terminals. |
| 1.7 | XIIDM | minimumValidationLevel and the equipment validation namespace. |
| 1.8 | XIIDM | Batteries spell targetP and targetQ. Fictitious bus injections (reported, not retained). Self connected switches are refused. |
| 1.9 | XIIDM | Shunt p. |
| 1.10 | XIIDM | Tie lines reference two dangling lines. Load models. |
| 1.11 | XIIDM, JIIDM | pairingKey replaces ucteXnodeCode. Subnetworks. Voltage angle limits (reported, not retained). |
| 1.12 | XIIDM, JIIDM | Operational limits groups and ratio tap changer regulationMode/regulationValue. |
| 1.13 | XIIDM, JIIDM | Areas, isCondenser, and active power control 1.2. |
| 1.14 | XIIDM, JIIDM | Solved tap positions and section counts, regulating on static VAR compensators and phase tap changers. |
| 1.15 | XIIDM, JIIDM | DC nodes, grounds, lines, switches, and AC/DC converters. |
| 1.16 | XIIDM, JIIDM | shuntCompensator and boundaryLine element names and multiple selected limit groups. |
| 1.17 | XIIDM, JIIDM | Optional zero conductance and susceptance, retained and lowTapPosition defaults, DC switch resistance. Writers use this version. |
JIIDM has no namespace, so the document’s version field selects the same
rules and minimumValidationLevel selects the validation level. PowSybl ships
JIIDM fixtures from 1.11 on; an older version value reads with that version’s
XML rules.
Missing generator costs
PSS/E .raw files have no generator cost curves. Converting a PSS/E case to
MATPOWER writes mpc.gen and omits mpc.gencost with a warning, because
powerio does not invent zero costs. If your workflow needs costs, pick a policy
explicitly:
powerio convert case.raw --from psse --to matpower --missing-gen-cost zero -o case.m
powerio dcopf case.m -o out --missing-gen-cost quadratic --default-gen-cost 0.01,2.0,0.0
powerio gridfm case.raw --from psse -o out --missing-gen-cost zero
preserve: leave missing costs absent (default for conversion and GridFM export);require: fail on an in service generator without cost (default for DC OPF export);zero: fill missing rows with a MATPOWER polynomial cost[0, 0, 0];quadratic: fill missing rows with--default-gen-cost C2,C1,C0.
--gen-cost-csv overrides costs by generator row before the missing cost
policy runs. The header is gen_index,bus,c2,c1,c0,startup,shutdown:
gen_index is zero based in the current generator table, bus must match that
generator’s bus id (which catches a stale table after reordering), and
startup/shutdown default to zero. GridFM stores cp0/cp1/cp2 columns;
missing or unsupported costs still write zero columns, and the manifest
separates missing_cost_gens, unsupported_cost_gens, zeroed_cost_gens, and
synthesized_gen_costs.
Geographic and display data
PowerIO keeps coordinates when a supported source provides them. They are optional. No parser invents them, and when you emit a located case to a network format that has no place for coordinates, the writer reports the loss.
A standalone geographic document parses to powerio.GeoLayer, a value like any
other case. The canonical .geo.json, plain GeoJSON, CSV or JSON records with
aliased field names, headerless buscoords CSV, and a PowerWorld .pwd display
all parse to it; a .pwd becomes a diagram space layer whose features target
substations.
use powerio::{PioValue, emit, parse, serialize};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let module = parse("layer.geo.json")?;
let PioValue::GeoLayer(layer) = module.value() else {
panic!("a layer document parses to powerio.GeoLayer");
};
// A layer travels through PowerIO IR and out as the canonical document.
serialize(&module, "layer.pio.json")?;
emit(&module, "geo-json", "layer.geo.json")?;
Ok(())
}
If you need the raw display record, PwdDisplay is still available; it has the
canvas, the save stamp, and the symbol table in diagram coordinates.
Coordinate fields
Balanced and multiconductor networks use the same JSON shape for coordinates:
#![allow(unused)]
fn main() {
pub struct Location {
/// Longitude for geographic coordinates.
pub x: f64,
/// Latitude for geographic coordinates.
pub y: f64,
/// Point origin when it differs from the network default.
pub kind: Option<CoordsKind>,
}
pub enum CoordsKind { Source, Synthetic, Manual, Derived }
pub struct GeoMeta {
pub space: CoordinateSpace,
pub kind: Option<CoordsKind>,
}
pub struct Canvas {
pub width: Option<f64>,
pub height: Option<f64>,
pub units: Option<String>,
}
pub enum CoordinateSpace {
Geographic { crs: Option<String> },
Projected { crs: Option<String> },
Diagram { canvas: Option<Canvas> },
Unknown,
}
}
Balanced networks reach these types as
powerio::geo::{Location, CoordsKind, CoordinateSpace, GeoMeta, Canvas}
through BalancedNetwork.geo and Bus.location; multiconductor networks use
the matching powerio_dist::geo types through MulticonductorNetwork.geo and
DistBus.location, and a package serialization test keeps the two JSON shapes
identical. A branch can also have a polyline route (Branch.route,
DistLine.route) when the source provides intermediate geometry. Without one,
a renderer draws the branch endpoint to endpoint from the bus locations.
The coordinate space belongs to the network, and a point sets its own kind
only when its origin differs from the network default. For geographic
coordinates, x is longitude and y is latitude, which is GeoJSON axis order,
and a missing CRS means EPSG:4326. kind says whether the coordinates came
from the source, a generated layout, a manual edit, or a derived transform.
Harvest and emit
A parser promotes source coordinates into location, sets the space, and
removes the raw keys from extras. Writers read location.
| Format | Fields | Space |
|---|---|---|
| PowerWorld aux | Latitude:1/Longitude:1 bus columns, else the bare Latitude/Longitude pair (SubNum stays in extras: it is identity rather than geometry) | geographic |
| pandapower | bus geo GeoJSON Point strings | geographic |
| PyPSA | buses.csv x/y | geographic |
| DOE GO Challenge 3 | bus longitude/latitude | geographic |
| OpenDSS | Buscoords | unknown; a diagnostic identifies values within longitude and latitude bounds |
| BMOPF JSON | longitude/latitude (the BMOPFTools sideload convention; emission is opt in via BmopfEmitOptions::sideload_coordinates) | geographic |
MATPOWER, PSS/E, PowerModels, egret, PSLF, and Surge have no place for
geometry. Emitting a located case to one of them reports the dropped locations,
in the same way it reports a dropped base_frequency. If you need the
coordinates to survive, powerio geo extract writes them as a separate layer
document.
The geographic document
Coordinates also arrive and leave as files of their own, such as a Buscoords
CSV next to a DSS master or a GeoJSON export from a GIS tool, and a renderer
can hand back the layout it computed the same way. The container for such a
file is GeoLayer, which the Rust facade’s parse returns as
PioValue::GeoLayer.
The canonical form is a GeoJSON FeatureCollection with one foreign member,
powerio_geo, and the suggested extension is .geo.json:
{
"type": "FeatureCollection",
"powerio_geo": { "powerio_version": "0.11.0", "space": "geographic", "kind": "source" },
"features": [
{ "type": "Feature",
"geometry": { "type": "Point", "coordinates": [-80.05, 34.20] },
"properties": { "target": "bus", "id": "312", "uid": "buses:11" } },
{ "type": "Feature",
"geometry": { "type": "LineString", "coordinates": [[-80.05, 34.20], [-80.10, 34.30]] },
"properties": { "target": "branch", "uid": "branches:4", "from": "312", "to": "410" } }
]
}
When the space is geographic this is valid RFC 7946 GeoJSON, so GIS tools open it directly.
Parsing is tolerant and emission is canonical. GeoLayer::parse takes UTF-8
text plus a file name hint and touches no filesystem. It accepts headerless
buscoords CSV (bus, x, y), CSV and JSON records with aliased field names
(bus_i/bus/id, lat/latitude/y, lon/lng/longitude/x, branch
endpoint pairs), and GeoJSON Point and LineString features. A feature refers to
its element by up to three key fields, tried in order: uid, then id, then
case insensitive name. A branch route can also fall back to the unordered
(from, to) bus pair. A bare integer branch id (branch, branchid,
branchnumber, catsid) is accepted on the way in as a one based positional
row alias and is not written back; the uid in the payload is the durable key.
A branch key ignores a bare id property, because GIS exports and RFC 7946
tooling put a feature row counter there.
BalancedNetwork::to_geo_layer() turns a network’s coordinates into a layer,
and BalancedNetwork::apply_geo_layer(&layer) applies one and returns a
GeoApplyReport with the matched and unmatched feature counts plus
unlocated_buses and unlocated_branches, the elements still without geometry
when the pass ends. Together those counts let you tell a layer that matched
nothing from a model that needed nothing, and report.require_located() is the
one line check for a caller that wants everything placed. The multiconductor
equivalents, to_dist_geo_layer and apply_dist_geo_layer, live in
powerio::dist_geo. The CLI wraps the same functions:
$ powerio geo extract case.aux -o case.geo.json
$ powerio geo apply case.m layout.csv -o placed.m
$ powerio geo convert buscoords.csv -o case.geo.json
PowerWorld display files
The .pwd reader returns a diagram space GeoLayer whose features place the
decoded substations. Python also keeps the raw display compatibility helper
parse_display, which returns
DisplayData(kind="powerworld", data=PwdDisplay(...)) with the canvas
dimensions, a timestamp, and the substation symbols.
The facade helpers connect it to the geo model. to_geo_layer_from_pwd lifts
the substation symbols into a diagram space GeoLayer, which is also what
powerio geo extract case.pwd does. to_geo_layer_from_aux_text parses the
Latitude and Longitude columns of an AUX Substation table straight into a
geographic layer. apply_substation_points joins either layer onto buses
through the SubNum extras key. to_lonlat_from_pwd_mercator is a documented,
approximate inverse of the projection PowerWorld’s auto generated layouts use,
for when you want to place a diagram on a map. The component crate keeps
to_geo_layer_from_aux_substations(&AuxFile) for parser authors, since the
facade does not expose its borrowed parser type.
A bus row in a complete case export has its own coordinates as well. The aux
reader promotes the substation Latitude:1/Longitude:1 pair, or, when those
are absent, the bus’s own bare Latitude/Longitude pair, into
Bus.location, and a promoted pair leaves extras.
In Rust a display file parses to PioValue::GeoLayer. There is no path from a
display file to a BalancedNetwork; it is always a layer, and both module
emission and PowerIO IR handle it as one.
Distribution graph projection
MulticonductorNetwork::to_graph() returns a bus and terminal graph and does
not need coordinates to do it; in Python that is dist_net.to_graph(). Graph
topology and geographic placement stay separate data.
PowerIO stores and transports coordinates and does not compute them. Laying out
a case that has none is renderer math and belongs in the consumer, which can
store the result with kind = synthetic so the origin of the coordinates
survives.
Python has parse_geo, and both network types have to_geo_layer() and
apply_geo_layer().
Rust, Python, Julia, and C
Rust, Python, Julia, and C expose the same operations on the same power system types:
source -> parse -> PioModule<T> -> calculation or update -> emit
|
+-> serialize -> PowerIO IR
parse and emit handle grid exchange formats, while serialize and
deserialize handle PowerIO IR. A calc_* function returns a derived matrix,
vector, report, or count, and a to_* function transforms a value in memory
into another semantic type. The nouns are stored values and fields: module,
value, diagnostics, network, bus, branch, generator, load, operating point,
time series, scenario set, instance, solution.
| Meaning | Rust | Python | Julia | C ABI 7 |
|---|---|---|---|---|
| name a file | pass the name to parse | pass a path to parse | pass a path string to parse | pio_source_open |
| acquire memory | Source::from_memory(name, bytes) | pass a file or bytes-like object | pass IO or AbstractVector{UInt8} | pio_source_from_memory |
| parse | parse(input), parse_with_options(input, &options) | parse(source, format=..., name=...) | parse(source; format=..., name=...) | pio_parse |
| module value | module.value() | module.value | module.value | pio_module_value |
| module diagnostics | module.diagnostics() | module.diagnostics | module.diagnostics | pio_module_diagnostics |
| emit a format | emit(&module, format, destination) | emit(module, format, destination=None) | emit(module, format, destination=nothing) | pio_emit |
| serialize IR | serialize(&module, destination) | serialize(module, destination=None) | serialize(module, destination=nothing) | pio_module_serialize |
| deserialize IR | deserialize(source) | deserialize(source) | deserialize(source) | pio_module_deserialize |
| apply updates | apply_updates | apply_updates | apply_updates! | pio_apply_updates |
To find out what you parsed, Rust matches on the PioValue case that
module.value() returns, Python uses isinstance, Julia dispatches on
PioModule{T} and on the concrete value types, and C compares canonical
structural type names with pio_value_is_type before calling the typed
accessor for the type it handles.
Only Rust and C have a Source type, because both need an explicit owner for
acquired bytes. A Python path, open file, or bytes-like object, or a Julia
path, IO, or byte vector, already says where the bytes come from, and the
interpreter owns them, so parse takes them directly and reads the source name
from the path or from the name argument.
A format made of related files goes through the same parse. For GO Challenge
3, a directory holding the problem file returns AcScucInstance, and once the
matching solution file sits beside it the same call returns AcScucSolution.
Member access
Member names match across the four languages; what differs is the syntax, which follows what each language’s users expect.
| Language | A member | A table’s length |
|---|---|---|
| Rust | accessor method: module.value(), module.diagnostics(), network.buses() | network.buses().len() |
| Python | read only property: module.value, module.diagnostics, network.buses | len(network.buses) or network.n_buses |
| Julia | property: module.value, module.diagnostics, net.buses | length(net.buses) |
| C | one function per member: pio_module_value, pio_module_diagnostics | a _count function |
Rust containers (PioModule, BalancedNetwork, MulticonductorNetwork,
instances, and solutions) keep their fields private because they maintain
invariants; value_mut severs retained source bytes, for example, and
add_diagnostic rejects a duplicate identifier. Rust element records (Bus,
Branch, Generator, Load, and the rest) are plain public structs, because
struct literals and pattern matching are how Rust users build and read them.
Python keeps the n_* counts because a table property builds one dict per row.
A Python element is a dict keyed by the Rust field names, while a Julia element
is an immutable struct whose field names are the C ABI names, which spell out
the quantity and unit (vm_pu, active_power_mw). Unifying those two sets of
field names is listed under Known limits.
Collections
TimeSeries<T> and ScenarioSet<T> contain typed entries, and each language
reaches them its own way.
| Language | Operations |
|---|---|
| Rust | len, iter, checked get |
| Python | len, iteration, series[index], scenarios[id]; TimeSeries is a Sequence and ScenarioSet a Mapping |
| Julia | length, iteration, 1-based getindex |
| C | zero-based length and entry access; scenario lookup by identifier |
An entry is either the contained value or a view rooted in the owning module, so indexing does not serialize, expand, or copy a complete network.
Calculations
The DC matrix and vector functions have the same names in each language:
calc_incidence_matrix
calc_branch_susceptances
calc_bus_susceptance_matrix
calc_branch_flow_matrix
calc_branch_phase_shift_injection
calc_bus_phase_shift_injection
calc_branch_flow_dc
calc_bus_injection_dc
Rust and C use zero based sparse matrix positions, Python sparse matrices use SciPy’s zero based positions, and Julia presents one based indices. Stable component identities and source identifiers do not change at a language boundary. Matrices and graphs gives the signs and equations.
Errors and ownership
Rust returns Result, Python raises PowerIOError subclasses, Julia throws a
PowerIOError with structured diagnostics, and C returns a documented failure
value and writes one PioError * through its error output. Each failure has a
stable diagnostic code.
Python and Julia keep the native owner alive behind the borrowed typed views they hand you, whereas C callers retain and release opaque handles themselves. Ownership differs; the data types and the operation names do not.
Python API
Install the base package for parsing, emission, PowerIO IR, and typed values:
pip install powerio
The matrix and graph helpers need optional packages, so install the extra you want:
pip install 'powerio[matrix]' # NumPy and SciPy
pip install 'powerio[graph]' # NetworkX
pip install 'powerio[gridfm]' # Polars
pip install 'powerio[all]' # the three above
pip install 'powerio[pandas]' # pandas and PyArrow tables, Python 3.10 or later
pip install 'powerio[mcp]' # the MCP server, Python 3.10 or later
Importing powerio and calling parse, emit, serialize, or deserialize
does not import any of those optional packages.
Parse one source
powerio.parse accepts a path, a file object, or a bytes-like object. A str
is always a path, so wrap raw text in io.StringIO. There is no Python
Source class, because a path, file object, or bytes-like value already says
where the bytes come from and the interpreter owns them; parse takes it
directly. Rust and C build a Source because they need that ownership made
explicit, as Rust, Python, Julia, and C explains.
from io import StringIO
from pathlib import Path
import powerio
case = powerio.parse(Path("case9.m"))
case_from_text = powerio.parse(
StringIO(matpower_text), format="matpower", name="case9.m"
)
case_from_binary = powerio.parse(
pwb_bytes, format="pwb", name="case.pwb"
)
format is optional when the source name and content identify the format.
name applies only to memory and file object sources, where it supplies a
source name for diagnostics and format detection. There is no separate
parse_file, parse_text, or parse_bytes API.
Parse a GO Challenge 3 solution
Put the GO Challenge 3 problem file and its matching solution file in one
directory, and the ordinary parse call reads both:
solution = powerio.parse("scenario_002")
assert isinstance(solution.value, powerio.AcScucSolution)
With only the problem file, the same call returns AcScucInstance. A solution
file on its own fails, because it has neither the component definitions nor the
time axis. The solution module keeps both files and its diagnostics.
Module values and diagnostics
Parsing returns a PioModule[T], where module.value is the concrete Python
value and module.diagnostics is the list of diagnostics stored on that
module.
module = powerio.parse("case9.m")
if isinstance(module.value, powerio.BalancedNetwork):
print(module.value.n_buses)
for diagnostic in module.diagnostics:
print(diagnostic.code, diagnostic.severity, diagnostic.message)
Diagnostics live on the module rather than on the contained network or
solution. To find out what you have, use Python’s normal type system; there is
no .kind property, kind enum, or typed narrowing helper.
The value classes are BalancedNetwork, dist.MulticonductorNetwork,
OperatingPoint, TimeSeries, ScenarioSet, GeoLayer, the PF, OPF, and
SCUC instances and solutions, and SocwrOpfSolution.
Emit grid exchange formats
powerio.emit is the only function that writes a grid exchange format:
result = powerio.emit(module, "matpower")
text = result.text
result = powerio.emit(module, "psse", "case.raw")
result = powerio.emit(module, "pypsa", "case-directory")
With no destination the artifacts stay in memory; a path destination writes one
file or a directory, and a writable file object accepts a single file artifact.
An EmitResult has the artifacts (one Artifact per produced file, with its
name and either data for a memory result or path after a filesystem
commit), the layout, the fidelity, and the emission diagnostics. result.text
is the UTF-8 memory artifact when there is a single one, and None otherwise.
PowerIO IR has its own pair of functions:
ir = powerio.serialize(module)
powerio.serialize(module, "case.pio.json")
same_module = powerio.deserialize(ir.artifacts[0].data)
The IR header is "schema": "pio-ir" with the integer "version": 2, and
powerio.versions()["powerio_ir"] reports both. The producer record gives
powerio.__version__ separately. deserialize refuses a document whose schema
or version it does not support and reports what it found. PowerIO IR is not a
grid exchange format, so it does not appear in format discovery.
Collections
TimeSeries behaves like a Python sequence and ScenarioSet like a mapping:
series = module.value
first = series[0]
for value in series:
use(value)
scenarios = scenario_module.value
base = scenarios["base"]
for scenario_id in scenarios:
use(scenario_id, scenarios[scenario_id])
Entries are owner rooted typed values, so indexing does not serialize or copy a complete network.
Typed updates
PowerIO supplies OperatingPointUpdate, NetworkUpdate, and
CalculationUpdate. Each update targets a stable ComponentId, and power
values use ActivePower, ReactivePower, or ApparentPower so the unit is
explicit.
report = powerio.apply_updates(
module,
[
powerio.OperatingPointUpdate.set_load_active_power(
load_id, powerio.ActivePower.megawatts(42.0)
)
],
)
for change in report.changes:
print(change.component_id, change.field)
print(report.connectivity_changed)
The whole batch is validated before anything is mutated, so a failed batch
leaves the module unchanged. The UpdateReport lists each change and says
whether energized connectivity changed.
Matrices and vectors
The derived calculations are calc_* methods on BalancedNetwork:
A = network.calc_incidence_matrix()
b = network.calc_branch_susceptances()
B = network.calc_bus_susceptance_matrix()
Bf = network.calc_branch_flow_matrix()
p_branch = network.calc_branch_flow_dc(voltage_angles)
p_bus = network.calc_bus_injection_dc(voltage_angles)
calc_admittance_matrix, calc_bprime_matrix, calc_ptdf, calc_lodf,
to_normalized, and to_networkx are methods of the same class. SciPy is
imported only when you ask for a sparse matrix, NumPy only for the array based
helpers, and NetworkX only inside to_networkx.
Other functions
| Function | Result |
|---|---|
resolve_format(name) | the canonical FormatInfo for a token or alias, or None |
features() | which build features the installed extension carries |
versions() | the release, the PowerIO IR identity, and the BMOPF schema version |
parse_geo(text, name_hint=None) | a geographic layer in canonical form with its diagnostics |
parse_display(path, format=None) | the raw PowerWorld .pwd display record as DisplayData |
from_ppc(ppc) | a BalancedNetwork from a pandapower or PYPOWER case dictionary |
PioModule.from_value(value) | a module around a value built in Python |
module.to_balanced_report(), module.to_balanced() | the multiconductor to balanced transformation |
Errors
A parse failure raises PowerIOParseError, and valid data that cannot satisfy
an operation raises PowerIODataError. Both derive from PowerIOError and
have a stable diagnostic code, so branch on .code rather than on the rendered
message. A Rust panic inside the extension raises PowerIOError with code
BIND.PY.PANIC instead of pyo3_runtime.PanicException, and the module is
left unchanged, because each mutation is built in full before it is installed.
MCP server
The optional MCP server accepts paths, grid exchange content held in memory,
and serialized PowerIO modules through the powerio_ir field. Electrical
inputs and outputs stay PowerIO types and PowerIO IR; the server does not
define another network, calculation, update, or solution schema.
Filesystem access is off unless POWERIO_MCP_ALLOWED_ROOTS lists the
directories the server may read, and remote URI schemes are rejected. Host
approval, request identifiers, timeouts, and cancellation are MCP transport
concerns and do not touch the PowerIO data.
C ABI
powerio-capi exports ABI 7 through powerio-capi/include/powerio.h. The
header is generated from the Rust declarations and checked in. Regenerate it
with:
cbindgen --config powerio-capi/cbindgen.toml --crate powerio-capi \
--output powerio-capi/include/powerio.h
scripts/capi-header-parity.sh compares the exported symbols with the header
in every CI feature job, and scripts/capi-header-regen.sh regenerates the
header with cbindgen and diffs it once. Do not edit the header by hand.
ABI 7 is the only C API in PowerIO 0.11; symbols from ABI 4, 5, and 6 are
not exported and have no aliases. Compare pio_abi_version() with
PIO_ABI_VERSION before you use the library.
The exported symbol set is fixed. The gridfm cargo feature adds GridFM
Parquet parsing and emission behind the same entry points, and the arrow,
matrix, dist, and prob feature names are still accepted by the build but
gate nothing. pio_schema_report returns a JSON document with the release
(powerio_version), the ABI (abi), the PowerIO IR schema name and
generation (powerio_ir with schema and version), the BMOPF schema
version, the compiled features, and the diagnostic namespaces and error
categories.
Parse and inspect a module
PioError *error = NULL;
PioSource *source = pio_source_open("case9.m", 7, &error);
PioModule *module = pio_parse(source, NULL, 0, &error);
PioValueHandle *value = pio_module_value(module);
if (!pio_value_is_type(value, "powerio.BalancedNetwork", 23)) {
/* handle an unexpected PowerIO type */
}
PioBalancedNetwork *network = pio_value_balanced_network(value, &error);
size_t buses = pio_balanced_network_bus_count(network);
PioDiagnostics *diagnostics = pio_module_diagnostics(module);
for (size_t i = 0; i < pio_diagnostics_len(diagnostics); i++) {
PioStringView code = pio_diagnostic_code(diagnostics, i);
/* code.data has code.len bytes and is not NUL terminated */
}
pio_diagnostics_release(diagnostics);
pio_balanced_network_release(network);
pio_value_release(value);
pio_module_release(module);
pio_source_release(source);
Use pio_source_from_memory for text or binary bytes you already hold in
memory; both source constructors feed the same pio_parse.
pio_geo_layer_parse reads a geographic layer straight from text, with no
source object, for callers that have the layer document in memory.
pio_module_value returns an owner rooted value handle, and the exact typed
accessors return owner rooted views without serializing or copying the module
value. Releasing a module does not invalidate a child you have retained.
Every opaque handle has matching retain and release functions, and
release(NULL) is a no-op.
Structural type names have replaced ordinal kind integers. Check the type
with pio_value_type_name or pio_value_is_type, then call the exact typed
accessor for the type you handle.
Diagnostics and errors
Fallible functions take one PioError ** output and signal failure with a
null return or a documented failure value. Inspect pio_error_code,
pio_error_message, and pio_error_diagnostics, and branch on the stable
code rather than the message text. If you pass a null error output the error
is discarded.
Every string and buffer comes with an explicit length. PioStringView,
PioByteView, PioSizeView, and PioF64View borrow their data from an owning
handle and need not end in NUL.
Emit and serialize
pio_emit writes a grid exchange format. With a memory destination the
artifact bytes stay in the returned PioEmitResult; with a path destination
the artifacts are written to disk and the result holds the same list of
artifacts and the same diagnostics.
PioDestination *destination = pio_destination_memory("case", 4, &error);
PioEmitResult *result = pio_emit(module, "matpower", 8, destination, &error);
for (size_t i = 0; i < pio_emit_result_artifact_count(result); i++) {
PioArtifact *artifact = pio_emit_result_artifact(result, i, &error);
PioStringView name = pio_artifact_name(artifact);
PioByteView bytes = pio_artifact_bytes(artifact);
/* consume name and bytes before releasing artifact */
pio_artifact_release(artifact);
}
pio_emit_result_release(result);
pio_destination_release(destination);
pio_module_serialize writes PowerIO IR and pio_module_deserialize reads
it. The IR header is "schema": "pio-ir" with integer "version": 2, and
pio_schema_report reports both; the producer record names the PowerIO
release separately. pio_module_deserialize refuses an unsupported schema
name or generation and tells you what it found. ABI 7 has no reader for
earlier generations and no module JSON aliases.
Collections, updates, and calculations
Time series and scenario set handles give you the length and owner rooted access to each element. Positions are zero based in C, and scenario sets can also be looked up by scenario ID.
Typed update constructors produce PioOperatingPointUpdate,
PioNetworkUpdate, and PioCalculationUpdate. pio_apply_updates validates
the whole batch before it applies anything and returns a PioUpdateReport
listing the exact component IDs and fields it changed, and whether energized
connectivity changed.
Named matrix and vector functions expose the public DC calculations directly:
pio_calc_incidence_matrix
pio_calc_branch_susceptances
pio_calc_bus_susceptance_matrix
pio_calc_branch_flow_matrix
pio_calc_branch_phase_shift_injection
pio_calc_bus_phase_shift_injection
pio_calc_branch_flow_dc
pio_calc_bus_injection_dc
Sparse matrices come back as owned CSR arrays and vectors as owned double
arrays. The C API has no public DC data bundle.
CLI and MCP
The powerio command
The powerio binary runs the same operations from a shell, and with no subcommand it opens the interactive TUI.
powerio convert case14.m --to psse -o case14.raw # parse + emit, findings on stderr
powerio summary case14.m # the canonical network summary JSON
powerio serialize case14.m -o case14.pio.json # PowerIO IR
powerio verify case30.m --kind bdoubleprime # matrix stats and the SDDM check
powerio batch -i tests/data -o out --matrices bprime,bdoubleprime
powerio sensitivities case30.m -o out # PTDF and LODF
powerio dcopf case30.m -o out # the static DC OPF bundle
powerio gridfm case14.m -o out # a GridFM Parquet dataset
powerio geo extract case.aux -o layer.geo.json # standalone geographic layers
The other subcommands are batch, which writes matrix families for every
case in a directory, gen, which writes synthetic cases, geo apply and
geo convert, corpus, the private corpus harness described in
Corpus harness, and tui. dcopf accepts the alias
dc-opf. --from and --to take the format tokens and aliases of the
format table; iidm and rawx are accepted as
input spellings only.
Format names, structural value types, and diagnostic codes are the same
strings the language APIs use. Diagnostics print one per line on stderr as
CODE: message.
Standard input and output
convert, summary, serialize, verify, dcopf, and sensitivities read
the case from standard input when the input is -. A stream has no file name
to infer a format from, so --from is required, and a gridfm dataset is a
directory, so it cannot arrive on a stream at all. convert and serialize
write a single text result to standard output when -o is - or omitted; a
directory format such as pypsa-csv or cgmes needs an output directory.
cat case9.m | powerio convert - --from matpower --to psse -o - > case9.raw
powerio serialize - --from psse < case9.raw
Exit status
| Status | Meaning |
|---|---|
| 0 | success |
| 1 | a failure without a PowerIO error category |
| 2 | request: the arguments name something the request cannot satisfy (a format the writer cannot produce, a missing --from for standard input); clap usage errors also exit 2 |
| 3 | io: a path could not be read or written |
| 4 | parse: the input is malformed, or a refused include left the output incomplete |
| 5 | data: valid input that the operation cannot satisfy |
| 6 | output: the writer could not produce the requested format |
The categories are the ErrorCategory values the Rust and Python APIs report
on every failure, so a shell script and a Python caller branch on the same
five names.
Every failure the binary reports is a PowerIO error with a registered
diagnostic code, including the ones the command line raises itself
(REQUEST.CLI.FORMAT_REQUIRED, REQUEST.CLI.TARGET_UNSUPPORTED,
REQUEST.CLI.OUTPUT_REQUIRED, REQUEST.CLI.FAMILY_MISMATCH,
REQUEST.CLI.OPTION_INVALID, REQUEST.CLI.NO_CASES,
PARSE.CLI.ERRORS_REPORTED, VALIDATE.CLI.INPUT_LACKS_DATA,
EMIT.CLI.SIDECAR_PATH, EMIT.CLI.ERRORS_REPORTED). A reader that reports
errors ends the run with PARSE.CLI.ERRORS_REPORTED and status 4 after the
output is written; a writer that reports errors ends it with
EMIT.CLI.ERRORS_REPORTED and status 6. A failure that reaches the top of
the program without a registered code is reported as BIND.CLI.UNCLASSIFIED
and exits 1.
Diagnostics format
--diagnostics-format text (the default) prints one CODE: message line per
diagnostic as the command runs, wrote <path> when a file lands, and a failure
as Error: and Caused by: lines.
--diagnostics-format json (accepted before or after the subcommand) prints
one JSON array on stderr when the command ends, and nothing else. The array
holds every diagnostic the run produced, warnings and errors alike, as PowerIO
IR diagnostic records: the same encoding a module’s diagnostics field carries
in a .pio.json document, with the same fields (id, severity, code,
message, target, spans, related, details, suggested_action). A
failure adds its record, and each further reason in its cause chain becomes a
note record whose related names the failure record:
[{"id": "failure", "severity": "error", "code": "READ.IO.OPEN",
"message": "cannot open source `case9.m`"},
{"id": "d0", "severity": "note", "code": "READ.IO.OPEN",
"message": "No such file or directory (os error 2)", "related": ["failure"]}]
The exit status stays the process exit status and is not repeated inside the records.
The MCP server
powerio-mcp (from the Python package) calls the public Python API directly.
Its tools are parse, emit, summarize, diagnostics, to_normalized,
calc_matrix, to_balanced_report, to_balanced, display, and about.
parse returns serialized PowerIO IR, and the tools that accept powerio_ir
deserialize that same IR rather than defining a network, collection,
calculation, update, or solution representation of their own.
You address a collection entry with a plain zero based time_index or a
scenario_id; no tool exports or expands an entry into a static network.
emit(format, destination) writes a file or directory, and if you omit
destination it returns the artifacts in memory. Diagnostics stay structured
records with code, severity, message, target, and source spans.
pip install 'powerio[mcp]' # Python 3.10 or later
powerio-mcp # stdio transport
The server accepts exactly one input source: serialized PowerIO IR, a grid
exchange path, or grid exchange content in memory. Filesystem access is off
unless POWERIO_MCP_ALLOWED_ROOTS names the directories the server may read,
and remote URI schemes are rejected.
Known limits and versions
Known limits
- Format profiles are bounded. PyPSA support covers the CSV electrical profile; multi carrier components, investment periods, and stochastic data are retained and reported rather than typed, and PyPSA NetCDF does not parse. Egret support is the scalar network profile with time series, with unit commitment fields outside it. OpenDSS support is the static circuit; load shapes and solve instructions are retained and reported.
- DOE GO Challenge 3 problem data, DeepMind OPFData, PowerWorld PWB, and the
IEEE Common Data Format are parse only, though a complete
AcScucSolutiondoes emit the official GO Challenge 3 solution file. - PowerIO does not solve anything; instances feed external solvers.
- Balanced to multiconductor construction, load linearized multiconductor admittance from an operating point, and a general multi period planning instance are waiting on a later release, and dynamic simulation data has no representation.
- The multiconductor to balanced transformation is Rust and Python only.
Multiconductor admittance assembly,
powerio_matrix::calc_multiconductor_admittance_matrix, is Rust only. - Only the MATPOWER and PSS/E readers attach source spans, marking the record
each finding is about. Findings from the other readers, from
transformations, and from writers have no byte range, and the text rendering
of a diagnostic is still
CODE: message. - There is no one call
convertin the library; you composeparsewithemit. The command line does havepowerio convert. - The MATPOWER, PSS/E, and PowerWorld AUX readers tokenize without owned strings. PyPSA CSV, PSLF, and OpenDSS tokenize through owned strings, and the Egret, GO Challenge 3, OPFData, and pandapower readers decode through a generic JSON value tree first.
- Two sets of field names coexist. Rust records and Python dict rows use the
MATPOWER derived names (
vm,pg,rate_a,tap); C ABI 7 and Julia spell out the quantity and unit (vm_pu,active_power_mw,rate_a_mva,tap_ratio). Settling on one set of names for all four languages is a 1.0 decision, and it is separate from the PowerIO IR keys, which change only with a generation. - Python tables return dict rows; typed element views like Julia’s are not offered yet.
to_balancedandto_balanced_reportlive inpowerio::transformrather than the facade root, andserialize_diagnosticsreturns aStringwhereserializereturns anEmitResult.SocwrOpfSolutioncalls its branch flowsbranch_from_active_powerwhere the other solutions saybranch_from_active_flow, and itsbus_orderandbranch_orderare iterators rather than vectors. PowerIO IR generation 2 has the same split in its keys.
Versions
One PowerIO release version covers the Rust crates, the Python distribution, and PowerIO.jl. The boundaries checked independently are:
| Boundary | Value at 0.11.0 | Checked where | Moves when |
|---|---|---|---|
| PowerIO release | 0.11.0 | the manifests, powerio::VERSION, powerio.versions(), pio_version | every release |
| C ABI | 7 | the pio_abi_version handshake at load | an existing C signature or documented behavior changes |
| PowerIO IR generation | 2, and the reader accepts 2 | the document header, powerio::IR_VERSION, powerio::IR_MIN_VERSION | the serialized representation changes |
| Rust toolchain | 1.88 | rust-version in the workspace manifest, checked by CI | a dependency in the locked graph requires a newer compiler |
| Python | 3.9 or later; the mcp extra needs 3.10, the bench extra 3.11 | pyproject.toml | a dependency drops a version |
The release version is what you install. ABI 7 is what a compiled consumer has to match. The IR generation identifies the serialized document; its rule is in PowerIO IR. The MCP server passes PowerIO IR documents through and defines no electrical data shape of its own.
The 0.11.x line is for compatible fixes, performance work, and additive
change; a break in the public Rust API that cannot be avoided goes into 0.12.
The Rust API stays pre-1.0 as long as public signatures expose types from the
pre-1.0 sprs and petgraph crates.
Developer guides
These pages are for contributors and for anyone whose code depends on PowerIO internals. They cover the change from 0.10, the PowerIO IR document and its field reference, the BMOPF mapping, the crate graph, the design decisions borrowed from LLVM and MLIR, the DC OPF bundle, the private corpus harness, benchmarks, and the checks a release runs.
Everything here describes the current implementation; earlier releases are in
the changelog. The dated design record behind the 0.11 API lives outside the
guide at docs/design/
and is not API authority.
From 0.10 to 0.11
PowerIO 0.11 breaks the 0.10 source API and the C ABI once, to remove duplicate ways of doing the same thing and to expose the same concepts in Rust, Python, Julia, and C. After that the 0.11.x line stays compatible, and any further unavoidable public break waits for 0.12.
One parse, one emit
Every grid exchange format now goes through parse, which opens the input
itself. You can hand it a file or directory name, content already in memory,
or a Source of named buffers, and all of them reach the same call.
let module = powerio::parse("case9.m")?;
let module = powerio::parse(case_directory)?;
let module = powerio::parse(bytes)?;
// Content in memory carries the name `<memory>`, which identifies no format,
// so a format read from a file extension is declared or named.
let module = powerio::parse_with_options(
bytes,
&powerio::ParseOptions::default().format("matpower")?,
)?;
let module = powerio::parse(powerio::Source::from_memory("case9.m", bytes)?)?;
| 0.10 | 0.11 |
|---|---|
parse(Source::open(path)?, None) | parse(path) |
parse(source, Some(format)) | parse_with_options(source, &ParseOptions::default().format(format)?) |
emit(&module, format, Destination::path(path)) | emit(&module, format, path) |
serialize(&module, Destination::path(path)) | serialize(&module, path) |
deserialize(Source::open(path)?) | deserialize(path) |
network.to_json() | serialize(&PioModule::new(network), destination) |
BalancedNetwork::from_json(text) | deserialize(Source::from_memory("module.pio.json", text)?)?.into_value() |
parse_display(source, from) | parse(input), which returns PioValue::GeoLayer; PwdDisplay remains for the raw display record |
DisplayData, DisplayFormat | removed from Rust; Python keeps parse_display and DisplayData for the raw PowerWorld display record |
| a layer written by hand | emit(&module, "geo-json", path) |
Python accepts a path, a file object, or a bytes-like object. A str is
taken as a path, so wrap text already in memory in io.StringIO.
module = powerio.parse("case9.m")
module = powerio.parse(io.StringIO(text), format="matpower", name="case9.m")
module = powerio.parse(binary_data, format="pwb", name="case.pwb")
Julia uses multiple dispatch on a path string, IO, or
AbstractVector{UInt8}.
module_ = parse("case9.m")
module_ = parse(IOBuffer(text); format="matpower", name="case9.m")
module_ = parse(bytes; format="pwb", name="case.pwb")
In C you construct a PioSource with pio_source_open or
pio_source_from_memory and then call pio_parse.
These 0.10 names are removed:
parse_fileparse_textparse_strparse_bytes
Read the module fields
PioModule<T> contains the typed value and its diagnostics, producer, sources,
source mappings, history, and extensions. Rust, Python, and Julia expose
these members using their usual language conventions: Rust has accessor
methods such as value() and diagnostics(), and Python and Julia have
properties such as .value and .diagnostics.
In Rust, dynamic parsing returns PioModule<PioValue>, so match on
module.value() directly. In Python, use
isinstance(module.value, BalancedNetwork); in Julia, dispatch on
PioModule{BalancedNetwork} or another concrete parameter.
PioValueKind, .kind, try_into_typed, IntoTypedModule, and the binding
wrappers that duplicated each language’s own type inspection are removed. The
C ABI uses structural names such as powerio.BalancedNetwork and exact type
predicates instead of an ordinal value kind enum.
Diagnostics belong to the module, so Python and Julia no longer have a
callable diagnostics().
Emit formats; serialize PowerIO IR
emit is now the one way to produce a grid exchange format.
powerio::emit(&module, "matpower", "copy.m")?;
memory_result = powerio.emit(module, "matpower")
file_result = powerio.emit(module, "psse", "case.raw")
memory_result = emit(module_, "matpower")
file_result = emit(module_, "psse", "case.raw")
The result lists every artifact it wrote, the output layout, the fidelity, and the emission diagnostics, and the same call handles text, binary, single file, and directory formats.
PowerIO IR goes through serialize and deserialize instead. .pio.json is
not a grid exchange format, so format discovery does not list it. Both calls
use the PowerIO IR document shape, "schema": "pio-ir" with integer
"version": 2, and the producer record names the PowerIO release separately.
Documents from earlier generations have to be regenerated from their original
power system data.
These 0.10 names are removed:
write_towrite_stringwrite_fileto_format- module JSON read and write names
BalancedNetwork::to_json,from_json, andto_json_with_diagnostics- the
model-jsonJSON classification family - the
pio-jsonformat token
to_* is still there for a genuine semantic transformation in memory.
Use ordinary collection operations
TimeSeries<T> and ScenarioSet<T> hold real typed values, so you use them
like any other collection. Rust has len, iter, and checked get; Python
has iteration and indexing; Julia has length, iteration, and 1-based
getindex; C has opaque length and element access.
Remove calls to StateInventory, StateSelector, SelectedState,
list_states, select_state, and export_state and index the collection
instead. What you get back is the contained typed value itself, or an owner
rooted typed view of it; nothing is encoded and reparsed on the way out.
Apply typed updates
PowerIO 0.11 adds OperatingPointUpdate, NetworkUpdate,
CalculationUpdate, apply_updates, and UpdateReport. An update identifies
its component by stable ComponentId and gives an absolute value with an
explicit unit; the whole batch is validated first and then applied
atomically. In Julia the mutating functions end in !.
UpdateReport lists the component IDs and fields that changed and says
whether energized connectivity changed. A bus demand update has to name a load
or an explicit allocation rule, because PowerIO will not pick an arbitrary
load to receive aggregate demand. LoadAllocation::ProportionalToCurrentActivePower
keeps the current shares and refuses an all zero basis;
LoadAllocation::Equal splits the replacement evenly, which is how you
restore demand after setting every participating load to zero.
Use calculation names that state the result
The DC data types PioDcData, DcNetworkData, and dc_data, and the phrase
“DC branch coefficients”, are gone, though the DC OPF bundle files written by
powerio dcopf and emit_dcopf_bundle remain. Use the named calculations
instead:
calc_incidence_matrix
calc_branch_susceptances
calc_bus_susceptance_matrix
calc_branch_flow_matrix
calc_branch_phase_shift_injection
calc_bus_phase_shift_injection
calc_branch_flow_dc
calc_bus_injection_dc
The public orientation and signs are:
A[e, from] = +1
A[e, to] = -1
Bf = Diagonal(b) * A
B = A' * Diagonal(b) * A
p_branch = -Bf * va + b .* shift
p_shift = A' * (b .* shift)
p_bus = -B * va + p_shift
BranchSusceptanceFormula picks between the documented series susceptance,
tap adjusted reactance, and reactance only equations.
Calculation instances and solutions
PowerIO 0.11 registers these calculation pairs:
DcPfInstance DcPfSolution
AcPfInstance AcPfSolution
DcOpfInstance DcOpfSolution
AcOpfInstance AcOpfSolution
McAcPfInstance McAcPfSolution
McAcOpfInstance McAcOpfSolution
AcScucInstance AcScucSolution
SocwrOpfSolution holds a PowerModels SOCWR relaxation, including its
W-space values and objective lower bound. PowerIO does not call it an
AcOpfSolution unless voltage recovery and AC residual checks support that
claim.
The initial assignment of an instance is its initial_point field.
C ABI 7
PowerIO 0.11 replaces ABI 6 with ABI 7, which drops the ABI 4, 5, and 6 aliases and the Arrow export. Sources, destinations, modules, typed values, collections, diagnostics, artifacts, sparse matrices, and vectors are all opaque reference counted handles, every buffer comes with an explicit length, and a borrowed typed handle keeps its module owner alive.
PowerIO.jl moves to ABI 7 in the same release. Julia packages should depend on PowerIO.jl rather than call the C ABI directly.
Inputs accepted by 0.11
PowerIO still accepts documented aliases for names that external formats
define, such as rawx for the canonical psse-rawx format token. An alias
like that names the same third party format; it does not keep a prerelease
PowerIO API or IR shape alive, and 0.11 has no beta source or document
aliases at all.
PowerIO IR
A .pio.json file serializes one PioModule<PioValue>, which is one typed
value together with its diagnostics, producer, sources, source mappings,
history, and extensions. A current document begins like this:
{
"schema": "pio-ir",
"version": 2,
"producer": { "name": "powerio", "version": "0.11.0" },
"value": {
"type": "powerio.BalancedNetwork",
"data": {}
}
}
Use serialize to produce it and deserialize to read it. PowerIO IR is not
one of the grid exchange formats. MATPOWER, PSS/E, XIIDM, CGMES, OpenDSS, PMD
JSON, BMOPF, and the other formats in the format registry exist to exchange
power system data with other tools, and they enter through parse and leave
through emit. PowerIO IR preserves PowerIO types and module records instead,
and it is deliberately left out of grid exchange format discovery. Use it when
both sides consume PowerIO values, including calculation instances, solutions,
time series, and scenario sets.
The generated JSON Schema is checked in at
docs/schema/pio-ir/2/schema.json and served from
https://powerio.dev/schema/pio-ir/2/schema.json. That schema, the
serializer, and the deserializer are all tested from the same Rust types.
docs/schema/README.md lists the earlier pio-package and
powerio.module documents as one history under pio-ir.
Module records
The document stores these common records when present:
| field | meaning |
|---|---|
producer | the software operation that created this module |
sources | source names, sizes, declared formats, and digests |
source_map | JSON Pointer paths in value.data mapped to source byte ranges |
diagnostics | structured findings with stable codes and severities |
history | ordered derivations that produced the current value |
extensions | namespaced data outside the PowerIO core schema |
Source bytes retained at runtime are not serialized. A source record names a source without exposing a local absolute path; a parser may keep the bytes in memory for byte exact same format emission while the process runs, but that buffer is separate from the stored module.
The deserializer checks the cross references before it returns a module: diagnostic source references and source mappings must name declared source IDs, byte ranges must fit the declared source length, and history references must name records in the same document. The MATPOWER and PSS/E readers attach the byte range of the record a finding is about to every diagnostic they raise at a known record, so a document serialized from one of their modules has those spans; the other readers attach none yet.
Typed values
value.type is the canonical structural type name used by Rust, C, Python,
Julia, and the document schema. Examples include:
powerio.BalancedNetwork
powerio.MulticonductorNetwork
powerio.OperatingPoint<powerio.BalancedNetwork>
powerio.TimeSeries<powerio.MulticonductorNetwork>
powerio.ScenarioSet<powerio.TimeSeries<powerio.BalancedNetwork>>
powerio.DcOpfInstance
powerio.SocwrOpfSolution
value.data has the exact shape that type demands, and the deserializer
rejects a document where the two disagree, as it rejects a wrong schema name
or version, an unknown PowerIO type, duplicate IDs, invalid references,
nonfinite values in untyped positions, or a collection whose entries disagree
with its element type. PowerIO IR reference defines every
structural type field by field: type, unit, sign convention, invariant, and
the value a reader takes when a field is absent.
Typed floating point fields spell nonfinite values as "Infinity",
"-Infinity", and "NaN". JSON null is not a floating point value.
Collections and operating points
TimeSeries<T> stores ordered time points and values of T, and
ScenarioSet<T> stores named alternatives of T with optional probabilities
and no implied time order. Nested collections keep their structural type; the
document does not invent a flattened name for each composition.
An OperatingPoint<N> stores a shared base network and typed overrides keyed
by stable component ID. The serializer preserves that relationship rather
than expanding each entry into another complete static network.
Determinism
serialize is a function of the module alone. Serializing one module twice
produces identical text, and serializing the module that text deserializes to
produces the same text again. Members are written in a fixed order, record
fields in declaration order and map keys (extras, quantities,
extensions, details) sorted. Diagnostic IDs are minted d0, d1, … in
record order for records that have none, so the minted IDs depend only on
record order. Every float is written in the shortest decimal form that reads
back to the same value, and nonfinite values use the three string spellings
above. Equal modules therefore produce equal documents, which is what lets
you use a document as a cache key, a golden file, or the input of a content
digest.
Resource limits
Before it retains large input data, deserialization applies explicit limits on source count and byte lengths, diagnostics, source map and history records, extension data, collection lengths, ID lengths, and nested value depth. Hitting a limit produces a structured PowerIO diagnostic rather than an allocation failure or a truncated result.
Generations
The integer version is the generation of the serialized representation. It
is a property of the document alone, separate from the Rust memory layout,
the PowerIO release, any grid exchange format, and the C ABI, and it changes
only when the representation changes. producer.version records the release
that wrote the document; the reader reports it and ignores it when deciding
compatibility.
When a generation bumps inside one minor release line, the release ships with
a reader for the generation it replaces, so every 0.11.x release reads every
generation any 0.11.x release wrote. powerio::IR_VERSION is the generation
a build writes and powerio::IR_MIN_VERSION the oldest it reads; in 0.11
both are 2. A refused document is reported with the schema name,
generation, and producer it claims and the remedy: a later generation needs a
newer PowerIO, and an earlier schema name or generation has to be regenerated
from its source data.
docs/schema/README.md
is the ledger of every generation and the archive of every published schema.
PowerIO IR reference
This page lists every structural value type in the PowerIO IR, field by
field: each field’s type, unit, and sign convention, the invariant the
deserializer or the constructors enforce, and what a reader uses when the
field is absent. The generated schema at docs/schema/pio-ir/2/schema.json
is the machine form of the same definitions. To keep the two from drifting
apart, powerio/tests/ir_reference.rs reads this page and checks in both
directions that each table lists the same fields the schema defines for its
definition.
Reading the tables
Each field table sits under a line that begins with the words “Schema
definition” and gives, in backticks, the $defs entries of the schema the
table documents; that line is how the test pairs a table with its
definitions. The columns are:
- field: the JSON member name in
value.data(or in the nested record). - type:
floatis a JSON number or one of the strings"Infinity","-Infinity", and"NaN";nullis refused at a float position.float or nullis a float the source may leave unstated.idis a bus identifier: a nonnegative integer, the source’s own bus number.matrixis an array of equal length float arrays.tokenis one of the listed strings. - unit: the physical unit.
p.u.is per unit on the network’sbase_mvaand the busbase_kvunless the row says otherwise. - sign: the direction a positive value means; blank when the quantity is unsigned or the sign carries no convention.
- invariant: what the deserializer refuses or the constructors hold.
- if absent:
requiredwhen the serializer always writes the field and the deserializer refuses a document without it; otherwise the value a reader takes when the member is missing.
uid on an element row is the stable component identity that operating
points, solutions, and updates refer to. When the source gives none, parse
and serialize assign {table}:{row}, so a document PowerIO writes has one
wherever a table below says “assigned at serialization”. extras on an
element is the map of source fields the typed model has no slot for, keyed
by the source’s own field names; the member is required in the document but
may be empty.
Structural type names
value.type is one of these names, and value.data has the shape of the
schema definition beside it.
| structural type | schema definition |
|---|---|
powerio.BalancedNetwork | BalancedNetwork |
powerio.MulticonductorNetwork | MulticonductorNetwork |
powerio.GeoLayer | GeoLayer |
powerio.OperatingPoint<powerio.BalancedNetwork> | StoredOperatingPoint |
powerio.OperatingPoint<powerio.MulticonductorNetwork> | StoredOperatingPoint2 |
powerio.TimeSeries<powerio.BalancedNetwork> | StoredTimeSeries |
powerio.TimeSeries<powerio.MulticonductorNetwork> | StoredTimeSeries2 |
powerio.TimeSeries<powerio.OperatingPoint<powerio.BalancedNetwork>> | StoredOperatingPointTimeSeries |
powerio.TimeSeries<powerio.OperatingPoint<powerio.MulticonductorNetwork>> | StoredOperatingPointTimeSeries2 |
powerio.ScenarioSet<powerio.BalancedNetwork> | StoredScenarioSet |
powerio.ScenarioSet<powerio.MulticonductorNetwork> | StoredScenarioSet2 |
powerio.ScenarioSet<powerio.OperatingPoint<powerio.BalancedNetwork>> | StoredOperatingPointScenarioSet |
powerio.ScenarioSet<powerio.OperatingPoint<powerio.MulticonductorNetwork>> | StoredOperatingPointScenarioSet2 |
powerio.ScenarioSet<powerio.TimeSeries<powerio.BalancedNetwork>> | StoredScenarioSet3 |
powerio.ScenarioSet<powerio.TimeSeries<powerio.MulticonductorNetwork>> | StoredScenarioSet4 |
powerio.ScenarioSet<powerio.TimeSeries<powerio.OperatingPoint<powerio.BalancedNetwork>>> | StoredScenarioSet5 |
powerio.ScenarioSet<powerio.TimeSeries<powerio.OperatingPoint<powerio.MulticonductorNetwork>>> | StoredScenarioSet6 |
powerio.DcPfInstance | DcPfInstance |
powerio.AcPfInstance | AcPfInstance |
powerio.DcOpfInstance | DcOpfInstance |
powerio.AcOpfInstance | AcOpfInstance |
powerio.McAcPfInstance | McAcPfInstance |
powerio.McAcOpfInstance | McAcOpfInstance |
powerio.AcScucInstance | AcScucInstance |
powerio.DcPfSolution | DcPfSolution |
powerio.AcPfSolution | AcPfSolution |
powerio.DcOpfSolution | DcOpfSolution |
powerio.AcOpfSolution | AcOpfSolution |
powerio.SocwrOpfSolution | SocwrOpfSolution |
powerio.McAcPfSolution | McAcPfSolution |
powerio.McAcOpfSolution | McAcOpfSolution |
powerio.AcScucSolution | AcScucSolution |
powerio.BalancedNetwork
The positive sequence transmission model, in the units MATPOWER uses: powers
in MW and MVAr, voltage magnitudes in per unit, angles in degrees, and
impedances in per unit on base_mva. Bus identifiers are the source’s own.
Schema definition: BalancedNetwork.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
name | string | required | |||
base_mva | float | MVA | finite and positive | required | |
base_frequency | float | Hz | positive | 60 | |
source_format | token | one of the format tokens (matpower, psse, psse-rawx, powermodels-json, egret-json, powerworld, powerworld-pwb, pslf, pandapower-json, pypsa-csv, gridfm, goc3-json, surge-json, opfdata-json, xiidm, cgmes, in-memory, normalized) | required | ||
buses | array of Bus | id unique | required | ||
loads | array of Load | bus names a bus | required | ||
shunts | array of Shunt | bus names a bus | required | ||
branches | array of Branch | from and to name buses | required | ||
generators | array of Generator | bus names a bus | required | ||
storage | array of Storage | bus names a bus | required | ||
hvdc | array of Hvdc | from and to name buses | required | ||
switches | array of Switch | from and to name buses | [] | ||
transformers_3w | array of Transformer3W | every winding bus names a bus | [] | ||
static_var_compensators | array of StaticVarCompensator | bus names a bus | [] | ||
areas | array of Area | number unique | [] | ||
solver | SolverParams or null | null | |||
case_metadata | CaseMetadata | every member null | |||
geo | GeoMeta or null | null | |||
detailed_connectivity | DetailedConnectivity or null | null | |||
generated_uids | array of string | subset of component uid values; identifies identities PowerIO assigned because the source stated none | [] |
Bus
Schema definition: Bus.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
id | id | unique within buses | required | ||
kind | token PQ, PV, REF, ISOLATED | MATPOWER type codes 1, 2, 3, 4 | required | ||
vm | float | p.u. | required | ||
va | float | degrees | required | ||
base_kv | float | kV | nonnegative; 0 when the source states none | required | |
vmax | float | p.u. | vmin <= vmax; Infinity for no bound | required | |
vmin | float | p.u. | required | ||
evhi | float or null | p.u. | emergency band, stated only when it differs from vmax | null (equals vmax) | |
evlo | float or null | p.u. | stated only when it differs from vmin | null (equals vmin) | |
area | integer | required | |||
zone | integer | required | |||
name | string or null | null | |||
uid | string or null | unique within buses | assigned at serialization | ||
location | Location or null | network coordinate space | null | ||
extras | object | required |
Load
Schema definition: Load.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
bus | id | names a bus | required | ||
p | float | MW | positive is consumption | required | |
q | float | MVAr | positive is inductive consumption | required | |
in_service | boolean | required | |||
voltage_model | LoadVoltageModel or null | null (constant power) | |||
uid | string or null | unique within loads | assigned at serialization | ||
extras | object | required |
LoadVoltageModel is tagged by kind. constant_power has no further
member. zip has p_constant_power, p_constant_current,
p_constant_impedance (MW, summing to p) and q_constant_power,
q_constant_current, q_constant_impedance (MVAr, summing to q), plus an
optional v_nom (kV), an optional source load_type code, and an optional
scaling factor. exponential has gamma_p, gamma_q, and v_nom, with
P = p (V / v_nom)^gamma_p and Q = q (V / v_nom)^gamma_q.
Shunt
Schema definition: Shunt.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
bus | id | names a bus | required | ||
g | float | MW at V = 1 p.u. | positive is consumption | required | |
b | float | MVAr at V = 1 p.u. | positive is capacitive injection | the initial value of a switched shunt | required |
in_service | boolean | required | |||
section_count | integer or null | null (unset) | |||
control | SwitchedShuntControl or null | null (fixed shunt) | |||
uid | string or null | unique within shunts | assigned at serialization | ||
extras | object | required |
Schema definition: SwitchedShuntControl.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
mode | token locked, continuous, discrete | PSS/E MODSW 0, 1, 2 and up | required | ||
vhigh | float | p.u. | vlow <= vhigh | required | |
vlow | float | p.u. | required | ||
control_bus | id or null | names a bus | null (the shunt’s own bus) | ||
regulating_terminal | TerminalReference or null | null | |||
rmpct | float | percent | required | ||
blocks | array of ShuntBlock | required |
Schema definition: ShuntBlock.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
steps | integer | required | |||
g | float | MW at V = 1 p.u. per step | positive is consumption | required | |
b | float | MVAr at V = 1 p.u. per step | positive is capacitive injection | required |
Branch
Schema definition: Branch.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
from | id | names a bus | required | ||
to | id | names a bus | required | ||
r | float | p.u. | required | ||
x | float | p.u. | r and x not both zero when a matrix is built | required | |
b | float | p.u. | positive is capacitive | total line charging; half at each end unless charging is present | required |
charging | BranchCharging or null | canonical per terminal admittance when present | null (derive from b) | ||
rate_a | float | MVA | 0 is unrated | required | |
rate_b | float | MVA | 0 is unrated | required | |
rate_c | float | MVA | 0 is unrated | required | |
rating_sets | array of BranchRatingSet | [] | |||
current_ratings | BranchCurrentRatings or null | null | |||
tap | float | ratio at the from side | 0 means 1 (a line); otherwise positive | required | |
shift | float | degrees | positive means the from side voltage leads the to side | required | |
in_service | boolean | required | |||
angmin | float | degrees | angmin <= angmax; -360 and 360 mean unconstrained | required | |
angmax | float | degrees | required | ||
control | TransformerControl or null | null (a line or a fixed ratio transformer) | |||
solution | BranchSolution or null | null | |||
name | string or null | null | |||
uid | string or null | unique within branches | assigned at serialization | ||
route | array of Location or null | network coordinate space | null | ||
extras | object | required |
Schema definition: BranchCharging.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
g_fr | float | p.u. | required | ||
b_fr | float | p.u. | positive is capacitive | required | |
g_to | float | p.u. | required | ||
b_to | float | p.u. | positive is capacitive | required |
Schema definition: BranchRatingSet.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
name | string | required | |||
rate_mva | float | MVA | required |
Schema definition: BranchCurrentRatings.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
c_rating_a | float | source units (amperes for PSS/E) | required | ||
c_rating_b | float | source units | required | ||
c_rating_c | float | source units | required |
Schema definition: BranchSolution.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
pf | float | MW | positive flows into the branch at the from terminal | required | |
qf | float | MVAr | positive flows into the branch at the from terminal | required | |
pt | float | MW | positive flows into the branch at the to terminal | required | |
qt | float | MVAr | positive flows into the branch at the to terminal | required |
Schema definition: TransformerControl.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
mode | token fixed, voltage, reactive_flow, active_flow, dc_line_quantity, asymmetric_active_flow | PSS/E COD magnitude 0 through 5 | required | ||
enabled | boolean | automatic adjustment on; the sign of PSS/E COD | required | ||
controlled_bus | id or null | names a bus | null | ||
controlled_bus_on_winding_side | boolean | false | |||
regulating_terminal | TerminalReference or null | null | |||
band_min | float | p.u. voltage, MVAr, or MW as mode selects | band_min <= band_max | required | |
band_max | float | as band_min | required | ||
tap_min | float | ratio, or degrees for phase control | tap_min <= tap_max | required | |
tap_max | float | as tap_min | required | ||
ntp | integer | number of tap positions | required | ||
mva_base | float | MVA | required | ||
winding_connection_angle | float or null | degrees | asymmetric active power flow control only | null |
Generator
Schema definition: Generator.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
bus | id | names a bus | required | ||
pg | float | MW | positive is generation | required | |
qg | float | MVAr | positive is generation | required | |
qmax | float | MVAr | qmin <= qmax; Infinity and -Infinity mean unbounded | required | |
qmin | float | MVAr | required | ||
vg | float | p.u. | required | ||
mbase | float | MVA | positive when stated | required | |
pmax | float | MW | pmin <= pmax | required | |
pmin | float | MW | required | ||
in_service | boolean | required | |||
cost | GenCost or null | null (no cost curve) | |||
caps | map of string to float | MW, MVAr, MW per minute, or a fraction per key | keys among pc1, pc2, qc1min, qc1max, qc2min, qc2max, ramp_agc, ramp_10, ramp_30, ramp_q, apf, the MATPOWER columns past PMIN | {} | |
energy_source | token hydro, nuclear, wind, thermal, solar, other | other | |||
voltage_regulation_on | boolean | true | |||
regulated_bus | id or null | names a bus | null (the generator’s own bus) | ||
regulating_terminal | TerminalReference or null | null | |||
active_power_control | ActivePowerControl or null | null | |||
uid | string or null | unique within generators | assigned at serialization |
Schema definition: GenCost.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
model | integer | 1 is piecewise linear, 2 is polynomial | required | ||
startup | float | currency | required | ||
shutdown | float | currency | required | ||
ncost | integer | polynomial: coeffs.len() == ncost; piecewise: coeffs.len() == 2 * ncost | required | ||
coeffs | array of float | polynomial: currency per MW^k per hour, highest order first; piecewise: alternating MW and currency per hour breakpoints | required |
Schema definition: ActivePowerControl.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
participate | boolean | required | |||
droop_percent | float or null | percent | null | ||
participation_factor | float or null | null | |||
minimum_target_active_power_mw | float or null | MW | null | ||
maximum_target_active_power_mw | float or null | MW | null |
Storage
The PowerModels storage model.
Schema definition: Storage.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
bus | id | names a bus | required | ||
ps | float | MW | positive is withdrawn from the network (charging) | required | |
qs | float | MVAr | positive is withdrawn from the network | required | |
energy | float | MWh | 0 <= energy <= energy_rating | required | |
energy_rating | float | MWh | required | ||
charge_rating | float | MW | required | ||
discharge_rating | float | MW | required | ||
charge_efficiency | float | fraction | in [0, 1] | required | |
discharge_efficiency | float | fraction | in [0, 1] | required | |
thermal_rating | float | MVA | required | ||
current_rating | float or null | amperes | null | ||
qmin | float | MVAr | qmin <= qmax | required | |
qmax | float | MVAr | required | ||
r | float | p.u. | required | ||
x | float | p.u. | required | ||
p_loss | float | MW | standby loss | required | |
q_loss | float | MVAr | standby loss | required | |
in_service | boolean | required | |||
active_power_control | ActivePowerControl or null | null | |||
uid | string or null | unique within storage | assigned at serialization | ||
extras | object | required |
Hvdc
A two terminal HVDC line in the MATPOWER dcline convention, whatever the
source format.
Schema definition: Hvdc.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
from | id | names a bus | required | ||
to | id | names a bus | required | ||
in_service | boolean | required | |||
pf | float | MW | positive flows from the from bus to the to bus, measured at the from end | required | |
pt | float | MW | positive flows from the from bus to the to bus, measured at the to end | required | |
qf | float | MVAr | positive is injected into the from bus | required | |
qt | float | MVAr | positive is injected into the to bus | required | |
vf | float | p.u. | voltage setpoint at the from bus | required | |
vt | float | p.u. | voltage setpoint at the to bus | required | |
pmin | float | MW | bounds on pf; pmin <= pmax | required | |
pmax | float | MW | required | ||
qminf | float | MVAr | bounds on qf | required | |
qmaxf | float | MVAr | required | ||
qmint | float | MVAr | bounds on qt | required | |
qmaxt | float | MVAr | required | ||
loss0 | float | MW | constant loss term | required | |
loss1 | float | MW per MW | linear loss term in pf | required | |
resistance_ohm | float or null | ohm | null | ||
nominal_voltage_kv | float or null | kV | null | ||
converters_mode | token side1_rectifier_side2_inverter, side1_inverter_side2_rectifier, or null | null | |||
converter1 | HvdcConverter or null | null | |||
converter2 | HvdcConverter or null | null | |||
cost | GenCost or null | usage cost in pf | null | ||
uid | string or null | unique within hvdc | assigned at serialization | ||
extras | object | required |
Schema definition: HvdcConverter.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
component | ComponentId | required | |||
kind | token vsc, lcc | required | |||
loss_factor_percent | float | percent of active power | required | ||
power_factor | float or null | null | |||
reactive_power_setpoint_mvar | float or null | MVAr | null | ||
regulating_terminal | TerminalReference or null | null | |||
voltage_regulator_on | boolean or null | null | |||
voltage_setpoint_kv | float or null | kV | null |
Switch
A transmission switch. A closed switch stays a switch in the data; the matrix calculations do not lower it to a zero impedance branch.
Schema definition: Switch.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
from | id | names a bus | required | ||
to | id | names a bus | required | ||
closed | boolean | required | |||
thermal_rating | float or null | MVA | null | ||
current_rating | float or null | amperes | null | ||
pf | float or null | MW | positive flows into the switch at the from terminal | null | |
qf | float or null | MVAr | positive flows into the switch at the from terminal | null | |
pt | float or null | MW | positive flows into the switch at the to terminal | null | |
qt | float or null | MVAr | positive flows into the switch at the to terminal | null | |
uid | string or null | unique within switches | assigned at serialization | ||
extras | object | required |
Transformer3W
Three windings joined at a star point; the indexed view star-lowers the record for matrix calculations.
Schema definition: Transformer3W.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
name | string or null | null | |||
windings | array of Winding | exactly three, in the order primary, secondary, tertiary | required | ||
z | array of Impedance | exactly three: z12, z23, z31 | required | ||
mag_g | float | p.u. on the system base, at the star point | required | ||
mag_b | float | p.u. on the system base, at the star point | positive is capacitive | required | |
star_vm | float | p.u. | solved star point voltage | required | |
star_va | float | degrees | required | ||
in_service | boolean | required | |||
uid | string or null | unique within transformers_3w | assigned at serialization | ||
extras | object | required |
Schema definition: Winding.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
bus | id | names a bus | required | ||
nominal_kv | float | kV | 0 defers to the terminal bus base_kv | required | |
tap | float | ratio | 1 is nominal (PSS/E WINDV with CW = 1) | required | |
shift | float | degrees | as Branch.shift | required | |
rate_a | float | MVA | 0 is unrated | required | |
rate_b | float | MVA | required | ||
rate_c | float | MVA | required | ||
control | TransformerControl or null | null |
Schema definition: Impedance.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
r | float | p.u. on the system base | required | ||
x | float | p.u. on the system base | required | ||
base_mva | float | MVA | the source’s declared base for the pair; positive | required |
Area
Schema definition: Area.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
number | integer | unique within areas; matches Bus.area | required | ||
name | string or null | null | |||
net_interchange | float | MW | positive is export out of the area | required | |
tolerance | float | MW | required | ||
slack_bus | id or null | names a bus | null | ||
area_type | string or null | null | |||
uid | string or null | null |
StaticVarCompensator
Schema definition: StaticVarCompensator.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
bus | id | names a bus | required | ||
b_min_siemens | float | siemens | b_min_siemens <= b_max_siemens | required | |
b_max_siemens | float | siemens | required | ||
voltage_setpoint_kv | float | kV | required | ||
reactive_power_setpoint_mvar | float | MVAr | positive is injection | required | |
regulation_mode | token voltage, reactive_power | required | |||
regulating | boolean | required | |||
regulating_terminal | TerminalReference or null | null | |||
p | float | MW | positive is consumption | required | |
q | float | MVAr | positive is consumption | required | |
in_service | boolean | required | |||
uid | string or null | null | |||
extras | object | required |
SolverParams
Each member is set only when the source has it.
Schema definition: SolverParams.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
newton_tolerance | float or null | MW and MVAr mismatch | null | ||
max_iterations | integer or null | null | |||
zero_impedance_threshold | float or null | p.u. reactance | null | ||
adjust_taps | boolean or null | null | |||
adjust_phase_shift | boolean or null | null | |||
adjust_dc_taps | boolean or null | null | |||
adjust_switched_shunt | boolean or null | null | |||
adjust_area_interchange | boolean or null | null |
CaseMetadata
Schema definition: CaseMetadata.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
case_date | string or null | null | |||
forecast_distance | integer or null | minutes, as the source states | null | ||
source_model_format | string or null | null | |||
minimum_validation_level | string or null | null |
Coordinates
Schema definition: GeoMeta.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
kind | token source, synthetic, manual, derived, or null | default origin of points without their own kind | null |
Schema definition: Location.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
x | float | network coordinate space; longitude in geographic space | required | ||
y | float | network coordinate space; latitude in geographic space | required | ||
kind | token as GeoMeta.kind, or null | null (the network default) |
Component references
Schema definition: ComponentId.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
component_type | string | the element table or record kind | required | ||
local_id | string | the source supplied or assigned identity | required |
Schema definition: TerminalReference.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
equipment | ComponentId | required | |||
terminal | integer | 1 for single terminal equipment, else the side number | required |
DetailedConnectivity
The hierarchy and bus breaker or node breaker connectivity a source gives
beyond the balanced calculation view (XIIDM, CGMES, PSS/E RAW 35 and RAWX).
Every collection is empty when absent. The schema defines the record types
under the names below. Their field names end in their units (_kv, _mw,
_mvar, _a, _ohm, _h, _f, _km, _degrees, _percent,
_seconds), and terminal powers use the load sign convention (positive is
consumption) where the record says so.
Schema definition: DetailedConnectivity.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
subnetworks | array of Subnetwork | [] | |||
substations | array of Substation | [] | |||
voltage_levels | array of VoltageLevel | kV | nominal_kv positive | [] | |
bus_breaker_buses | array of BusBreakerBus | [] | |||
calculated_buses | array of CalculatedBus | [] | |||
connectivity_nodes | array of ConnectivityNode | [] | |||
busbar_sections | array of BusbarSection | [] | |||
junctions | array of Junction | [] | |||
terminals | array of Terminal | [] | |||
switches | array of TopologySwitch | [] | |||
internal_connections | array of InternalConnection | [] | |||
operational_limit_groups | array of OperationalLimitGroup | amperes, MW, or MVA per limit kind | [] | ||
tap_changers | array of TapChanger | low_tap_position <= tap_position when stated | [] | ||
equipment_reactive_limits | array of EquipmentReactiveLimits | MVAr | [] | ||
boundary_lines | array of BoundaryLine | [] | |||
tie_lines | array of TieLine | [] | |||
component_metadata | array of ComponentMetadata | [] | |||
omitted_fields | array of OmittedField | a field absent from the source, distinct from a stated zero | [] | ||
dc_converter_units | array of DcConverterUnit | [] | |||
dc_topological_nodes | array of DcTopologicalNode | [] | |||
dc_nodes | array of DcNode | [] | |||
dc_grounds | array of DcGround | [] | |||
dc_busbars | array of DcBusbar | [] | |||
dc_lines | array of DcLine | [] | |||
dc_series_devices | array of DcSeriesDevice | [] | |||
dc_switches | array of DcSwitch | [] | |||
voltage_source_converters | array of VoltageSourceConverter | [] | |||
line_commutated_converters | array of LineCommutatedConverter | [] |
powerio.MulticonductorNetwork
The conductor level distribution model, in SI units (watts, vars, volts,
amperes, ohms, siemens, meters) with angles in radians. Bus identifiers and
terminal names are the source’s own strings; for OpenDSS the terminal names
are its node numbers. An element’s terminal_map lists, in conductor order,
the terminals of the named bus it connects to.
Schema definition: MulticonductorNetwork.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
name | string or null | null | |||
base_frequency | float | Hz | positive | required | |
source_format | token dss, bmopf-json, pmd-json, or null | null | |||
buses | array of DistBus | id unique | required | ||
linecodes | array of DistLineCode | name unique | required | ||
lines | array of DistLine | bus_from, bus_to name buses; linecode names a line code | required | ||
switches | array of DistSwitch | bus_from, bus_to name buses | required | ||
transformers | array of DistTransformer | every winding bus names a bus | required | ||
loads | array of DistLoad | bus names a bus | required | ||
shunts | array of DistShunt | bus names a bus | required | ||
capacitors | array of DistCapacitor | bus names a bus | [] | ||
generators | array of DistGenerator | bus names a bus | required | ||
ibrs | array of DistIbr | bus names a bus; control_profile names a profile | [] | ||
control_profiles | array of DistControlProfile | name unique | [] | ||
sources | array of VoltageSource | BMOPF carries exactly one | required | ||
untyped | array of UntypedObject | required | |||
commands | array of [verb, args] string pairs | source order | required | ||
options | array of [name, value] string pairs | source order | required | ||
extras | object | required | |||
geo | DistGeoMeta or null | null |
DistBus
Schema definition: DistBus.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
id | string | unique within buses | required | ||
terminals | array of string | ordered, unique | required | ||
grounded | array of string | each names a terminal of this bus; zero impedance to ground | required | ||
v_min | float or null | volts | v_min <= v_max | null (unbounded) | |
v_max | float or null | volts | null (unbounded) | ||
v_min_phase | array of float or null | volts | phase order excludes neutral and earth; scalar v_min takes precedence | null | |
v_max_phase | array of float or null | volts | phase order excludes neutral and earth; scalar v_max takes precedence | null | |
vpn_min | array of float or null | volts | per phase to neutral bound | null | |
vpn_max | array of float or null | volts | null | ||
vpp_min | array of float or null | volts | per phase to phase bound | null | |
vpp_max | array of float or null | volts | null | ||
vpos_min | float or null | volts | positive sequence bound | null | |
vpos_max | float or null | volts | null | ||
vneg_max | float or null | volts | negative sequence magnitude cap | null | |
vzero_max | float or null | volts | zero sequence magnitude cap | null | |
vn_max | float or null | volts | neutral to ground magnitude cap | null | |
location | DistLocation or null | network coordinate space | null | ||
extras | object | required |
DistLineCode
Schema definition: DistLineCode.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
name | string | unique within linecodes | required | ||
n_conductors | integer | positive; the order of every matrix | required | ||
r_series | matrix | ohm per meter | n_conductors square, symmetric | required | |
x_series | matrix | ohm per meter | n_conductors square, symmetric | required | |
g_from | matrix | siemens per meter | half of the shunt admittance, at the from end | required | |
b_from | matrix | siemens per meter | positive is capacitive | required | |
g_to | matrix | siemens per meter | half of the shunt admittance, at the to end | required | |
b_to | matrix | siemens per meter | positive is capacitive | required | |
i_max | array of float or null | amperes per conductor | null | ||
s_max | array of float or null | VA per conductor | null | ||
source | string or null | origin of the matrices (BMOPF source) | null | ||
extras | object | required |
DistLine
Schema definition: DistLine.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
name | string | unique within lines | required | ||
bus_from | string | names a bus | required | ||
bus_to | string | names a bus | required | ||
terminal_map_from | array of string | n_conductors terminals of bus_from | required | ||
terminal_map_to | array of string | n_conductors terminals of bus_to | required | ||
linecode | string | names a line code | required | ||
length | float | meters | nonnegative | required | |
i_max | array of float or null | amperes per conductor | null (the line code’s) | ||
s_max | array of float or null | VA per conductor | null (the line code’s) | ||
route | array of DistLocation or null | network coordinate space | null | ||
extras | object | required |
DistSwitch
Schema definition: DistSwitch.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
name | string | unique within switches | required | ||
bus_from | string | names a bus | required | ||
bus_to | string | names a bus | required | ||
terminal_map_from | array of string | terminals of bus_from, same length as terminal_map_to | required | ||
terminal_map_to | array of string | terminals of bus_to | required | ||
open | boolean | required | |||
i_max | array of float or null | amperes per conductor | null | ||
extras | object | required |
DistTransformer
Schema definition: DistTransformer.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
name | string | unique within transformers | required | ||
phases | integer | 1 through 3 | required | ||
windings | array of DistWinding | two or three | required | ||
xsc_pct | array of float | percent | [xhl] for two windings, [xhl, xht, xlt] for three | required | |
extras | object | required |
Schema definition: DistWinding.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
bus | string | names a bus | required | ||
terminal_map | array of string | terminals of bus | required | ||
conn | token wye, delta | required | |||
v_ref | float | volts, line to line for two and three phases | positive | required | |
s_rating | float | VA | positive | required | |
r_pct | float | percent of the winding base | required | ||
tap | float | ratio | 1 is nominal | required | |
r_neutral | float or null | ohm | null | ||
x_neutral | float or null | ohm | null |
DistLoad
Schema definition: DistLoad.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
name | string | unique within loads | required | ||
bus | string | names a bus | required | ||
terminal_map | array of string | terminals of bus | required | ||
configuration | token wye, delta, single_phase | required | |||
p_nom | array of float | watts per phase | positive is consumption | one entry per active phase | required |
q_nom | array of float | vars per phase | positive is inductive consumption | same length as p_nom | required |
voltage_model | DistLoadVoltageModel | required | |||
extras | object | required |
DistLoadVoltageModel is tagged by model. constant_power,
constant_current, and constant_impedance each have v_nom (volts per
active phase). zip has v_nom and the per phase coefficient arrays
alpha_z, alpha_i, alpha_p (active power) and beta_z, beta_i,
beta_p (reactive power), each triple summing to one. exponential has
v_nom, gamma_p, and gamma_q.
DistCapacitor
Schema definition: DistCapacitor.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
name | string | unique within capacitors | required | ||
bus | string | names a bus | required | ||
terminal_map | array of string | terminals of bus | required | ||
configuration | token wye, delta, single_phase | required | |||
q_rated | float | vars, whole bank at v_nom | positive is capacitive injection | required | |
v_nom | float | volts, line to line for the three phase configurations | positive | required | |
extras | object | required |
DistShunt
Schema definition: DistShunt.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
name | string | unique within shunts | required | ||
bus | string | names a bus | required | ||
terminal_map | array of string | terminals of bus, the order of the matrices | required | ||
g | matrix | siemens | square in terminal_map | required | |
b | matrix | siemens | positive is capacitive | square in terminal_map | required |
extras | object | required |
DistGenerator
Schema definition: DistGenerator.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
name | string | unique within generators | required | ||
bus | string | names a bus | required | ||
terminal_map | array of string | terminals of bus | required | ||
configuration | token wye, delta, single_phase | required | |||
p_nom | array of float | watts per phase | positive is generation | required | |
q_nom | array of float | vars per phase | positive is generation | same length as p_nom | required |
p_min | array of float or null | watts per phase | p_min <= p_max | null | |
p_max | array of float or null | watts per phase | null | ||
q_min | array of float or null | vars per phase | q_min <= q_max | null | |
q_max | array of float or null | vars per phase | null | ||
s_max | array of float or null | VA per conductor | null | ||
i_max | array of float or null | amperes per conductor | null | ||
cost | array of float or null | currency per kWh per phase, or one scalar | null | ||
extras | object | required |
DistIbr
Schema definition: DistIbr.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
name | string | unique within ibrs | required | ||
bus | string | names a bus | required | ||
terminal_map | array of string | terminals of bus | required | ||
prime_mover | token PV, BATTERY, GENERIC, STATCOM, DSTATCOM | required | |||
topology | token SINGLE_PHASE, THREE_LEG, FOUR_LEG | required | |||
s_max | array of float | VA per phase | nameplate | required | |
p_avail | float or null | watts | null | ||
p_min | array of float or null | watts per phase | null | ||
p_max | array of float or null | watts per phase | null | ||
q_min | array of float or null | vars per phase | null | ||
q_max | array of float or null | vars per phase | null | ||
i_max | array of float or null | amperes per conductor | null | ||
voltage_aggregation | token PER_PHASE, AVERAGE, or null | null | |||
control_profile | string or null | names a control profile | null | ||
extras | object | required |
DistControlProfile
Schema definition: DistControlProfile.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
name | string | unique within control_profiles | required | ||
volt_var | VoltVarControl or null | null | |||
volt_watt | VoltWattControl or null | null | |||
power_factor | PowerFactorControl or null | null | |||
extras | object | required |
Schema definition: VoltVarControl.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
breakpoints | array of float | p.u. voltage | ascending | required | |
q_limits | array of float | q_unit | positive is injection | same length as breakpoints | required |
voltage_reference | token PN_PER_PHASE, PP_PER_PHASE, PP_AVERAGED, PG_AVERAGED, PN_AVERAGED, PG_PER_PHASE, or null | null | |||
q_ref | token VAR_MAX, VAR_AVAILABLE, or null | null | |||
q_unit | token VA_FRACTION, VAR, or null | null | |||
p_min_for_q | float or null | watts | null | ||
p_min_for_q_max | float or null | watts | null |
Schema definition: VoltWattControl.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
breakpoints | array of float | p.u. voltage | ascending | required | |
p_limits | array of float | p_unit | same length as breakpoints | required | |
voltage_reference | token as VoltVarControl.voltage_reference, or null | null | |||
p_ref | token P_AVAILABLE, P_MAX, S_MAX, or null | null | |||
p_unit | token VA_FRACTION, W, or null | null |
Schema definition: PowerFactorControl.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
pf | float | in [-1, 1] | required |
VoltageSource
Schema definition: VoltageSource.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
name | string | unique within sources | required | ||
bus | string | names a bus | required | ||
terminal_map | array of string | terminals of bus | required | ||
v_magnitude | array of float | volts per terminal | 0 on a grounded terminal; same length as terminal_map | required | |
v_angle | array of float | radians per terminal | same length as terminal_map | required | |
energy_cost_rate | optional array of float | $/kWh | positive injection supplies the network | one entry per phase in terminal-map order; excludes neutral terminals | no stated source-price term |
extras | object | required |
UntypedObject
An object the reader recognized but does not type, kept so a conversion can report it precisely.
Schema definition: UntypedObject.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
class | string | required | |||
name | string | required | |||
props | array of [key, value] string pairs | source order | required |
Distribution coordinates
Schema definition: DistGeoMeta.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
kind | token source, synthetic, manual, derived, or null | null |
Schema definition: DistLocation.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
x | float | network coordinate space; longitude in geographic space | required | ||
y | float | network coordinate space; latitude in geographic space | required | ||
kind | token as DistGeoMeta.kind, or null | null (the network default) |
powerio.GeoLayer
A standalone geographic document: element points and routes in one coordinate
space, keyed by element identity rather than embedded in a case. The
canonical .geo.json, GeoJSON, aliased CSV or JSON records, headerless
buscoords CSV, and a PowerWorld .pwd display all parse to one, and
apply_geo_layer places it onto a network.
Schema definition: GeoLayer.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
space | CoordinateSpace | the space every feature’s coordinates are in | required | ||
kind | token source, synthetic, manual, derived, or null | default origin of features without their own kind | null | ||
features | array of GeoFeature | empty |
CoordinateSpace is tagged by space. geographic has an optional crs;
x is longitude and y latitude in decimal degrees, and a null crs means
EPSG:4326. projected has an optional crs for planar coordinates.
diagram has an optional canvas for drawing coordinates with no earth
referent. unknown has no further member and means the source declared no
space.
Schema definition: Canvas.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
width | float or null | canvas units | positive | the drawing width the source states | null |
height | float or null | canvas units | positive | the drawing height the source states | null |
units | string or null | the source’s own name for its canvas units | null |
GeoFeature
Schema definition: GeoFeature.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
target | token bus, branch, or substation | the element family the feature places | required | ||
key | ElementKey | at least one member names an element, unless a branch states both endpoints | required | ||
geometry | GeoGeometry | every coordinate is finite | required | ||
from | string or null | a branch’s endpoint bus, the unordered fallback identity | null | ||
to | string or null | the other endpoint bus | null | ||
kind | token as GeoLayer.kind, or null | null (the layer default) |
GeoGeometry is one tagged object: point is a single [x, y] position for
a placed element, and line_string is an array of [x, y] positions for a
route. Positions are in the layer’s coordinate space, a route has at least
one, and every coordinate is finite.
ElementKey
Matching tries uid, then id, then case insensitive name; a branch
additionally falls back to the unordered (from, to) bus pair.
Schema definition: ElementKey.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
uid | string or null | the durable identity (buses:3, branches:7) | null | ||
id | string or null | the source’s own element identifier | null | ||
name | string or null | matched case insensitively | null | ||
index | integer or null | positive | 1-based row alias, accepted on read and never written | null |
powerio.OperatingPoint<N>
An alternate electrical assignment, possibly partial, over the fixed
component identities of one base network N: demand, setpoints, dispatch,
voltages, injections, service status, switch positions, transformer taps, and
phase shifts. Any quantity the point leaves out resolves to the network’s own
value.
Schema definitions: StoredOperatingPoint, StoredOperatingPoint2.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
network | BalancedNetwork or MulticonductorNetwork, as the type name says | the complete base network | required | ||
quantities | map of quantity name to StoredQuantity | per quantity, below | keys among the quantity names of the network family | required |
Schema definition: StoredQuantity.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
identities | array of string | each names a component of the network; unique; same length as values | required | ||
values | array of float | the quantity’s unit; a flag is 0 or 1 | required |
An operating point stored inside a collection or an instance omits the network, because the enclosing record gives it once.
Schema definition: StoredOperatingPointAssignment.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
quantities | map of quantity name to StoredQuantity | as StoredOperatingPoint.quantities | required |
Balanced quantities. A bus quantity is keyed by the bus id’s decimal
spelling; an element quantity by the element’s uid.
| quantity | keyed by | unit | sign |
|---|---|---|---|
bus_voltage_magnitude | bus id | p.u. | |
bus_voltage_angle | bus id | degrees | |
bus_active_injection | bus id | MW | positive into the network |
bus_reactive_injection | bus id | MVAr | positive into the network |
generator_active_power | generator uid | MW | positive is generation |
generator_reactive_power | generator uid | MVAr | positive is generation |
generator_voltage_setpoint | generator uid | p.u. | |
generator_in_service | generator uid | flag | |
load_active_power | load uid | MW | positive is consumption |
load_reactive_power | load uid | MVAr | positive is consumption |
branch_in_service | branch uid | flag | |
branch_tap_ratio | branch uid | ratio | |
branch_phase_shift | branch uid | degrees | as Branch.shift |
switch_closed | switch uid | flag |
Multiconductor quantities. A terminal quantity is keyed bus_id/terminal, a
per phase element quantity element_name/terminal, and a whole element
quantity by the element name.
| quantity | keyed by | unit | sign |
|---|---|---|---|
terminal_voltage_magnitude | bus/terminal | volts | |
terminal_voltage_angle | bus/terminal | radians | |
load_active_power | load/terminal | watts | positive is consumption |
load_reactive_power | load/terminal | vars | positive is consumption |
generator_active_power | generator/terminal | watts | positive is generation |
generator_reactive_power | generator/terminal | vars | positive is generation |
transformer_tap | transformer name | ratio | |
capacitor_steps | capacitor name | count | |
switch_closed | switch name | flag |
powerio.TimeSeries<T>
Ordered time points and one value of T per point. Labels are the source’s
own; PowerIO imposes no calendar meaning.
Schema definitions: StoredTimeSeries, StoredTimeSeries2.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
time_points | array of TimePoint | required | |||
values | array of the element type, each a complete network | same length as time_points | required |
A series of operating points gives the shared base network once.
Schema definitions: StoredOperatingPointTimeSeries, StoredOperatingPointTimeSeries2.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
network | the base network, or null | null only for an empty series | required when values is nonempty | ||
time_points | array of TimePoint | required | |||
values | array of StoredOperatingPointAssignment | same length as time_points; identities name components of network | required |
Schema definition: TimePoint.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
label | string | nonempty, bounded | required | ||
duration | Duration or null | the interval the point covers | null |
Schema definition: Duration.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
secs | integer | seconds | required | ||
nanos | integer | nanoseconds | below one billion | required |
powerio.ScenarioSet<T>
Named alternatives of T with no implied order. Either every scenario has a
probability or none does; when present, the probabilities are nonnegative and
sum to one within SCENARIO_PROBABILITY_TOLERANCE.
Schema definitions: StoredScenarioSet, StoredScenarioSet2, StoredScenarioSet3, StoredScenarioSet4, StoredScenarioSet5, StoredScenarioSet6.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
scenarios | array of the scenario record | id unique | required |
Schema definitions: StoredScenario, StoredScenario2, StoredScenario3, StoredScenario4, StoredScenario5, StoredScenario6.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
id | string | nonempty, bounded, unique in the set | required | ||
probability | float or null | in [0, 1] | null | ||
value | the element type: a network, a network time series, or an operating point time series | required |
A scenario set of operating points gives the shared base network once.
Schema definitions: StoredOperatingPointScenarioSet, StoredOperatingPointScenarioSet2.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
network | the base network, or null | null only for an empty set | required when scenarios is nonempty | ||
scenarios | array of StoredOperatingPointScenario | id unique | required |
Schema definition: StoredOperatingPointScenario.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
id | string | nonempty, bounded, unique in the set | required | ||
probability | float or null | in [0, 1] | null | ||
quantities | map of quantity name to StoredQuantity | identities name components of network | required |
Instances
An instance is the complete input to one named calculation, including the
network it runs over. An instance may also include an initial_point, a
starting operating assignment a solver may use, whose identities refer to
components of the instance’s network.
powerio.DcPfInstance
Schema definition: DcPfInstance.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
network | BalancedNetwork | at least one REF bus | required | ||
approximation | token series_susceptance, tap_adjusted_reactance, reactance_only | the DC branch susceptance formula | required | ||
initial_point | StoredOperatingPointAssignment or null | null |
The bus specifications are derived from the network: a REF bus contributes
its stated angle, an ISOLATED bus no equation, and every other bus its net
active injection over in service generators and loads.
powerio.AcPfInstance
Schema definition: AcPfInstance.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
network | BalancedNetwork | at least one REF bus | required | ||
specifications | array of AcBusSpecification | one per bus, in bus table order | required | ||
initial_point | StoredOperatingPointAssignment or null | null |
AcBusSpecification is tagged by kind. pq gives p (MW) and q
(MVAr), the prescribed net injection; pv gives p and vm (p.u.);
reference gives vm and va (degrees); isolated gives nothing. Powers
are net injections into the network, positive for generation.
powerio.DcOpfInstance
Schema definition: DcOpfInstance.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
network | BalancedNetwork | at least one REF bus and one in service generator on a non isolated bus | required | ||
objective | Objective | required | |||
constraints | ActiveConstraints | required | |||
approximation | token as DcPfInstance.approximation | required | |||
initial_point | StoredOperatingPointAssignment or null | null |
powerio.AcOpfInstance
Schema definition: AcOpfInstance.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
network | BalancedNetwork | at least one REF bus and one in service generator on a non isolated bus | required | ||
objective | Objective | required | |||
constraints | ActiveConstraints | required | |||
initial_point | StoredOperatingPointAssignment or null | null |
Schema definition: Objective.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
terms | array of ObjectiveTerm | empty is a feasibility problem | [] |
ObjectiveTerm is tagged by term: network_generator_cost sums the
network’s generator cost curves; active_power_dispatch_cost prices active
dispatch.
Schema definition: ActiveConstraints.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
generator_capability | ConstraintSelection | active and reactive generator limits | required | ||
voltage_bounds | ConstraintSelection | bus voltage magnitude bounds | required | ||
thermal_limits | ConstraintSelection | branch thermal limits | required | ||
angle_bounds | ConstraintSelection | branch angle difference bounds | required |
ConstraintSelection is tagged by select: all (every element with a
stated limit), none (the family is relaxed), or only with identities,
the list of element identities (uid values, or bus ids) the family applies
to.
powerio.McAcPfInstance
Schema definition: McAcPfInstance.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
network | MulticonductorNetwork | at least one voltage source | required | ||
initial_point | StoredOperatingPointAssignment or null | null |
The prescribed terminal powers, source voltages, and active regulator and capacitor controls are derived from the network.
powerio.McAcOpfInstance
Schema definition: McAcOpfInstance.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
network | MulticonductorNetwork | at least one voltage source | required | ||
objective | Objective | required | |||
constraints | MulticonductorActiveConstraints | required | |||
initial_point | StoredOperatingPointAssignment or null | null |
Schema definition: MulticonductorActiveConstraints.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
terminal_voltage_bounds | ConstraintSelection | required | |||
conductor_limits | ConstraintSelection | current or apparent power limits | required | ||
generator_capability | ConstraintSelection | per phase bounds | required |
powerio.AcScucInstance
The DOE GO Challenge 3 formulation: one balanced network plus scheduling,
reserve, and contingency inputs. Powers in the inputs are per unit on the
network’s base_mva, times are hours from the start of the horizon, and
costs are dollars or dollars per per unit hour, as the data format gives
them.
Schema definition: AcScucInstance.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
network | BalancedNetwork | required | |||
inputs | ScucInputs | identities name components of network; time varying records match the horizon | required |
Schema definition: ScucInputs.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
interval_durations | array of float | hours | positive; chronological | required | |
devices | array of ScucDevice | id unique | required | ||
active_reserve_zones | array of ScucActiveReserveZone | required | |||
reactive_reserve_zones | array of ScucReactiveReserveZone | required | |||
contingencies | array of ScucContingency | required | |||
shunts | array of ScucShunt | required | |||
transformer_controls | array of ScucTransformerControl | required | |||
branch_switching_costs | array of ScucBranchSwitchingCost | required | |||
violation_costs | ScucViolationCosts | required |
Schema definition: ScucDevice.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
id | ComponentId | the generator or load in the network | required | ||
kind | token producer, consumer | required | |||
on_cost | float | dollars | required | ||
startup_cost | float | dollars | required | ||
shutdown_cost | float | dollars | required | ||
initial_on_status | boolean | required | |||
initial_commitment | ScucInitialCommitment | required | |||
minimum_up_time | float | hours | required | ||
minimum_down_time | float | hours | required | ||
ramp_limits | ScucRampLimits | required | |||
reserve_limits | ScucReserveLimits | required | |||
reactive_capability | ScucReactiveCapability | required | |||
energy_lower_bounds | array of ScucEnergyRequirement | required | |||
energy_upper_bounds | array of ScucEnergyRequirement | required | |||
startup_cost_adjustments | array of ScucStartupCostAdjustment | required | |||
startup_limits | array of ScucStartupLimit | required | |||
periods | array of ScucDevicePeriod | one per interval, chronological | required |
ScucReactiveCapability is tagged by kind: none; linear with
reactive_power_at_zero_active_power and a slope; bounded with the
_max and _min forms of both.
Schema definition: ScucDevicePeriod.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
on_status_min | boolean | required | |||
on_status_max | boolean | required | |||
active_power_min | float | p.u. | active_power_min <= active_power_max | required | |
active_power_max | float | p.u. | required | ||
reactive_power_min | float | p.u. | reactive_power_min <= reactive_power_max | required | |
reactive_power_max | float | p.u. | required | ||
energy_cost_blocks | array of ScucEnergyCostBlock | required | |||
reserve_costs | ScucReserveCosts | required |
Schema definition: ScucEnergyCostBlock.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
block_size | float | p.u. | nonnegative | required | |
marginal_cost | float | dollars per p.u. hour | required |
Schema definition: ScucReserveCosts.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
regulation_up | float | dollars per p.u. hour | required | ||
regulation_down | float | dollars per p.u. hour | required | ||
synchronized | float | dollars per p.u. hour | required | ||
nonsynchronized | float | dollars per p.u. hour | required | ||
ramping_up_online | float | dollars per p.u. hour | required | ||
ramping_up_offline | float | dollars per p.u. hour | required | ||
ramping_down_online | float | dollars per p.u. hour | required | ||
ramping_down_offline | float | dollars per p.u. hour | required | ||
reactive_up | float | dollars per p.u. hour | required | ||
reactive_down | float | dollars per p.u. hour | required |
Schema definition: ScucReserveLimits.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
regulation_up | float | p.u. | nonnegative | required | |
regulation_down | float | p.u. | nonnegative | required | |
synchronized | float | p.u. | nonnegative | required | |
nonsynchronized | float | p.u. | nonnegative | required | |
ramping_up_online | float | p.u. | nonnegative | required | |
ramping_up_offline | float | p.u. | nonnegative | required | |
ramping_down_online | float | p.u. | nonnegative | required | |
ramping_down_offline | float | p.u. | nonnegative | required |
Schema definition: ScucRampLimits.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
up | float | p.u. per hour | nonnegative | required | |
down | float | p.u. per hour | nonnegative | required | |
startup | float | p.u. per hour | nonnegative | required | |
shutdown | float | p.u. per hour | nonnegative | required |
Schema definition: ScucInitialCommitment.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
accumulated_up_time | float | hours | nonnegative | required | |
accumulated_down_time | float | hours | nonnegative | required |
Schema definition: ScucEnergyRequirement.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
start_time | float | hours | start_time <= end_time | required | |
end_time | float | hours | required | ||
energy | float | p.u. hour | required |
Schema definition: ScucStartupCostAdjustment.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
maximum_down_time | float | hours | required | ||
cost | float | dollars | required |
Schema definition: ScucStartupLimit.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
start_time | float | hours | start_time <= end_time | required | |
end_time | float | hours | required | ||
maximum_startups | integer | required |
Schema definition: ScucActiveReserveZone.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
id | ComponentId | unique | required | ||
buses | array of ComponentId | each names a bus of the network | required | ||
regulation_up_requirement_fraction | float | fraction of zone load | required | ||
regulation_down_requirement_fraction | float | fraction of zone load | required | ||
synchronized_requirement_fraction | float | fraction of zone load | required | ||
nonsynchronized_requirement_fraction | float | fraction of zone load | required | ||
ramping_up_requirement | array of float | p.u. | one per interval | required | |
ramping_down_requirement | array of float | p.u. | one per interval | required | |
regulation_up_violation_cost | float | dollars per p.u. hour | required | ||
regulation_down_violation_cost | float | dollars per p.u. hour | required | ||
synchronized_violation_cost | float | dollars per p.u. hour | required | ||
nonsynchronized_violation_cost | float | dollars per p.u. hour | required | ||
ramping_up_violation_cost | float | dollars per p.u. hour | required | ||
ramping_down_violation_cost | float | dollars per p.u. hour | required |
Schema definition: ScucReactiveReserveZone.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
id | ComponentId | unique | required | ||
buses | array of ComponentId | each names a bus of the network | required | ||
reactive_up_requirement | array of float | p.u. | one per interval | required | |
reactive_down_requirement | array of float | p.u. | one per interval | required | |
reactive_up_violation_cost | float | dollars per p.u. hour | required | ||
reactive_down_violation_cost | float | dollars per p.u. hour | required |
Schema definition: ScucContingency.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
id | ComponentId | unique | required | ||
components | array of ComponentId | Challenge 3 requires exactly one AC line, transformer, or DC line | required |
Schema definition: ScucShunt.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
id | ComponentId | the shunt in the network | required | ||
initial_step | integer | step_min <= initial_step <= step_max | required | ||
step_min | integer | required | |||
step_max | integer | required | |||
conductance_per_step | float | p.u. | required | ||
susceptance_per_step | float | p.u. | positive is capacitive | required |
Schema definition: ScucTransformerControl.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
id | ComponentId | the two winding transformer in the network | required | ||
tap_ratio_min | float | p.u. | tap_ratio_min <= tap_ratio_max | required | |
tap_ratio_max | float | p.u. | required | ||
phase_shift_min | float | radians | phase_shift_min <= phase_shift_max | required | |
phase_shift_max | float | radians | required |
Schema definition: ScucBranchSwitchingCost.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
id | ComponentId | the branch or transformer in the network | required | ||
connection_cost | float | dollars | required | ||
disconnection_cost | float | dollars | required |
Schema definition: ScucViolationCosts.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
active_power_balance | float | dollars per p.u. hour | required | ||
reactive_power_balance | float | dollars per p.u. hour | required | ||
branch_thermal_limit | float | dollars per p.u. hour | required | ||
energy_requirement | float | dollars per p.u. hour | required |
Solutions
A solution contains the instance it solves, and its arrays follow the
instance network’s table order: one entry per bus, per branch, or per
generator. Injections are net injections into the network at a bus,
positive for generation; branch flows are measured into the branch at the
named terminal. Each solution says how the calculation ended and what
residuals the producer reported. producer is the producer’s free text
solver identity, or null.
Termination is tagged by kind: converged, iteration_limit,
infeasible, unbounded, failed, or not_reported, for a source that
stores a solved calculation without termination information, as DeepMind
OPFData does.
Schema definition: Residuals.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
max_active_power_mismatch | float or null | MW | largest absolute active balance mismatch | null (not reported) | |
max_reactive_power_mismatch | float or null | MVAr | largest absolute reactive balance mismatch | null (not reported) |
Schema definition: GeneratorDispatch.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
p_mw | array of float | MW | positive is generation | one per generator | required |
q_mvar | array of float | MVAr | positive is generation | one per generator, or empty | required |
Schema definition: ThreeWindingTransformerTerminalActivePower.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
p_mw | array of float | MW | positive flows into the transformer at the winding | three entries, winding order | required |
Schema definition: ThreeWindingTransformerTerminalPower.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
p_mw | array of float | MW | positive flows into the transformer at the winding | three entries, winding order | required |
q_mvar | array of float | MVAr | positive flows into the transformer at the winding | three entries, winding order | required |
powerio.DcPfSolution
Schema definition: DcPfSolution.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
instance | DcPfInstance | required | |||
termination | Termination | required | |||
residuals | Residuals | required | |||
producer | string or null | null | |||
bus_voltage_angle | array of float | degrees | one per bus | required | |
bus_active_injection | array of float | MW | positive into the network | one per bus | required |
branch_from_active_flow | array of float | MW | positive into the branch at the from terminal | one per branch | required |
branch_to_active_flow | array of float | MW | positive into the branch at the to terminal | one per branch | required |
three_winding_transformer_terminal_active_powers | array of ThreeWindingTransformerTerminalActivePower | one per three winding transformer | required | ||
generator_dispatch | GeneratorDispatch or null | null |
powerio.AcPfSolution
Schema definition: AcPfSolution.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
instance | AcPfInstance | required | |||
termination | Termination | required | |||
residuals | Residuals | required | |||
producer | string or null | null | |||
bus_voltage_magnitude | array of float | p.u. | one per bus | required | |
bus_voltage_angle | array of float | degrees | one per bus | required | |
bus_active_injection | array of float | MW | positive into the network | one per bus | required |
bus_reactive_injection | array of float | MVAr | positive into the network | one per bus | required |
branch_from_active_flow | array of float | MW | positive into the branch at the from terminal | one per branch | required |
branch_from_reactive_flow | array of float | MVAr | positive into the branch at the from terminal | one per branch | required |
branch_to_active_flow | array of float | MW | positive into the branch at the to terminal | one per branch | required |
branch_to_reactive_flow | array of float | MVAr | positive into the branch at the to terminal | one per branch | required |
three_winding_transformer_terminal_powers | array of ThreeWindingTransformerTerminalPower | one per three winding transformer | required | ||
generator_dispatch | GeneratorDispatch or null | null |
powerio.DcOpfSolution
Multipliers and marginals are the optimizing producer’s optional economic outputs, in the objective’s units per MW; no currency is assumed.
Schema definition: DcOpfSolution.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
instance | DcOpfInstance | required | |||
termination | Termination | required | |||
residuals | Residuals | required | |||
producer | string or null | null | |||
bus_voltage_angle | array of float | degrees | one per bus | required | |
bus_active_injection | array of float | MW | positive into the network | one per bus | required |
branch_from_active_flow | array of float | MW | positive into the branch at the from terminal | one per branch | required |
branch_to_active_flow | array of float | MW | positive into the branch at the to terminal | one per branch | required |
generator_active_power | array of float | MW | positive is generation | one per generator | required |
three_winding_transformer_terminal_active_powers | array of ThreeWindingTransformerTerminalActivePower | one per three winding transformer | required | ||
objective | float | objective units | required | ||
bus_active_power_marginal | array of float or null | objective units per MW | one per bus | null | |
branch_from_limit_multiplier | array of float or null | objective units per MW | one per branch; finite and nonnegative | null | |
branch_to_limit_multiplier | array of float or null | objective units per MW | one per branch; finite and nonnegative | null |
powerio.AcOpfSolution
Schema definition: AcOpfSolution.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
instance | AcOpfInstance | required | |||
termination | Termination | required | |||
residuals | Residuals | required | |||
producer | string or null | null | |||
bus_voltage_magnitude | array of float | p.u. | one per bus | required | |
bus_voltage_angle | array of float | degrees | one per bus | required | |
bus_active_injection | array of float | MW | positive into the network | one per bus | required |
bus_reactive_injection | array of float | MVAr | positive into the network | one per bus | required |
branch_from_active_flow | array of float | MW | positive into the branch at the from terminal | one per branch | required |
branch_from_reactive_flow | array of float | MVAr | positive into the branch at the from terminal | one per branch | required |
branch_to_active_flow | array of float | MW | positive into the branch at the to terminal | one per branch | required |
branch_to_reactive_flow | array of float | MVAr | positive into the branch at the to terminal | one per branch | required |
generator_active_power | array of float | MW | positive is generation | one per generator | required |
generator_reactive_power | array of float | MVAr | positive is generation | one per generator | required |
three_winding_transformer_terminal_powers | array of ThreeWindingTransformerTerminalPower | one per three winding transformer | required | ||
objective | float | objective units | required | ||
bus_active_power_marginal | array of float or null | objective units per MW | one per bus | null | |
bus_reactive_power_marginal | array of float or null | objective units per MVAr | one per bus | null | |
branch_from_limit_multiplier | array of float or null | objective units per MVA | one per branch; finite and nonnegative | null | |
branch_to_limit_multiplier | array of float or null | objective units per MVA | one per branch; finite and nonnegative | null |
powerio.SocwrOpfSolution
The PowerModels SOCWR relaxation of an AcOpfInstance. Its objective is a
lower bound, and its voltage products make no claim of an AC feasible
phasor.
Schema definition: SocwrOpfSolution.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
instance | AcOpfInstance | required | |||
termination | Termination | required | |||
residuals | Residuals | required | |||
producer | string or null | null | |||
values | SocwrOpfValues | required | |||
duals | SocwrOpfDuals | every member null | |||
objective_lower_bound | float | objective units | required |
Schema definition: SocwrOpfValues.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
bus_voltage_magnitude_squared | array of float | p.u. squared | w[i] = |V_i|^2; one per bus | required | |
branch_voltage_product_real | array of float | p.u. squared | Re(V_from conj(V_to)); one per branch | required | |
branch_voltage_product_imaginary | array of float | p.u. squared | Im(V_from conj(V_to)); one per branch | required | |
generator_active_power | array of float | MW | positive is generation | one per generator | required |
generator_reactive_power | array of float | MVAr | positive is generation | one per generator | required |
branch_from_active_power | array of float | MW | positive into the branch at the from terminal | one per branch | required |
branch_from_reactive_power | array of float | MVAr | positive into the branch at the from terminal | one per branch | required |
branch_to_active_power | array of float | MW | positive into the branch at the to terminal | one per branch | required |
branch_to_reactive_power | array of float | MVAr | positive into the branch at the to terminal | one per branch | required |
three_winding_transformer_terminal_powers | array of ThreeWindingTransformerTerminalPower | one per three winding transformer | required |
Schema definition: SocwrOpfDuals.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
bus_active_power_marginal | array of float or null | objective units per MW | one per bus | null | |
bus_reactive_power_marginal | array of float or null | objective units per MVAr | one per bus | null | |
branch_from_thermal_limit_multiplier | array of float or null | objective units per MVA | one per branch; finite and nonnegative | null | |
branch_to_thermal_limit_multiplier | array of float or null | objective units per MVA | one per branch; finite and nonnegative | null |
powerio.McAcPfSolution
Terminal columns run in bus table order and, within a bus, in the bus’s
stated terminal order. Source columns run in source table order and, within
a source, in its terminal_map order.
Schema definition: McAcPfSolution.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
instance | McAcPfInstance | required | |||
termination | Termination | required | |||
residuals | Residuals | required | |||
producer | string or null | null | |||
terminal_voltage_magnitude | array of float | volts | one per terminal | required | |
terminal_voltage_angle | array of float | radians | one per terminal | required | |
terminal_current_magnitude | array of float or null | amperes | one per terminal | null | |
terminal_active_power | array of float or null | watts | positive into the network | one per terminal | null |
source_active_injection | array of float | watts | positive into the network | one per source terminal | required |
powerio.McAcOpfSolution
Schema definition: McAcOpfSolution.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
instance | McAcOpfInstance | required | |||
termination | Termination | required | |||
residuals | Residuals | required | |||
producer | string or null | null | |||
terminal_voltage_magnitude | array of float | volts | one per terminal | required | |
terminal_voltage_angle | array of float | radians | one per terminal | required | |
terminal_current_magnitude | array of float or null | amperes | one per terminal | null | |
terminal_active_power | array of float or null | watts | positive into the network | one per terminal | null |
source_active_injection | array of float | watts | positive into the network | one per source terminal | required |
generator_active_power | array of float | watts | positive is generation | generator table order, each generator’s terminal_map order | required |
objective | float | objective units | required |
powerio.AcScucSolution
The GO Challenge 3 output fields. Every series is values[t][row] over the
instance’s time points and the corresponding table order; a series a
producer did not supply is empty.
Schema definition: AcScucSolution.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
instance | AcScucInstance | required | |||
termination | Termination | required | |||
residuals | Residuals | required | |||
producer | string or null | null | |||
network_outputs | ScucNetworkOutputs | required | |||
device_outputs | ScucDeviceOutputs | required | |||
objective | float or null | dollars | null |
Schema definition: ScucNetworkOutputs.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
bus_vm | array of array of float | p.u. | one row per time point, one per bus | required | |
bus_va | array of array of float | radians | one per bus | required | |
shunt_step | array of array of integer | one per shunt | required | ||
ac_line_on_status | array of array of boolean | one per AC line | required | ||
transformer_tm | array of array of float | ratio | one per two winding transformer | required | |
transformer_ta | array of array of float | radians | one per two winding transformer | required | |
transformer_on_status | array of array of boolean | one per two winding transformer | required | ||
dc_line_pdc_fr | array of array of float | p.u. | positive from the from bus to the to bus | one per DC line | [] |
dc_line_qdc_fr | array of array of float | p.u. | positive is injected into the from bus | one per DC line | [] |
dc_line_qdc_to | array of array of float | p.u. | positive is injected into the to bus | one per DC line | [] |
Schema definition: ScucDeviceOutputs.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
on_status | array of array of boolean | one row per time point, one per device | required | ||
startup_status | array of array of boolean | required | |||
shutdown_status | array of array of boolean | required | |||
p_on | array of array of float | p.u. | positive is production for a producer, consumption for a consumer | required | |
q | array of array of float | p.u. | as p_on | required | |
p_reg_res_up | array of array of float | p.u. | nonnegative | required | |
p_reg_res_down | array of array of float | p.u. | nonnegative | required | |
p_syn_res | array of array of float | p.u. | nonnegative | required | |
p_nsyn_res | array of array of float | p.u. | nonnegative | required | |
p_ramp_res_up_online | array of array of float | p.u. | nonnegative | required | |
p_ramp_res_up_offline | array of array of float | p.u. | nonnegative | required | |
p_ramp_res_down_online | array of array of float | p.u. | nonnegative | required | |
p_ramp_res_down_offline | array of array of float | p.u. | nonnegative | required | |
q_res_up | array of array of float | p.u. | nonnegative | required | |
q_res_down | array of array of float | p.u. | nonnegative | required |
Module records
The records beside value in a .pio.json document.
.pio.json schema explains what each one is for; the
tables here list their fields.
Schema definition: Producer.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
name | string | nonempty, bounded | required | ||
version | string | nonempty, bounded | required |
Schema definition: SourceDescriptor.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
id | string | unique among sources; spans and source map entries name it | required | ||
name | string | a file name, never a local file path | required | ||
byte_length | integer | bytes | every span into this source ends at or before it | required | |
format | string or null | a format token | null | ||
digest | Digest or null | null |
Schema definition: Digest.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
algorithm | token sha256 | required | |||
value | string | 64 lowercase hexadecimal characters | required |
Schema definition: SourceSpan.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
source | string | names a source id | required | ||
byte_start | integer | bytes into the retained source | byte_start <= byte_end <= byte_length | required | |
byte_end | integer | bytes into the retained source | half open | required |
Schema definition: SourceMapEntry.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
target | string | an RFC 6901 pointer into value.data | required | ||
relation | token exact, defaulted, inferred, converted_units, aggregated, split, synthetic, transformed, retained_extra | required | |||
spans | array of SourceSpan | empty only for defaulted, synthetic, and transformed | [] |
Schema definition: Diagnostic.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
id | string | unique among diagnostics; assigned d0, d1, … at serialization when a record has none | required | ||
code | string | NAMESPACE.SCOPE.SPECIFIC | required | ||
severity | token error, warning, remark, note | required | |||
message | string | one line | required | ||
target | string or null | an RFC 6901 pointer into value.data | null | ||
spans | array of SourceSpan | the records the finding is about | [] | ||
related | array of string | each names a diagnostic id in the document | [] | ||
details | object | {} | |||
suggested_action | string or null | null |
Schema definition: HistoryEntry.
| field | type | unit | sign | invariant | if absent |
|---|---|---|---|---|---|
id | string | unique among history | required | ||
kind | token parse, transform, edit, repair, solve | required | |||
name | string | the operation | required | ||
input_type | string or null | a structural type name | null | ||
output_type | string or null | a structural type name | null | ||
parameters | object | {} | |||
assumptions | array of string | [] | |||
losses | array of string | [] |
BMOPF field mapping
This page says what each BMOPF field becomes in PowerIO, in which unit, and which constraint or objective term it enters. It is the authority for the BMOPF converter, so if you find a field that reads or writes differently from what is written here, one of the two has a defect.
Schema versions come from the dsopt-schema
repository. 0.1.0 is the version the IEEE PES Task Force on Benchmarking
Multiconductor OPF accepts, and 0.2.0 is the proposal that adds the element
classes 0.1.0 has no table for. PowerIO reads both and writes 0.2.0 by
default; to write 0.1.0, pass
BmopfEmitOptions::with_schema_version(BmopfSchemaVersion::Bmopf010).
Conventions
Every BMOPF quantity is SI and absolute: volts, amperes, watts, vars, volt-amperes, ohms, siemens, metres, radians, hertz, and a cost rate in currency per kilowatt-hour. PowerIO stores the same units with no scaling, so a mapping below gives a unit only where the two names differ.
A matrix element A_k_j is row k, column j, counting from one, and lands
in row k - 1, column j - 1 of the corresponding ConductorMatrix. These
matrices are symmetric, so the reader fills an unstated transpose cell from
its mirror; a stated cell wins over its mirror. A per terminal array has one
entry per name in the element’s own terminal map, in that order, and PowerIO
keeps that order.
An absent constraint field means no constraint and reads as None; an absent
parameter field is zero. A field with no typed slot lands in the element’s
extras, and the reader reports it under READ.BMOPF.RETAINED_SOURCE_ONLY.
Fields with no typed slot lists every one.
Document level
| BMOPF | PowerIO | Note |
|---|---|---|
name | PioModule value name | |
meta.$schema | resolved by BmopfSchemaVersion::from_schema_id | Absent raises READ.BMOPF.SCHEMA_ABSENT; a value naming no version raises READ.BMOPF.SCHEMA_UNKNOWN. Both parse, and both versions are accepted. |
meta.schema_version | explicit schema version | The reader checks agreement with meta.$schema. Fresh proposal output pins a retrieval URL and records proposal status and schema digest in provenance. |
meta.frequency | MulticonductorNetwork::base_frequency, Hz | Absent defaults to 60 with READ.BMOPF.VALUE_DEFAULTED. |
meta.* (the rest) | MulticonductorNetwork::extras["bmopf_meta"] | Re-emitted, except the three the writer owns. |
terminal_conventions | MulticonductorNetwork::extras["bmopf_terminal_conventions"] | Re-emitted verbatim; authored from the terminal names when the source states none. |
extras | MulticonductorNetwork::extras["bmopf_extras"] | Re-emitted verbatim, minus the tables the reader types out of it. |
bus
DistBus, in MulticonductorNetwork::buses().
| BMOPF | PowerIO | Note |
|---|---|---|
terminal_names | terminals | Ordered; fixes every per-terminal order on this bus. |
perfectly_grounded_terminals | grounded | |
v_min, v_max | v_min_phase, v_max_phase and scalar v_min, v_max | Unequal bounds remain ordered phase vectors through IR and bindings. A scalar edit explicitly overrides the vector; balanced lowering rejects unequal phase bounds. |
vpn_min, vpn_max | vpn_min, vpn_max | Per phase terminal, kept as arrays. |
vpp_min, vpp_max | vpp_min, vpp_max | Per ordered phase pair. |
vpos_min, vpos_max | vpos_min, vpos_max | Scalars. |
vneg_max, vzero_max | vneg_max, vzero_max | Magnitude caps; the lower bound is always zero. |
vn_max | vn_max | Neutral to ground cap. |
longitude, latitude | location and MulticonductorNetwork::geo | The BMOPFTools coordinate fields, outside both schema versions. Read into the coordinate space; written back only with BmopfEmitOptions::sideload_coordinates. |
line and linecode
DistLine and DistLineCode.
| BMOPF | PowerIO | Note |
|---|---|---|
line.bus_from, line.bus_to | bus_from, bus_to | |
line.terminal_map_from, line.terminal_map_to | terminal_map_from, terminal_map_to | Position i of the from map fixes matrix index i. |
line.linecode | linecode | |
line.length | length, m | |
line.R_series_i_j, line.X_series_i_j | a synthesized DistLineCode named after the line, ohm per metre | The inline branch states absolute ohms, so the reader divides by length to store per metre and keeps the line’s own length. READ.BMOPF.VALUE_INFERRED names the synthesis. |
line.G_from_i_j, line.B_from_i_j, line.G_to_i_j, line.B_to_i_j | the same synthesized line code’s g_from, b_from, g_to, b_to | |
line.i_max, line.s_max | i_max, s_max | Per conductor; override the line code’s. |
linecode.R_series_i_j, linecode.X_series_i_j | r_series, x_series, ohm per metre | |
linecode.G_from_i_j, linecode.B_from_i_j, linecode.G_to_i_j, linecode.B_to_i_j | g_from, b_from, g_to, b_to, siemens per metre | Half the total shunt at each end. |
linecode.i_max, linecode.s_max | i_max, s_max | Per conductor, applied at both ends. |
linecode.source | source | |
linecode.line_geometry, linecode.derivation | extras | 0.2.0 fields; retained, not typed. |
A line has exactly one impedance source, and the oneOf in both schema
versions enforces it: either a linecode with a length, or inline
R_series_1_1 and X_series_1_1 with no linecode.
switch
DistSwitch.
| BMOPF | PowerIO | Note |
|---|---|---|
bus_from, bus_to, terminal_map_from, terminal_map_to | the same names | |
open_switch | open | |
i_max | i_max | Per conductor. A switch has no shunt, so both ends carry the same magnitude and one array bounds both. |
load
DistLoad. Each array has one entry per branch of the load: a phase to
neutral branch for WYE, the terminal pair for SINGLE_PHASE, or a line to
line branch for DELTA.
| BMOPF | PowerIO | Note |
|---|---|---|
bus, terminal_map, configuration | the same names | configuration reads case-insensitively; an unrecognized value reads as WYE with READ.BMOPF.VALUE_UNSUPPORTED. |
p_nom, q_nom | p_nom, q_nom, W and var | |
model | the DistLoadVoltageModel variant | CONSTANT_POWER, CONSTANT_CURRENT, CONSTANT_IMPEDANCE, ZIP, EXPONENTIAL. |
v_nom | the variant’s v_nom | |
alpha_z, alpha_i, alpha_p, beta_z, beta_i, beta_p | the Zip variant’s fields | The three active fractions sum to one, and so do the three reactive. |
gamma_p, gamma_q | the Exponential variant’s fields |
generator
DistGenerator. Arrays are per phase conductor; WYE is the only
configuration the specification supports.
| BMOPF | PowerIO | Note |
|---|---|---|
bus, terminal_map, configuration | the same names | |
p_min, p_max, q_min, q_max | the same names, W and var | When a bound pair is equal the dispatch is pinned, and the reader states the same values as p_nom and q_nom so a power flow target has a setpoint. |
s_max | s_max, VA | Bounds the sum of squares of that phase’s active and reactive power. |
i_max | i_max, A | Per phase, with an optional trailing entry bounding the neutral return current. |
cost | cost, currency per kWh | Kept exactly as stated: one entry per phase. A bare scalar reads as a one-entry statement. |
voltage_source
VoltageSource. Both versions permit exactly one.
| BMOPF | PowerIO | Note |
|---|---|---|
bus, terminal_map | the same names | |
v_magnitude | v_magnitude, V | Per terminal, phase to ground; a grounded terminal states zero. |
v_angle | v_angle, rad | Per terminal. |
cost | extras["cost"] | A 0.2.0 field; retained and re-emitted, not typed. |
p_min, p_max, q_min, q_max | extras | The same standing as cost. |
shunt and capacitor
DistShunt is raw admittance, grounding impedance included; DistCapacitor
is a bank with a nameplate rating.
| BMOPF | PowerIO | Note |
|---|---|---|
shunt.bus, shunt.terminal_map | the same names | |
shunt.G_i_j, shunt.B_i_j | g, b, total siemens in conductor order | |
capacitor.bus, capacitor.terminal_map, capacitor.configuration | the same names | |
capacitor.q_rated | q_rated, var | The whole bank, not one element. |
capacitor.v_nom | v_nom, V | Line to line for the three-phase configurations; across the element terminals for SINGLE_PHASE. |
transformer
DistTransformer with windings: Vec<DistWinding>. The BMOPF subtype is
kept in DistTransformer::extras["bmopf_subtype"], because the winding list
alone does not pin down every subtype; a centre tap unit, for example, reads
as two secondary windings.
| BMOPF | PowerIO | Note |
|---|---|---|
bus_from, bus_to | windings[0].bus, windings[1].bus | |
terminal_map_from, terminal_map_to | the corresponding terminal_map | center_tap expands the three to-side terminals into two windings. |
v_nom_from, v_nom_to | windings[k].v_ref, V | For center_tap, v_nom_to is the per leg voltage. |
s_rating | windings[k].s_rating, VA | |
r_series_from, r_series_to | windings[k].r_pct, percent of that winding’s own base | The base is n_phases * v_ref^2 / s_rating. |
x_series_from, x_series_to | xsc_pct, percent | The short-circuit test measures the series sum referred to one side, which is what this field is. |
r_series, x_series (three-phase legacy) | windings[0].r_pct and xsc_pct | The lumped wye-side spelling; the delta winding is lossless in this model. |
tap_ratio (0.2.0), tap (retained under 0.1.0) | windings[0].tap / windings[1].tap | The multiplier on the nameplate turns ratio. |
tap_ratio_min, tap_ratio_max | extras | Bounds have no typed winding slot. |
r_neutral_from, x_neutral_from | windings[0].r_neutral, x_neutral, ohm | The winding’s own neutral to earth branch. |
r_neutral_to, x_neutral_to | windings[1].r_neutral, x_neutral | |
g_no_load, b_no_load | extras | The magnetising branch has no typed slot. |
i_max_from, i_max_to | extras | Per winding conductor of that side, in that side’s own amperes. |
n_winding.windings[] | one DistWinding each | bus, terminal_map, v_nom, configuration, r_winding, delta_roll, i_max. |
n_winding.x_sc | xsc_pct, ordered 12, 13, ..., 1n, 23, ..., (n-1)n | Keyed i_j with i < j in BMOPF, all referred to winding 1. |
single_phase_autotransformer, open_delta_regulator | windings plus extras["bmopf_subtype"] | The regulator ratio, its bounds, the ANSI type and the open delta connection ride in extras. |
Under schema 0.1.0, the nine fields that have no subtype slot (tap,
tap_min, tap_max, the four winding neutral fields, and the two no-load
fields) are written to extras.transformer.<subtype>.<name> and folded back
on read, with each move reported under EMIT.BMOPF.RETAINED_SOURCE_ONLY.
Under 0.2.0 the subtypes declare all nine, so nothing moves; only the three
tap names change, to tap_ratio, tap_ratio_min, and tap_ratio_max.
OpenDSS Xscarray populates every winding-pair reactance in this order.
The reader applies scalar XHL/XHT/XLT updates at edit boundaries;
regenerated four-or-more-winding records use the complete Xscarray.
ibr and control_profile
DistIbr and DistControlProfile. Both are typed under either version.
Because 0.1.0 has no top-level table for them, that version writes them
under extras; on read they come from either place, and the top-level copy
wins.
| BMOPF | PowerIO | Note |
|---|---|---|
bus, terminal_map | the same names | |
topology | topology: SINGLE_PHASE, THREE_LEG, FOUR_LEG | |
prime_mover | prime_mover: PV, BATTERY, GENERIC, STATCOM, DSTATCOM | |
s_max | s_max, VA per phase | |
i_max | i_max, A per conductor | |
p_avail | p_avail, W | |
p_min, p_max, q_min, q_max | the same names, per phase | |
control_profile | control_profile, an id | |
voltage_aggregation | voltage_aggregation: PER_PHASE, AVERAGE | |
cost | extras["cost"] | Retained and re-emitted, not typed. |
dc_bus, dc_terminal_map, dc_control, dc_v_set, dc_p_ref, dc_droop, dc_deadband, dc_link_coupled, p_dc_min, p_dc_max | extras | The DC coupling fields; retained, not typed. |
r_filter, x_filter, b_filter_shunt, grid_forming, v_ref_internal | extras | Retained, not typed. |
control_profile.power_factor.pf | PowerFactorControl | |
control_profile.volt_var.* | VoltVarControl: voltage_reference, breakpoints, q_limits, q_unit, q_ref, and the two active-power thresholds | |
control_profile.volt_watt.* | VoltWattControl: voltage_reference, breakpoints, p_limits, p_unit, p_ref |
Fields with no typed slot
The classes below read into MulticonductorNetwork::untyped() by class and
name, with their properties kept as text, and are written back to the table
the target version declares: the top level under 0.2.0, extras under
0.1.0. Reading each one reports READ.BMOPF.RETAINED_SOURCE_ONLY.
dc_bus, dc_branch, dc_grounding, dc_load, dc_source, time_series,
wire_data, line_geometry, and a per-element time_series reference map.
They pass through and re-emit unchanged. Nothing reads their values into a calculation, so if a case’s behaviour depends on them, the instance PowerIO builds from it does not represent that case. The reader says so per class rather than leaving you to discover it.
The calculation
powerio::to_mc_ac_opf_instance builds a McAcOpfInstance through
McAcOpfInstance::from_network. The instance shares the network rather than
copying it: it selects which of the network’s stated limits are active
constraints and which objective terms are summed, and reads the numbers from
the network.
Objective. There is one term, ObjectiveTerm::ActivePowerDispatchCost,
which is the specification’s default objective: the sum over every
dispatchable element and every phase of that phase’s cost rate against the
active power the element injects into the network. A positive cost minimises
the element’s injection and a negative cost maximises it, and that holds the
same way for a generator, the voltage source, and an IBR. The rate comes from
the element’s own per phase cost array: a generator reads it from
DistGenerator::cost, and a voltage source or IBR from its extras["cost"].
ObjectiveTerm::NetworkGeneratorCost is the term for a balanced network and
is not part of a BMOPF instance.
Constraints. MulticonductorActiveConstraints selects three families, each
ConstraintSelection::All by default, which matches the specification’s rule
that constraints are active for the elements present.
| Family | The BMOPF fields it activates |
|---|---|
terminal_voltage_bounds | bus.v_min, v_max, vpn_min, vpn_max, vpp_min, vpp_max, vpos_min, vpos_max, vneg_max, vzero_max, vn_max |
conductor_limits | line.i_max, line.s_max, linecode.i_max, linecode.s_max, switch.i_max, transformer.i_max_from, transformer.i_max_to |
generator_capability | generator.p_min, p_max, q_min, q_max, s_max, i_max, and the same bounds on an IBR |
A family selects by stable element identity, so a study that relaxes one
line’s thermal limit refers to that line instead of restating the bound.
ConstraintSelection::None relaxes a whole family, and Only lists the
elements whose limits stay active.
Equipment behaviour has no selection family, because there is no limit to relax: a closed switch equates its two ends conductor by conductor, an open switch carries no current, the ideal winding pair relates its two coil voltages by the turns ratio and balances ampere-turns, and the voltage source fixes its terminal voltage with a free current. Those hold in every instance built from a network that includes them.
The solution
Solved values stay in McAcOpfSolution and are not written back into the
network, so the network still describes the case and the solution describes
one result of it.
McAcOpfSolution | Unit and order |
|---|---|
terminal_voltage_magnitude | V, resolved terminal order |
terminal_voltage_angle | rad, resolved terminal order |
terminal_current_magnitude | A, resolved terminal order, when the solver reports it |
terminal_active_power | W, resolved terminal order, when the solver reports it |
source_active_injection | W, per source terminal |
generator_active_power | W, generator table order with each generator’s terminal map order |
objective | the optimised objective value |
termination, residuals, producer | how the solve ended, its residuals, and what produced it |
Checked against what
The reference data is the two published example networks, vendored at
tests/data/dist/bmopf/. powerio-dist/tests/bmopf.rs checks that each one
parses, that writing the result validates against the schema of the version
written, that a second write is identical to the first, and that parsing the
written document reproduces the model. The equations above were checked
against the specification pages of
math-and-data-model-specifications
rather than inferred from the data.
BMOPFTools.jl is publicly
available and is identified as the generator of example_ieee13.json.
The PowerIO 0.11 compatibility change
exercises the typed module API, explicit legacy schema version, retained diagnostics,
transformer core-shunt locations and nominal n-winding imports. Its numerical
suite compares power-flow voltages and transformer admittance with OpenDSS,
including independently prepared BMOPF cases and native OpenDSS conversions.
PowerIO’s structural tests additionally check triangular matrix completion, conductor order, regulator fields, proposal provenance and rejected malformed records. These checks distinguish data preservation from the equations a particular solver supports; retaining a regulator or n-winding record does not imply that PowerIO’s own matrix compiler implements it.
Explicit terminal-coil no-load admittance
transformer.<subtype>.<id>.no_load_shunt holds {winding, g, b} in transformer
extras and generation-2 IR. The one-based winding index fixes its physical
location, and g + j b is siemens per coil at the terminal voltage. It cannot
coexist with the existing from-side g_no_load and b_no_load fields.
OpenDSS and PMD exciting-branch percentages map to winding 2 with a negative
magnetizing susceptance. Conversion uses the actual tapped WYE phase-to-neutral
or DELTA phase-to-phase coil voltage and divides total transformer VA by the
phase count. A winding-2 shunt converts back to those percentages; other
locations report that this target parameterization cannot represent them.
Legacy BMOPF output preserves the object under extras.transformer and reports
the relocation. Nonzero core shunts reject the limited PowerIO passive
transformer matrix profile before execution.
The independent check evals/validation/validate_bmopf_core_shunts.py compares
six transformer topologies with OpenDSS Yprim through an intermediate PowerIO
IR document. BMOPFTools’ 0.11 adapter uses an equivalent bus-shunt matrix and
retains the original coil object in provenance. Successful parsing alone does
not establish support for a transformer calculation.
Crate graph and architecture map
The workspace is layered. Shared infrastructure sits below every parser and a thin facade sits on top; no lower crate depends on the facade, and the two network crates do not depend on each other.
powerio-core Source, FormatId, Diagnostic, Error, PioModule<T>,
TimePoint, TimeSeries<T>, ScenarioSet<T>, Destination
├── powerio-tx BalancedNetwork and the balanced readers and writers
└── powerio-dist MulticonductorNetwork and the OpenDSS, PMD, and BMOPF converters
powerio-prob operating points, updates, the seven instances, the eight
solutions, and GO Challenge 3, OPFData, and BMOPF assembly;
depends on core, tx, and dist; matrix free
powerio-matrix sparse matrices and graph data for both network families;
depends on core, tx, dist, and prob
powerio PioValue, parse, emit, serialize, deserialize, and the
re-exports; depends on core, tx, dist, and prob, and on
matrix behind the `matrix` feature
powerio-cli, powerio-capi, powerio-py over the facade
A change has to keep that shape. powerio-core owns no electrical type,
parser, matrix, instance, or IR record layout. powerio-tx and powerio-dist
stay usable on their own, so a distribution consumer pulls in neither the
balanced model nor the matrix stack. powerio-prob does not depend on
powerio-matrix. The facade re-exports the component crates, which is why
cargo add powerio gets you everything, and its matrix feature adds the
matrix crate without creating a cycle. CI checks these edges against
cargo metadata, so the map below and the manifests cannot drift apart.
Components
The facade does not force a consumer to depend on every component crate, and the two network crates are siblings with no edge between them. Solvers consume instances but sit outside PowerIO, and their workspaces and numerical caches stay outside with them.
Data flow
Every arrow out of the module is an explicit call that returns diagnostics; nothing is transformed as a side effect of something else.
The diagram sources are in docs/diagrams/*.dot.
scripts/check-architecture-map.py checks the drawn crate edges against
cargo metadata and, with --render, regenerates the images.
LLVM and MLIR lessons
PowerIO’s design borrows from LLVM and MLIR where their problems overlap with reading, transforming, and writing power system data. This page goes through each lesson taken and where the shipped design applies it, then the mechanisms left out on purpose. Primary references: the MLIR language reference, diagnostics, interfaces, pass management, and dialect definition documents.
Adopted
A small shared foundation under acyclic higher layers. LLVM’s library layering puts Support under IR under the producers. PowerIO’s powerio-core owns sources, diagnostics, errors, the module, and the generic containers; the network crates, the calculation crate, and the matrix crate stack over it in one direction, and CI asserts the edges from cargo metadata (Crate graph).
Source ownership that survives parsing. MLIR’s source manager keeps buffers alive so locations mean something after parsing. A PioModule retains its source, and same format emission returns those bytes unchanged. A diagnostic has a source identifier plus a byte range into those exact bytes, end to end, and the MATPOWER and PSS/E readers attach the range of the record a finding is about, for the failure that ends a read and for warnings alike; the other readers attach no span yet.
Deterministic serialization. serialize is a function of the module alone, so one module serializes to identical text every time, and serializing the module that text deserializes to reproduces the text. Members are written in a fixed order (record fields in declaration order, map keys sorted), diagnostic IDs are minted d0, d1, … in record order for records that have none, and every float is written in the shortest decimal form that reads back to the same value. Two equal modules therefore compare equal as documents, which is what a cache key, a golden file, or a content digest needs.
Entry points that acquire their own input, with configuration as a value. MLIR reads a module through one operation with three input kinds: parseSourceFile(filename, block, config) opens the named file itself, parseSourceFile(sourceMgr, ...) takes a source manager the caller built, and parseSourceString(text, block, config, sourceName) takes content whose name supplies diagnostic locations. LLVM’s object::createBinary has the same pair of a path and a buffer and “autodetect[s] the file type” rather than taking a format argument. PowerIO’s parse, emit, serialize, and deserialize take their input or output through IntoSource and IntoDestination, which accept a file or directory name, content already in memory, and a built Source or Destination, so the ordinary read is parse("case.raw") with one call and one failure point, and detection is the default. Optional configuration is a documented value, as in writeBytecodeToFile(op, os, config = {}) and spirv::serialize(module, binary, options = {}); Rust has no default arguments, so each operation is a pair, parse with parse_with_options.
Its own representation read separately from foreign formats. MLIR reads its own text and bytecode through parseSourceFile, which detects the encoding, and reaches foreign formats through the mlir-translate registry of named translations. PowerIO does the same. serialize and deserialize handle PowerIO IR, parse and emit translate grid exchange formats, and parse refuses a .pio.json by naming deserialize. A future binary IR encoding therefore goes behind deserialize, detected from its own leading bytes, rather than behind a new operation. The naming lines up as well. MLIR spells its own binary encoding spirv::serialize and spirv::deserialize, and LLVM reserves emit for producing a target file from IR (TargetMachine::addPassesToEmitFile).
One value set, so which value a format produces is data. MLIR has one IR and reads foreign formats through registered translations whose result is that IR, rather than a reading operation per category of input. PowerIO’s PioValue is that one set, so a geographic layer is a value in it, powerio.GeoLayer, and the canonical .geo.json, GeoJSON, aliased CSV or JSON records, headerless buscoords CSV, and a PowerWorld .pwd display all reach it through parse. There is no second reading path beside parse, so a document that is not a grid case still parses, serializes, and emits like every other value.
Structured diagnostics with stable severities and attached context. The four severities (error, warning, remark, note) are MLIR’s, with the same meanings: a remark reports on success, a note attaches context to another finding. PowerIO adds a stable dotted code, which is what you branch on, plus targets, related records, and suggested actions.
Typed representations at more than one abstraction level. The value families span reusable networks, calculation instances, and solutions the way a compiler holds IR at several levels; nothing forces the richer levels through the poorer ones.
Explicit transformations with testable boundaries. Each transformation names its input and output types, returns diagnostics, and refuses what it cannot represent; balanced lowering exposes powerio::transform::to_balanced_report so you can inspect its assumptions and refusals before you transform a value. Nothing rewrites as a side effect, and format loss diagnostics belong to the EmitResult returned by emit.
Verification at representation boundaries. Parsers and transformations verify what they produce and report findings rather than repairing silently; repairs are explicit operations that leave history records.
Shared operations instead of per format switches. The parse and emission dispatchers route once, at the facade; matrix calculations, serializers, and inspectors consume the concrete typed values, so a new format adds one parser and one serializer rather than a case in every consumer.
Analysis caches. Factorizations and prepared solver arrays are derived data behind the public results, invalidated when their inputs change, the way pass manager analyses are; IndexedNetwork, the derived index view, stays public in 0.11 because downstream consumers build matrices through it directly.
Registries checked mechanically where tables drift. Structural type names, format tokens, diagnostic codes, and drawn architecture edges are each held to one source by a CI gate, which is the maintainable slice of MLIR’s declarative dialect definitions.
Serialization specified apart from memory. Each PowerIO IR generation has an explicit schema and validation rules, and the Rust structs do not define the public document layout. As in MLIR bytecode and LLVM bitcode, the document has an integer generation and a separate producer string, and the reader decides compatibility from the generation alone. The generation window follows LLVM’s bitcode epoch rule: within one minor release line every release reads what the line wrote, and the floor moves only at a line boundary. PowerIO IR reference defines every structural type field by field, the way the MLIR language reference defines its types, and a test holds the page to the generated schema. PowerIO makes no LLVM or MLIR compatibility promise by analogy.
Scrutiny proportional to permanence. A new core concept (a value family or common module record) needs a registered structural type name, an exact IR representation, and binding coverage. A new format adapter needs none of that.
Not adopted
PowerIO 0.11 has no SSA values, no generic operation tree, no region nesting, no global context, no open runtime dialect registry, no generic pass manager, and no bytecode. The existing Rust types describe power system data more directly than an operation tree would, and none of those mechanisms has a PowerIO use with measured benefit. Public names stay power system names. A bus is a bus, a lowering is named for its concrete result, and no Rust struct is renamed to an operation so it resembles MLIR. A PioContext would be justified only by measured interning or shared allocation needs, and none has appeared.
Two MLIR bytecode mechanisms are also left out while one generation covers the whole document. Per dialect versions would let one value family evolve on its own schedule; PowerIO adopts them only when a family needs a representation change the others do not. Writer back deployment (setDesiredBytecodeVersion) would let a newer build write an older generation for a consumer pinned to it; PowerIO adopts it only when such a consumer appears.
DC OPF bundle
powerio dcopf <case>.m -o <out> assembles a DcOpfInstance and writes
<out>/<case>_dcopf/. Rust callers pass an assembled instance to
powerio_matrix::emit_dcopf_bundle. The directory contains Matrix
Market files and dcopf_meta.json.
The bundle writes the DC problem in the solver’s positive form, where b.mtx
holds the positive susceptance magnitude of each branch and A.mtx is bus
by branch. The public calculations in Matrices and graphs use
the PowerModels form instead, where calc_branch_susceptances is negative for
an inductive branch and calc_incidence_matrix is branch by bus. So the
bundle’s b is the negated public b, and A.mtx is the transposed public
A.
Definitions
- Format. Matrix Market. Matrices are
coordinate real; square symmetric ones (L,L_grounded) use thesymmetricheader and store the lower triangle only. Vectors arearray real general, one value per line. - Index base.
.mtxrow and column indices are 1-based, as Matrix Market requires.reference_busesin the manifest are 0-based dense bus indices. - Sign convention. The DC bus susceptance matrix \(L\) uses the positive M-matrix form: stored nonzero off-diagonal entries are negative, diagonals are nonnegative, and \(L_{ii} = \sum_j \lvert L_{ij} \rvert\). An off-diagonal entry is \(L_{ij} = -b_e\) for the branch between \(i\) and \(j\), so a consumer recovers the branch susceptance as \(-L_{ij} > 0\).
- Units. The default
PerUnitdivides power bybase_mvaand rescales cost so it is a function of per unit power, \(q \leftarrow 2c_2 \cdot \mathrm{base}^2\) and \(c \leftarrow c_1 \cdot \mathrm{base}\).Nativekeeps MW and native cost. The manifest records which one you chose. - Generator costs. The default export policy for costs is
require, so an in service generator without cost data is an error. Pass--missing-gen-costto fill the missing rows for a feasibility test. - Reference buses.
reference_busesin the manifest lists every grounded bus as a 0-based dense index. Each in service island needs at least one reference. If several references lie in one island, the bundle fixes all of those voltage angles to zero; it is not a participation factor slack model. - Branch susceptance formula.
b.mtxholds \(b_e\), positive for an inductive branch, the coefficient on \(\theta_f - \theta_t\). The complete flow is \(f_e = b_e(\theta_f - \theta_t) - b_e\delta_e\), where \(\delta_e\) is the phase shift inshift.mtx. The default,SeriesSusceptance, uses \(b_e = x/(r^2 + x^2)\) plus the phase shift terms, with no tap scaling.TapAdjustedReactanceuses \(b_e = 1/(x \tau)\) plusp_shift.ReactanceOnly(\(b_e = 1/x\), taps and shifts ignored) is kept because it is the textbook DC linearization, and reproducing a published result needs it exactly as written. The manifest records the formula.
Matrices
| file | shape | what |
|---|---|---|
A.mtx | \(n \times m\) | signed incidence matrix; column \(e\) has \(+1\) at from-bus, \(-1\) at to-bus |
L.mtx | \(n \times n\) | DC bus susceptance matrix \(L = A \operatorname{diag}(b) A^\mathsf{T}\); with positive branch weights, its rank is \(n-c\) for \(c\) connected components |
L_grounded.mtx | \((n-k) \times (n-k)\) | \(L\) with \(k\) reference rows and columns removed; SPD when every island is grounded |
BAt.mtx | \(m \times n\) | branch flow matrix \(B A^\mathsf{T}\) over the bundle’s positive susceptance magnitudes; complete flow adds flow_offset. In PowerModels signs the same flow is \(p_\text{branch} = -B_f,v_a + b \odot \text{shift}\) with negated susceptances |
Cg.mtx | \(n \times n_{\mathrm{gen}}\) | generator-to-bus incidence, one \(1\) per column |
Vectors
The bus indexed vectors, of length \(n\), are pd (load), gs (shunt
conductance, the constant real power a shunt draws at one per unit voltage; a
nodal balance subtracts it beside pd), q, c, and c0 (the diagonal,
linear, and constant cost terms), pmax and pmin (generation bounds),
e_r (reference indicator: \(1\) at every reference bus, else \(0\)),
p_shift (phase shift injection; zero only under ReactanceOnly, which ignores
shifts, or when the case has no phase shifter), and fixed_withdrawal, equal to
pd + gs + p_shift.
The branch indexed vectors, of length \(m\), are b (susceptances), shift
(radians), flow_offset (equal to -b * shift elementwise), fmax (thermal
limits; \(0\) means unlimited per MATPOWER), and the radian limits
angle_min and angle_max.
The generator space vectors, of length \(n_{\mathrm{gen}}\), are q_gen,
c_gen, c0_gen, pmax_gen, and pmin_gen.
The bundle schema only represents polynomial generator costs. If a generator has a piecewise linear cost, preparation returns a typed error instead of writing zero polynomial coefficients; the in memory generator space preparation keeps those breakpoints exactly.
The constant cost terms c0 and c0_gen do not move the argmin. They are
there so that a consumer reporting objective values can reconstruct the full
cost.
Generator space is canonical. The nodal q, c, c0, pmax, and pmin
files aggregate the generators at each bus: the bounds are the sums of the
generator bounds, and the cost curves combine by the parallel rule
\(q = 1 / \sum_i 1/q_i\), which is the curve of the split that costs least.
That aggregate agrees with generator space only while the cheapest split stays
inside the bound of each generator. A bus with one generator keeps that
generator’s curve.
Manifest (dcopf_meta.json)
The manifest has schema powerio.dcopf and is stamped with the writing
release in powerio_version. It describes the Matrix Market files with
structured metadata:
dimensions:n_buses,n_source_branches,n_branch_columns,n_generators,n_reference_buses, andn_grounded_buses.index_base:dense = 0for manifest bus, branch, generator, and reference indices;matrix_market = 1for.mtxcoordinates.branch_susceptance_formula,units,build_options, andzero_impedance.build_optionsrecords bothskip_zero_impedanceandsynthesize_unrated_limits. The zero impedance block records the skip flag, denominator rule, skipped count, and skipped source branch rows.grounding: reference buses, removed rows and columns, the grounded operator (L_grounded), and the reference selector (e_r).operators[]: one entry per emitted operator withname,file,kind,rows,cols,index_space, andunits.
cost_policy, synthesized_gen_costs, patched_gen_costs, files[], and
powerio_version are top level fields.
Solving with it
The complete affine equations are
\[
f = B A^\mathsf{T}\theta + \texttt{flow_offset}
\]
and
\[
L\theta = C_g p_g - \texttt{fixed_withdrawal}.
\]
Factor the grounded system, since L_grounded is SPD when every island has a
reference. Drop all reference_buses entries from the right hand side, solve
the reduced system, and set each reference angle to \(0\). e_r identifies
the grounded buses without parsing the manifest. You can use the full singular
\(L\) instead when the right hand side sums to zero within each connected
component.
An interior point DC OPF solver builds reweighted bus Laplacians at each Newton
step from the same A and b (only the edge weights change), so A is the
durable operator to hand over.
Corpus harness
powerio corpus runs the conversion matrix’s properties against a private
case corpus without letting a byte of it into the repository. The vendored
fixtures cover the readers and writers on a handful of public cases, and real
corpora hold the tail: dialect quirks, fields no fixture uses, and records at
scales no fixture reaches. Much of that data is confidential, so it cannot
become a fixture, appear in a warning quoted in a commit, or fingerprint
itself through a numeric constant in a test.
powerio corpus ingest <corpus-dir> --work <scratch-dir> [--max-bytes N]
powerio corpus compare --work <scratch-dir>
powerio corpus walk --work <scratch-dir> [--walks N] [--hops N] [--seed N] [--settle N]
powerio corpus report --work <scratch-dir> -o findings.jsonl --summary summary.md
The corpus directory is only ever read. The work directory holds raw values
and is disposable, so keep it on the machine that owns the corpus.
findings.jsonl is the boundary: before report writes a byte, it audits its
own output against every string the corpus taught it. The tool and the
conversion matrix test in powerio-cli/tests/conversion_matrix_report.rs
run the same code, the invariants module of powerio-cli, so the CI gate
and the harness cannot drift apart.
Ingest and bucketing
Every readable file is parsed into the typed model and given an electrical fingerprint: element counts, base MVA, the sorted degree sequence of the bus graph, and quantized multisets of impedances and injections. Siblings of one case in different formats share a fingerprint even when all their names differ, so they land in one bucket, and every other file gets a bucket of its own. Bucket identifiers are ordinals assigned in fingerprint order, so nothing about a bucket identifier, path, or report row comes from a source file name. An unparseable file is a finding rather than an error, and a parser panic is reported by code location plus a tool minimized token level mutation distance, not by file content.
Compare
compare runs every leg from the pristine case: the four matrix properties
in both directions for every sibling pair, and as a PowerIO round trip for
every sibling on its own. The properties are warning accounting on read and
write, core survival, Y_bus entry for entry and per bus injections, and the
canonicalized typed model diff. Each typed model diff path is paired against
the warnings that leg emitted, and a diff with no covering warning becomes an
undeclared loss finding.
Walk
walk converts each bucket’s case through random cycles of formats and
grades the chain properties: the route must not change the destination
(converting A to B to C must land where A to C does), conversion must settle
(a second pass through a format changes nothing), and an emptied table must
stay empty (rows reappearing means a writer made up data no reader gave it).
compare already grades each hop’s own loss, so walk findings carry only the
chain properties, each with the format path and the seed that replays it.
The run learns as it goes. A ledger in the work directory keeps, per directed
format pair, every distinct signature that edge has produced, and the next
path is drawn toward the pairs that have taught the least. The run ends when
--settle consecutive walks teach the ledger nothing. The ledger persists
across runs; delete it to start over.
The anonymization boundary
The tool enforces the boundary itself rather than relying on the care of whoever reads the report:
- Identifiers: every name in a case is replaced with its element class
ordinal (
bus#12,gen#3) in every emitted string, warning texts included. - Values: findings give relative deltas, ratios, and quantized magnitudes. A field’s exact value appears only when it is a format constant such as a mode flag or a column count, and not when it is grid data.
- Text: no line of a source file is echoed, and comments in decks are not echoed either.
- Findings are property shaped: format, record type, field path, expected behavior, observed behavior, and structural preconditions. That is enough to reproduce the problem without any of the case.
From a finding to a fix
- Triage by severity: crash, silent value change, silent drop, undeclared loss, miscounted warning, declared loss confirmed.
- Restate the finding as a falsifiable sentence about the format, using the findings file alone.
- Build a minimal synthetic reproducer from an existing vendored fixture,
from
powerio gen, or by hand as a case of two to four buses with canonical values. The test must fail before the fix and pass after. A finding you cannot reproduce synthetically is not actionable; it goes into the report as open and does not go into a commit. - Fix under the existing decision order: carry the data, stop retaining restatements, warn only on losses. Then run the full gate, including the conversion matrix with rederived baselines.
- Commit one property per commit, and describe the property rather than the run that found it. Rerun the tool to confirm the finding class closed and no other bucket regressed.
A fixture added this way comes with its generation script or handwritten provenance, recorded in the fixture README. Corpus paths do not belong in a diff.
Performance
PowerIO has five benchmark tiers, listed below. They answer different questions, so keep their numbers separate when you publish them.
| tier | command | what it answers |
|---|---|---|
| Rust microbenchmarks | cargo bench -p powerio-tx --bench parse | parser, writer, and PowerWorld reader timing inside one process |
| Matrix microbenchmarks | cargo bench -p powerio-matrix --bench matrix | sparse matrix, DC OPF component, and dense sensitivity builder timing after parse/indexing |
| Cross tool parser and matrix comparison | julia --project=evals/validation evals/performance/bench_julia.jl --json | powerio through the C ABI against ExaPowerIO.jl and PowerModels.jl, including parse plus Y bus construction |
| Python parser comparison | .venv/bin/python evals/performance/bench_parse.py --json <cases> | Python package parse and matrix path against pandapower reader paths |
| C ABI release size | three cargo build -p powerio-capi --release feature sets plus stat | binary size for core, arrow,matrix, and all release features |
The published tables come from evals/performance/render_tables.py, which
renders the JSON the harnesses write, and this page is the reference for how
those numbers are made. Each refresh also records the snapshot environment:
machine model, chip, core count, memory, OS, Rust, C compiler, Julia, Python,
and the package versions of the comparison harnesses. Regenerate the JSON
inputs first, then render:
bash evals/validation/fetch_cases.sh
cargo build --release -p powerio-capi --features arrow,matrix
python3.11 -m venv .venv
.venv/bin/python -m pip install --upgrade pip maturin -r evals/validation/requirements.txt
env VIRTUAL_ENV=$PWD/.venv .venv/bin/maturin develop --release
julia --project=evals/validation evals/performance/bench_julia.jl --json
.venv/bin/python evals/performance/bench_parse.py --json \
tests/data/case2869pegase.m \
tests/data/large/case9241pegase.m \
tests/data/large/case13659pegase.m \
tests/data/large/case193k.m
# tests/data/large/ is not in the repository; evals/validation/fetch_cases.sh fills it.
python3 evals/performance/render_tables.py
python3 evals/performance/render_tables.py --check
The Julia benchmark writes rows for parse only and matrix_rows for parse
plus Y bus construction. Each tool is timed on its own path: PowerIO on ABI 7
parsing and typed network access, PowerModels on parse_file,
make_per_unit!, and calc_admittance_matrix, and ExaPowerIO on
parse_matpower plus a sparse Y bus assembled from its parsed branch
admittance rows.
The Rust Criterion benchmarks measure PowerWorld .pwb and .aux parse
timings. Fetch the public fixtures, run
cargo bench -p powerio-tx --bench parse -- "parse_aux_|parse_pwb_", then run
python3 evals/performance/extract_powerworld_bench.py before you render the
tables. If you are publishing the Texas7k local row, pass its aux and pwb
paths through POWERIO_BENCH_AUX and POWERIO_BENCH_PWB during the Criterion
run.
Matrix builder timings do not include parsing. The matrix benchmark parses
each fixture once, builds IndexedNetwork once, and times only the derived
matrix construction. Its pipeline row measures Pipeline::run for the paired
\(Y_{\mathrm{bus}}\) export, including MTX, shunt, and metadata writes:
cargo bench -p powerio-matrix --bench matrix
python3 evals/performance/extract_matrix_bench.py
python3 evals/performance/render_tables.py
While you work on one builder, filter the run down to the benchmarks you care about, for example:
cargo bench -p powerio-matrix --bench matrix -- 'matrix_bprime|matrix_ybus|dcopf_'
Criterion compares each run against whatever baseline is in your local
target/criterion, so treat a Performance has regressed line as a reason
to investigate rather than as a publishable claim by itself. A number that
goes into a release note or benchmark page needs the commit, tree
cleanliness, machine, toolchain, command, fixtures, and whether the optional
large cases were present.
Before you publish a C ABI change, measure the release binary size. ABI 7
exports one symbol set and only the gridfm feature changes the binary, so
two builds cover the range (the library suffix is .so on Linux, .dylib on
macOS, and .dll on Windows):
cargo build -p powerio-capi --release --no-default-features
cp target/release/libpowerio_capi.so /tmp/libpowerio_capi-core.so
cargo build -p powerio-capi --release --no-default-features --features gridfm
cp target/release/libpowerio_capi.so /tmp/libpowerio_capi-gridfm.so
stat -c '%s %n' /tmp/libpowerio_capi-core.so /tmp/libpowerio_capi-gridfm.so
Testing and release checks
If your change alters numerical semantics, it needs tests and a short note in code or docs saying why. If it is a performance change, it needs before and after measurements.
Baseline checks
scripts/ci-mirror.sh runs everything rust.yml runs, in the same feature combinations: format, clippy, the terminology and symbol gates, header parity, every crate’s tests in each gated feature set, the C smoke and header programs, schema generation, packaging checks, and the docs build. Run it before pushing rather than assembling your own subset, because a hand assembled subset misses the feature gated suites. cargo test --workspace, for example, builds powerio-capi with default features only and skips every test behind arrow, gridfm, matrix, or prob.
Point POWERIO_JL at a PowerIO.jl checkout to include the Julia binding suite against the freshly built library, or set POWERIO_JL_OPTIONAL=1 to run without one.
The Python binding tests need a wheel built into a virtual environment. Build
it from the repository root, where pyproject.toml lives:
python3 -m venv .venv && source .venv/bin/activate
pip install maturin pytest
maturin build --release -o dist
pip install dist/*.whl
python -m pytest python/tests
Install the built wheel rather than an editable one. When pytest runs from the
repository root, the powerio/ crate directory there shadows a
maturin develop install.
Route changes
Pick the smallest set of gates that covers what you changed, then run the release gates before you claim a release.
| changed surface | extra gates |
|---|---|
| parser or writer semantics | bash evals/validation/run_validation.sh; format round trip tests; affected cargo +nightly fuzz run <target> -- -runs=1 harnesses |
| rich model fields | bash evals/validation/run_rich_validation.sh |
| matrix calculations | cargo test -p powerio-matrix; cargo bench -p powerio-matrix --bench matrix |
| problem instances or DC OPF bundles | cargo test -p powerio-prob --no-default-features; cargo test -p powerio --features matrix |
| PowerWorld binary reader | PowerWorld parser tests plus `cargo bench -p powerio-tx –bench parse – “parse_aux_ |
| C ABI | scripts/capi-header-parity.sh; scripts/capi-smoke.sh; cargo test -p powerio-capi --no-default-features; cargo test -p powerio-capi --features arrow,matrix,gridfm,dist,prob; bash scripts/ci-clippy.sh capi-no-default; bash scripts/ci-clippy.sh capi-release |
| Python package metadata or extras | maturin build --release --out /tmp/powerio-wheel-check; inspect wheel METADATA |
| Julia binding compatibility | build powerio-capi --features arrow,matrix,gridfm,dist,prob, then run PowerIO.jl tests with POWERIO_CAPI |
| shared surface with PowerIO.jl | push a same-named PowerIO.jl companion branch; the tandem CI job tests against it |
| CLI behavior | cargo test -p powerio-cli --test cli |
| documentation or website | mdbook build docs; mdbook test docs; RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps; regenerate schemas and the C header when their source rustdoc changes; run scripts/capi-header-parity.sh; check links to retired guide outputs |
evals/validation/run_validation.sh needs the Python oracle stack in the same
Python 3.11+ venv as the local wheel, and treats a missing PyPSA, pandapower,
or egret as a setup failure. evals/validation/run_rich_validation.sh treats
the committed PowerModels rich oracle as strict, so missing Julia is a setup
failure there too.
Release gates
Before you publish a release claim, run the full set below on top of the baseline checks:
cargo test -p powerio-capi --no-default-features
cargo test -p powerio-capi --features arrow,matrix,gridfm,dist,prob
bash scripts/ci-clippy.sh capi-no-default
bash scripts/ci-clippy.sh capi-release
cargo build -p powerio-capi --release --features arrow,matrix,gridfm,dist,prob
scripts/capi-header-parity.sh
scripts/capi-smoke.sh
POWERIO_CAPI=$PWD/target/release/libpowerio_capi.so \
julia --project=../PowerIO.jl -e 'using Pkg; Pkg.test()' # .dylib on macOS
cargo bench -p powerio-matrix --bench matrix -- 'matrix_bprime|matrix_ybus|dcopf_'
(cd evals/performance/asv && ../../../.venv/bin/asv check -E existing:../../../.venv/bin/python)
(cd evals/performance/asv && ../../../.venv/bin/asv run --quick --show-stderr -E existing:../../../.venv/bin/python --dry-run)
for target in $(cd fuzz && ls fuzz_targets | sed 's/\.rs$//'); do
cargo +nightly fuzz run "$target" -- -runs=1
done
bash evals/validation/run_validation.sh
bash evals/validation/run_rich_validation.sh
run_validation.sh checks the classic transmission paths against
PowerModels.jl, ExaPowerIO.jl, egret, and pandapower to
writer matrix; run_rich_validation.sh covers fields outside the MATPOWER row
shape (branch terminal admittance, switches, current ratings, solution values,
HVDC costs, load voltage models). GOC3 has its own goc3-reference CI job,
which pins the GO-3 data model, C3DataUtilities, and the D1, D2, and D3 files
from GOC3Benchmark.jl. That job validates PowerIO’s problem and solution
documents with the GO-3 data model, parses all three benchmark problems as
AcScucInstance, runs the Challenge 3 data checks on them, and runs the same
checks on a PowerIO AcScucSolution output document. Surge has no external
validator in this harness, so its current evidence is its Rust parser, writer,
routing, stored module, and round trip tests. The
format chapter says what the independent checks prove
for each format.
The gates do not prove that every field of every source format survives. Known losses are part of the public behavior and show up as warnings.
Benchmark updates
Regenerate the benchmark JSON before you change a published table:
julia --project=evals/validation evals/performance/bench_julia.jl --json
.venv/bin/python evals/performance/bench_parse.py --json <cases>
cargo bench -p powerio-tx --bench parse -- "parse_aux_|parse_pwb_"
python3 evals/performance/extract_powerworld_bench.py
cargo bench -p powerio-matrix --bench matrix
python3 evals/performance/extract_matrix_bench.py
python3 evals/performance/render_tables.py
python3 evals/performance/render_tables.py --check
The ASV suite tracks Python wheel parse and matrix timing across git history. To smoke test it on an uncommitted worktree, point it at the local venv:
cd evals/performance/asv
../../../.venv/bin/asv check -E existing:../../../.venv/bin/python
../../../.venv/bin/asv run --quick --show-stderr -E existing:../../../.venv/bin/python --dry-run
Do not edit generated benchmark tables by hand. When you publish new numbers, update the snapshot environment described on the performance page as well: commit, tree cleanliness, machine, OS, toolchain, Python stack, Julia stack, commands, fixtures, and optional local data.
Broad local corpora stay local. Pass them in through the documented
environment variables or --root flags, review the reports the run writes
under evals/validation/results/, and do not commit corpus paths or generated
outputs.