Skip to content
 
 

Repository files navigation

PowerIO.jl

PowerIO logo

Julia bindings for PowerIO, which reads power system case files into a typed BalancedNetwork, writes them back, and converts between formats. The Rust core does the parsing and the byte-exact write, so a case reads identically in Julia, Python, C/C++, and Rust.

Supported formats (each reads and writes, so any pair converts):

A same-format round trip is byte exact; cross-format conversion reports fields the target cannot represent as warnings.

PowerIO format and matrix flow

Install

pkg> add PowerIO

Working on the binding itself needs a local C ABI build; see Develop below.

Use

using PowerIO

net = parse_file("case14.m")
net isa BalancedNetwork
PowerIO.n_buses(net), PowerIO.n_gens(net), PowerIO.base_mva(net)
PowerIO.source_format(net)        # "Matpower"
PowerIO.reference_bus_id(net)     # the slack bus id (or nothing)

text, warnings = convert_file("case14.m", "psse")

# egret and PowerModels both use .json; pass `from` to disambiguate:
egret = parse_file("grid.json"; from="egret")

parse_file also reads from an IO; a String argument is always a path, so in-memory text goes through an IO or parse_str with an explicit format:

net = parse_file(IOBuffer(read("case14.m", String)), "matpower")
net = parse_str(read("case14.m", String), "matpower")

Serialization and the structured transport:

to_matpower(net)                   # ::String, byte exact when the input was MATPOWER
to_json(net)                       # the JSON transport
to_format(net, "powermodels-json") # (text, warnings)
from_json(to_json(net))            # BalancedNetwork with a live handle

Network is kept as a deprecated compatibility alias for BalancedNetwork.

to_normalized derives a computation-ready copy: powers per unit, angles in radians, tap 0 → 1, out-of-service and isolated elements dropped, source bus ids preserved, bus types inferred:

norm = to_normalized(net)
PowerIO.source_format(norm)       # "Normalized"

to_dense returns the numeric tables as dense typed arrays straight from the C ABI extractors (no JSON parse) for matrix assembly:

d = to_dense(net)                 # or to_dense("case14.m") for parse + extract
d.n, d.m, d.ng                    # bus / branch / generator counts
d.bus_ids                         # 1-based ids; row k of every per-bus table is bus_ids[k]
d.branch.from, d.branch.x         # branch endpoints and reactances
d.reference_bus, d.n_components, d.is_radial

to_arrow brings one table across the Arrow C Data Interface (needs the library built with --features arrow; arrow_available() reports it). Raw selectors are :bus, :branch, :gen, :load, :shunt, and :switch; normalized solver selectors are :solver_bus, :solver_load, :solver_shunt, :solver_branch, :solver_switch, :solver_arc, :solver_gen, :solver_storage, and :solver_hvdc. By default it returns a NamedTuple of owned Julia Vectors (Tables.jl-shaped, flows into Arrow.write, DataFrame, etc.), so there is no lifetime caveat. copy=false returns a zero-copy ArrowTable whose columns view the producer's memory; keep it alive while reading them. For the numeric tables alone, to_dense is a copy-free, unsafe_wrap-free fast path (the C ABI fills Julia-owned buffers). If a selector reports an unknown table id, rebuild powerio-capi from a matching commit or repin the artifact.

t = to_arrow(net, :branch)                  # raw table, owned columns
t.from, t.x, t.tap
sb = to_arrow(net, :solver_bus)             # normalized solver table
sb.index, sb.bus_id, sb.pd                  # dense 0-based ids, per unit values
z = to_arrow(net, :branch; copy=false)      # zero-copy views; keep `z` alive while reading

.pio.json compiler packages are readable and writable through the native pio_package_* C ABI surface:

pkg = to_package(net)                         # ::CompilerPackage, model_kind = :balanced
json = to_json(pkg)                           # .pio.json envelope
from_package(json)                            # BalancedNetwork with a live handle
pkg2 = to_package(net; include_solver_metadata=true)

include_solver_metadata=true records the compact normalized solver table identity block used by powerio-pkg. Multiconductor packages can be preflighted and explicitly lowered:

