Working with Cases#
This tutorial shows how to load, inspect, edit, validate, and save a case. The example creates a small case in Python. You can use the same operations after you load a RAW, RAWX, or MATPOWER file. See Recipes for short examples of common case changes.
import tempfile, pathlib
import gpf
from gpf import GpfCase, Bus, Generator, Load, Branch, load_case, write_raw
case = GpfCase(
sbase=100.0,
buses=[
Bus(ibus=1, ide=3, vm=1.02, area=1, baskv=230.0), # slack
Bus(ibus=2, ide=2, vm=1.01, area=1, baskv=230.0), # PV
Bus(ibus=3, ide=1, vm=1.0, area=1, baskv=230.0),
Bus(ibus=4, ide=1, vm=1.0, area=1, baskv=230.0),
],
generators=[
Generator(ibus=1, machid="1", pg=0.0, vs=1.02, qb=-300, qt=300),
Generator(ibus=2, machid="1", pg=80.0, vs=1.01, qb=-150, qt=150),
],
loads=[Load(ibus=3, pl=120, ql=40), Load(ibus=4, pl=60, ql=20)],
branches=[
Branch(ibus=1, jbus=2, r=0.01, x=0.08),
Branch(ibus=2, jbus=3, r=0.01, x=0.08),
Branch(ibus=3, jbus=4, r=0.01, x=0.08),
Branch(ibus=1, jbus=3, r=0.01, x=0.10),
],
)
Editing, validating, and writing back#
Find equipment by its power-system identifier and change the required fields. Validate the case before you solve or save it. The validation result reports unsupported equipment and invalid values. See Recipes for examples that save the edited case.
Reconciling the node-breaker layer#
RAW and RAWX cases can describe switches and nodes inside a substation. Open and closed switches can split or join buses. The bus-branch network must match these switch positions before the solver can use the case.
validate_case reports SUBSTATION_TOPOLOGY_UNAPPLIED when the two network
views do not agree. Use apply_substation_topology to update the bus-branch
network. The example below starts with a closed breaker, so both nodes belong
to bus 2 and validation reports no disagreement.
flowchart TD
A[load node-breaker case] --> C[check switch positions]
C --> D{does the bus-branch network agree?}
D -->|yes| S[solve the case]
D -->|no| P[apply the substation topology]
P --> P1[create or join buses and move connected equipment]
P --> P2[represent switches that connect different buses as branches]
P1 --> W[save the updated case]
P2 --> W
W --> S
from gpf import (apply_substation_topology, validate_case, IssueCode,
Substation, SubstationNode, SubstationSwitchingDevice,
SubstationTerminal)
def build_station(breaker_status):
"""Bus 2 sits behind two nodes joined by a bus-tie breaker; the line
terminates on node 1 and the load on node 2."""
return GpfCase(
sbase=100.0, rev=36,
buses=[Bus(ibus=1, ide=3, vm=1.02, area=1, baskv=230.0),
Bus(ibus=2, ide=1, vm=1.0, area=1, baskv=230.0)],
generators=[Generator(ibus=1, machid="1", pg=0.0, vs=1.02, qb=-300, qt=300)],
loads=[Load(ibus=2, loadid="1", pl=80, ql=20)],
branches=[Branch(ibus=1, jbus=2, ckt="1", r=0.01, x=0.08)],
substations=[Substation(isub=1, name="STATION 2", nodes=[
SubstationNode(inode=1, ibus=2), SubstationNode(inode=2, ibus=2)],
switching_devices=[SubstationSwitchingDevice(
inode=1, jnode=2, swdid="A", name="BUS TIE",
type=2, stat=breaker_status)],
terminals=[SubstationTerminal(ibus=2, inode=1, type="B", jbus=1, eqid="1"),
SubstationTerminal(ibus=2, inode=2, type="L", eqid="1")])])
def unapplied(case):
return [issue.message for issue in validate_case(case).issues
if issue.code is IssueCode.SUBSTATION_TOPOLOGY_UNAPPLIED]
station = build_station(breaker_status=1) # breaker closed
unapplied(station)
[]
Opening the bus-tie breaker separates the two nodes. The bus-branch network still shows one bus, so validation reports the disagreement.
station.substations[0].switching_devices[0].stat = 0 # open the breaker
unapplied(station)
['substation 1 splits bus 2 into 2 node sections behind open switches; the power flow solves the bus-branch model with the bus intact']
apply_substation_topology returns the updated case and a change report. Bus 2
splits into buses 2 and 3. The load moves to bus 3 because its terminal is in
that section. The open breaker becomes an out-of-service branch between the
two buses. Bus 3 has no source, so the result marks it as de-energized.
Validation no longer reports a topology disagreement.
reconciled, report = apply_substation_topology(station)
(report.summary(),
sorted(bus.ibus for bus in reconciled.buses),
reconciled.loads[0].ibus,
unapplied(reconciled))
('1 bus split(s), 1 boundary branch(es), 1 bus(es) de-energized',
[1, 2, 3],
3,
[])
Saving the updated case keeps the node-breaker data by default. If you select RAW revision 33, the writer removes this data because that revision does not support it. The writer reports a warning when it removes the data.
import warnings
def subs_written(case, **write_options):
path = pathlib.Path(tempfile.mkdtemp()) / 'out.raw'
with warnings.catch_warnings():
warnings.simplefilter("ignore") # the downgrade warning is noted in prose
write_raw(case, path, **write_options)
return len(load_case(path).substations)
# default: revision derived from the case; version=33: explicit downgrade
subs_written(reconciled), subs_written(reconciled, version=33)
(1, 0)
Case management shows how to compare versions, restore an earlier version, and share a case with its history.