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

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

FunctionResult
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.