mpkg = to_package(parse_file(MulticonductorNetwork, "feeder.dss"))
report = multiconductor_to_balanced_preflight(mpkg)
bpkg = lower_multiconductor_to_balanced(mpkg)

These calls need a C library built with the default pkg feature.

read_gridfm reads a gridfm-datakit Parquet dataset back into a BalancedNetwork — the inverse of the gridfm writer, the ML→classical return leg (needs the library built with --features gridfm; gridfm_available() reports it). The read is lossy but complete enough for power flow; what the schema can't round-trip comes back in warnings.

r = read_gridfm("out/case14/raw")              # (; network, scenario, warnings)
to_matpower(r.network)                         # gridfm → any classical format
reads = read_gridfm_scenarios("out/case14/raw")  # one result per scenario id

Multiconductor distribution cases are a separate model on their own MulticonductorNetwork handle (OpenDSS "dss", PowerModelsDistribution ENGINEERING JSON "pmd", IEEE BMOPF JSON "bmopf"; needs the library built with --features dist, on by default in the released binaries; dist_available() reports it and checks PIO_DIST_ABI_VERSION == 1). Experimental while the BMOPF schema is v0.0.1. It shares the transmission verbs: to_format and warnings dispatch on the handle, and the entry points take MulticonductorNetwork first, the parse(T, x) idiom — Julia dispatches on argument types, not the return type.

dn = parse_file(MulticonductorNetwork, "feeder.dss")               # ::MulticonductorNetwork
text, warnings = to_format(dn, "pmd")                    # serialize; same-format write is byte exact
dss, _ = convert_file(MulticonductorNetwork, "feeder.dss", "bmopf")  # one-shot convert
PowerIO.warnings(dn)                                     # fidelity warnings retained on the handle

At first use the binding checks pio_abi_version against the core ABI version it targets and refuses a stale or mismatched library with an error stating both versions. Distribution entry points also check pio_dist_abi_version before calling pio_dist_*.

Interop

Target Direction Mechanism
PowerModels.jl both to_powermodels / from_powermodels
ExaPowerIO.jl / ExaModelsPower.jl out to_powerdata / parse_ac_power_data feeding build_polar_opf / build_rect_opf / build_dcopf
powerio-pkg .pio.json balanced both to_package / from_package / read_package / write_package
PowerDiff.jl out PowerDiff depends on PowerIO as its parser and data layer
MATPOWER / PSS/E / PowerWorld / PowerModels JSON / egret file parse_file / convert_file
GridFM (gridfm-datakit Parquet) in read_gridfm / read_gridfm_scenarios
OpenDSS / PowerModelsDistribution / IEEE BMOPF (distribution) both parse_file(MulticonductorNetwork, …) / to_format / convert_file(MulticonductorNetwork, …)

The parse_file / to_* naming is shared across Rust, Python, Julia, and the C ABI; the cross language table is in docs/languages.md.

Develop

With a sibling powerio checkout, build the C ABI and using PowerIO finds it:

# in the sibling powerio checkout:
cargo build -p powerio-capi --release --features arrow,gridfm,dist,pkg

For a non-sibling layout, point Julia at the library explicitly:

PowerIO.set_library!("/path/to/libpowerio_capi.dylib")
# or: ENV["POWERIO_CAPI"] = "...path..."  before `using PowerIO`

Users never build Rust: released versions fetch the per-platform binary as a lazy artifact. The pipeline is described in docs/binary.md.

Roadmap

0.2.1 tracks powerio v0.3.1 (C ABI 4, distribution ABI 1) and repins the binary artifacts. 0.2.0 added the multiconductor distribution binding (parse_file(MulticonductorNetwork, …) / to_format / convert_file(MulticonductorNetwork, …)) over OpenDSS, PowerModelsDistribution, and IEEE BMOPF. The 0.1.x line tracked C ABI 3: 0.1.0 added the gridfm reader, 0.1.1 the PyPSA CSV writer and reference_bus_indices, 0.1.2 the n_components / is_radial accessors. Next: a fully typed immutable BalancedNetwork mirroring the Rust model (today's view is JSON-backed), a Documenter site, package extensions for the PowerModels and ExaPowerIO bridges, and distribution through a registered PowerIO_jll.

License

MIT.

About

Power system compiler infrastructure

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages