Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

QuantityConventionReferencepowerio
Bus type codes\(1 = \mathrm{PQ}\), \(2 = \mathrm{PV}\), \(3 = \mathrm{ref}\), \(4 = \mathrm{isolated}\)MATPOWER idx_busnetwork::BusType
Impedance, susceptanceper unit on baseMVA, never rescaledMATPOWER idx_brch (BR_B already per unit)matpower
Branch terminal admittanceMATPOWER 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 projectionPowerModels matpower.jl; MATPOWER idx_brchnetwork::BranchCharging, Branch::calc_terminal_charging
Tap ratio0 means a line (treated as 1); nonzero is a transformerMATPOWER idx_brch TAPBranch::calc_effective_tap
Phase shift, angledegrees in the model; PowerModels JSON carries radiansPowerModels make_per_unit!powermodels-json
Angle limitsangmin/angmax default ±360 (unconstrained)MATPOWER idx_brch ANGMIN/ANGMAXBranch::has_angle_limits
pandapower/PyPSA impedanceline 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 siemenspandapower PPC conversion, PyPSA static componentspandapower-json, pypsa-csv
dcline Pt/Qf/Qtsign flips vs MATPOWERPowerModels matpower.jlpowermodels-json
Generator cost\(c_2 p^2 + c_1 p\) maps to \(q = 2c_2\), \(c = c_1\); coefficients high order firstMATPOWER idx_cost, egret matpower_parserGenCost::calc_quadratic
source_id["bus", id] for bus-tied elementsPowerModels matpower.jlpowermodels-json
PSLF shuntsEPC pu_mw/pu_mvar are per unit on sbase; Shunt stores MW/MVAr at \(V = 1\)paired EPC/RAW case checkspslf
DOE GO Challenge 3an 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 BalancedNetworkpinned GO-3 data model, C3DataUtilities, and GOC3Benchmark.jl D1/D2/D3 filespowerio::parse, powerio::emit
Surge anglesSurge JSON carries voltage angles, phase shifts, and angle limits in radians; BalancedNetwork stores degreesRust Surge round trip testssurge-json
DeepMind OPFData JSONDeepMind 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 IDsPaper Appendix A, the PyG loader, the smallest complete official fixture, and size independent FullTop and N-1 property testsopfdata-json
UCTE-DEF units and signsohm, 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 HzPowSybl Core UcteImporter and UcteNode.fixucte
IEEE CDF shunts and tapsbus 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 signMATPOWER cdf2mpc, PowSybl IeeeCdfBusReader and IeeeCdfBranchReader, the vendored 14 and 30 bus cases against case14.m and case30.mieee-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 with egret.data.model_data.ModelData and 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 transformer s_nom base. 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.

VersionReadWhat the version states differently
1.0XIIDMiTesla 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.1XIIDMPowSybl namespace. Calculated buses under node breaker topology. Three winding transformer ratedU0, leg 1 tap changers, and phase tap changers.
1.2XIIDMfictitious on every identifiable, targetDeadband on tap changers, ratedS, and shunt voltage regulation.
1.3XIIDMShunt linear and nonlinear models with sectionCount, aliases, and boundary line generation.
1.4XIIDMAlias types.
1.5XIIDMActive and apparent power limits.
1.6XIIDMVoltage levels outside substations and VSC regulating terminals.
1.7XIIDMminimumValidationLevel and the equipment validation namespace.
1.8XIIDMBatteries spell targetP and targetQ. Fictitious bus injections (reported, not retained). Self connected switches are refused.
1.9XIIDMShunt p.
1.10XIIDMTie lines reference two dangling lines. Load models.
1.11XIIDM, JIIDMpairingKey replaces ucteXnodeCode. Subnetworks. Voltage angle limits (reported, not retained).
1.12XIIDM, JIIDMOperational limits groups and ratio tap changer regulationMode/regulationValue.
1.13XIIDM, JIIDMAreas, isCondenser, and active power control 1.2.
1.14XIIDM, JIIDMSolved tap positions and section counts, regulating on static VAR compensators and phase tap changers.
1.15XIIDM, JIIDMDC nodes, grounds, lines, switches, and AC/DC converters.
1.16XIIDM, JIIDMshuntCompensator and boundaryLine element names and multiple selected limit groups.
1.17XIIDM, JIIDMOptional 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.