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

PSS/E contingency analysis files

A PSS/E contingency analysis reads three text files beside the case: a contingency description file (.con) naming the outages to run, a subsystem description file (.sub) naming the bus groups those outages draw on, and a monitored element file (.mon) naming what the run reports on. Each is a PowerIO value of its own, not a network: ContingencySet, SubsystemSet, and MonitoredSet. Their PowerIO IR structural type names are in the PowerIO IR reference.

CONTINGENCY 'L_000001ODES'
OPEN LINE FROM BUS   1001 TO BUS   1064 CIRCUIT 1
END
SINGLE BRANCH IN SUBSYSTEM 'WOA'
END

parse routes a file by its extension or by a declared token, emit writes it back under that same token, and serialize carries it through PowerIO IR. The tokens are psse-con, psse-sub, and psse-mon; con, sub, mon, contingency, subsystem, and monitored are accepted aliases. No grid exchange format states one of these files, so emitting a .con module as psse or as psse-sub is refused by value type.

Reading and binding

Reading holds what the file states and touches no network. Binding is the separate step.

use powerio::{PioValue, parse};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let case = parse("case.raw")?;
    let PioValue::BalancedNetwork(network) = case.value() else {
        panic!("a .raw parses to a balanced network");
    };

    let module = parse("cases.con")?;
    let PioValue::ContingencySet(set) = module.value() else {
        panic!("a .con parses to a contingency set");
    };

    let resolution = set.resolve(network);
    println!(
        "{} of {} cases bound",
        resolution.resolved,
        resolution.cases.len()
    );
    for note in resolution.diagnostics() {
        eprintln!("{}: {}", note.code(), note.message());
    }

    // An automatic specification expands into one case per element of a
    // subsystem, so the expanded set states every outage explicitly.
    let subsystems = parse("groups.sub")?;
    let PioValue::SubsystemSet(subsystems) = subsystems.value() else {
        panic!("a .sub parses to a subsystem set");
    };
    let expanded = set.expand(network, subsystems);
    print!("{}", expanded.set.to_con());
    Ok(())
}

ContingencySet::resolve returns one ResolvedCase per case: the elements each action bound to, and the actions that bound to nothing with the reason each one did not. Subsystem::select_buses names the buses of one subsystem, and MonitoredSet::resolve binds monitored branches, interfaces, and voltage scopes to table rows against a network and a subsystem set. The binding recomputes the PSS/E machine and circuit identifier of every element with the RAW writer’s own allocation, because a network row’s identity carries neither.

Python reaches the same three values and the same two operations:

import powerio

case = powerio.parse("case.raw").value
cases = powerio.parse("cases.con").value

resolution = case.resolve_contingencies(cases.text)
print(resolution["resolved"], "of", resolution["cases"], "cases bound")

groups = powerio.parse("groups.sub").value
expanded, notes = case.expand_contingencies(cases.text, groups.text)
print(expanded)
print(case.select_subsystem_buses(groups.text, "A1"))

C reaches them through pio_value_contingency_set, pio_contingency_set_resolve, and pio_contingency_set_expand, and writes each file back with pio_contingency_set_to_con, pio_subsystem_set_to_sub, and pio_monitored_set_to_mon, so an expanded set reaches a file from C. The C API guide lists the accessors.

What is kept as text

Each reader covers the statements its grammar states and keeps every other line as the source wrote it, under READ.CON.STATEMENT_UNRECOGNIZED, READ.SUB.STATEMENT_UNRECOGNIZED, or READ.MON.STATEMENT_UNRECOGNIZED. A line kept inside a case becomes ContingencyAction::Unrecognized; a line kept at file level becomes a RetainedStatement with the 1-based source line. A tool that writes its own directives into a .con therefore reads completely rather than failing on its first line, and to_con writes those lines back.

Only three shapes are errors: a case that never closes, a case that starts inside another, and a block left open at end of input. The reader’s note budget is sixteen records per file; past it one further note reports the suppression.

A case that does not apply to the network is a BUILD.CON.CASE_UNRESOLVED note naming the case and the first action that bound to nothing, rather than a silent omission. An automatic specification naming a subsystem the .sub file does not state stays unexpanded and earns BUILD.CON.SUBSYSTEM_UNKNOWN.

Emission

A parsed module emits its own file exactly: emit returns the retained source bytes for a same format write. A module that carries no source, such as one read back from PowerIO IR, emits canonical text through to_con, to_sub, or to_mon. Reading canonical text back gives the same set, except that a statement kept from the middle of a source file is written after the cases and so reads back from a later line; writing that second set gives the same text.

Command line

$ powerio contingency resolve case.raw cases.con
$ powerio contingency resolve case.raw cases.con --sub groups.sub --mon watch.mon --json
$ powerio contingency expand case.raw cases.con --sub groups.sub -o expanded.con

resolve prints the case counts, one line per case that bound to nothing with its reason, and, when --mon names a monitored element file, the monitored row counts. --sub is read with --mon, whose statements name the subsystems it states, and resolve refuses --sub without it. --json prints the same counts as one object on stdout. expand writes the expanded .con text to stdout, or to the file -o names.

The grammar

The grammar is established from public contingency files and the example set PSS/E ships, because the manual that defines it is licensed and not public. powerio-tx/src/contingency/FORMAT.md lists each statement with its evidence and gives the writer’s spellings. The case names an expansion generates and the reading of 3WLOWVOLTAGE are PowerIO’s convention, because PSS/E documents neither publicly.