Comparison#

spec_to_keyed_dict(spec, source='')#

Convert PfNetworkSpec to diff-friendly dict keyed by canonical IDs.

Parameters:
  • spec -- Normalized power flow spec.

  • source -- Source file name for metadata.

Returns:

Dict with meta + per-model dicts keyed by canonical identifiers.

export_spec_json(spec, source='', *, indent=2, sort_keys=True)#

Export PfNetworkSpec to JSON string.

Parameters:
  • spec -- Normalized power flow spec.

  • source -- Source file name for metadata.

  • indent -- JSON indentation level.

  • sort_keys -- Sort keys for deterministic output.

Returns:

JSON string.

export_spec_file(spec, path, source='')#

Export PfNetworkSpec to JSON file.

Parameters:
  • spec -- Normalized power flow spec.

  • path -- Output file path.

  • source -- Source file name for metadata.

diff_specs(spec_a, spec_b, source_a='A', source_b='B', *, tol=1e-06)#

Compare two normalized solver networks.

This comparison can show whether cases from different source formats represent the same network. An empty result means that all differences are within tol.

Parameters:
  • spec_a -- First finalized PfNetworkSpec.

  • spec_b -- Second finalized PfNetworkSpec.

  • source_a -- Display name for the first network.

  • source_b -- Display name for the second network.

  • tol -- Relative numeric tolerance. The comparison ignores smaller differences.

Returns:

A CaseDiff grouped by normalized network family and equipment identity. Added, removed, and changed values retain both source names.

diff_cases(case_a, case_b, source_a='A', source_b='B', *, tol=0.0)#

Compare the equipment records and headers of two related cases.

The comparison matches equipment by its stable key, such as a bus number or branch terminal pair. Use diff_specs() when two file formats should be compared after solver normalization.

Parameters:
  • case_a -- Source case.

  • case_b -- Target case.

  • source_a -- Display name for the source case.

  • source_b -- Display name for the target case.

  • tol -- Absolute tolerance for numeric field differences. Zero requires exact equality.

Returns:

Added, removed, and changed records from case_a to case_b.

case_to_keyed_dict(case)#

Convert a case to records indexed by their stable equipment keys.

Parameters:

case -- Case to convert.

Returns:

The keyed record families and any duplicate-key warnings. The first record wins when a family contains a duplicate key.

case_to_snapshot(case)#

Convert a case to a canonical JSON-compatible snapshot.

The snapshot contains every header field and equipment family. It can be restored with case_from_snapshot().

Parameters:

case -- Case to serialize.

Returns:

A snapshot that standard JSON encoders can serialize.

case_from_snapshot(doc, *, allow_skew=False)#

Restore and validate a case from a canonical snapshot.

Older snapshots receive current field defaults. Unknown fields raise an error by default because they can contain data from a newer version.

Parameters:
  • doc -- Snapshot created by case_to_snapshot().

  • allow_skew -- Drop unknown fields and add one finding for each drop.

Returns:

The restored case.

Raises:

SchemaSkewError -- If the snapshot has unknown fields and allow_skew is false.

class CaseDiff(source_a, source_b, tol, counts, added, removed, changed, warnings=<factory>)#

Record-level delta from source_a to source_b.

added / removed map family -> {key: record_dump}; changed maps family -> {key: {field: (before, after)}}. counts is per-family (n_a, n_b) over every family. Empty inner dicts are omitted.

classmethod from_entries(entries)#

Build an exact record delta from authored before and after values.

property is_empty#

Return true when the cases have no recorded differences.

filtered(families)#

A copy restricted to families (any iterable of family names). is_empty / totals then reflect only the kept families.

summary()#

Return a one-line difference summary.

invert()#

The reverse diff (source_b -> source_a): a pure swap, so diff(b, a) == diff(a, b).invert().

to_dict()#

Return a JSON-compatible representation of the case difference.

to_json(indent=2)#

Return the case difference as JSON.

query(sql)#

Query diff entries with read-only SQL.

Parameters:

sql -- One SQLite SELECT or WITH statement.

Returns:

Query rows as dictionaries. Use schema() to list columns.

Examples

>>> d.query("SELECT family, count(*) FROM entries GROUP BY family")
>>> d.query("SELECT key_str, before, after FROM entries "
...        "WHERE family='branches' AND field='rate1'")
schema()#

Return query table names and their columns.

classmethod from_dict(doc)#

Rebuild a CaseDiff from the to_dict() wire form.

This inverse lets a stored or transmitted diff be applied. Pure regrouping: the flat entries list folds back into the nested added/removed/changed maps, and an array key becomes a tuple (a spec string key stays a string). No value coercion occurs. The JSON-shaped values match the JSON-shaped snapshot apply_diff() compares against and assigns through pydantic, so apply_diff(a, CaseDiff.from_dict( diff_cases(a, b).to_dict())) re-keys identically to b.

to_markdown(*, limit=20)#

Return the case difference as Markdown.

apply_diff(case, diff, *, on_conflict='raise', allow_skew=False, identity_changes=None)#

Apply diff to a copy of case and return the patched case.

The input remains unchanged through copy-on-write edits. Changed and removed entries verify prior values, and added entries verify absence. on_conflict can raise, skip, or force these base-state mismatches. Current record classes normalize older patches. Unknown newer fields raise SchemaSkewError before application unless allow_skew=True records and skips them. Schema skew is independent of base-state conflict handling. If supplied, identity_changes receives applied renames and removals after the full patch succeeds.

class DiffConflictError#

apply_diff found the base case does not match the diff's recorded before state (a drifted base). Raised unless on_conflict relaxes it.

class SchemaSkewError#

Report fields that the installed gpf schema does not recognize.

A different gpf version can create these fields. Use allow_skew=True to omit them and record a finding on the returned case.

sol_to_keyed_dict(sol)#

Convert a solved operating point to keyed, readable values.

Parameters:

sol -- Solved case to project.

Returns:

A mapping with bus, gen, and branch families. Each family maps stable equipment identities to solved fields. Bus angles use degrees. Power values use MW and Mvar. Equipment identities match those used by diff_cases.

diff_solutions(sol_a, sol_b, source_a='A', source_b='B', *, tol=1e-06)#

Compare bus voltages, generation, and branch flows in two solutions.

Parameters:
  • sol_a -- Source solution.

  • sol_b -- Target solution.

  • source_a -- Display name for the source solution.

  • source_b -- Display name for the target solution.

  • tol -- Relative tolerance used to ignore numerical solve noise.

Returns:

The changed operating-point values.

merge_cases(base, *overlays, on_conflict='error')#

Merge related cases into a new case without changing the inputs.

Records are keyed by natural identity across every source, family by family plus the "case" header pseudo-family (sbase/rev/basfrq/titles/...). header fields follow the same rule as device fields: any disagreement is a conflict). A key in exactly one source is carried through; a key in several sources with identical values merges silently; differing values are a conflict, resolved by on_conflict:

  • "error": collect every conflict, then raise MergeConflictError.

  • "overlay_wins": the later source wins. Overlays apply in argument order, so the last overlay naming a key wins.

  • "base_wins": the earlier source wins. Base wins over any overlay, and an earlier overlay over a later one for keys the base lacks.

All sources must use the same bus numbers and equipment keys.

Parameters:
  • base -- Initial case and first source in conflict precedence.

  • overlays -- Additional cases in precedence order.

  • on_conflict -- Use "error", "overlay_wins", or "base_wins".

Returns:

The merged case and a merge report.

Raises:

MergeConflictError -- If conflicts exist and on_conflict is "error".

class MergeReport(sources, added, conflicts, warnings=<factory>)#

Summarize merged sources, added records, warnings, and conflicts.

summary()#

Return a one-line merge summary.

to_dict()#

Return a JSON-compatible merge report.

class MergeConflictError(conflicts)#

Report all same-key records that have conflicting values during a merge.

impact(before, after, *, solve=True, band=None, rating_set=1, top=10, provenance=None, before_label='before', after_label='after', backend=None)#

Measure the network and operating-point effects of case changes.

Parameters:
  • before -- Case before the change.

  • after -- Case after the change.

  • solve -- Solve both cases and compare operating points.

  • band -- Global voltage band as (low_pu, high_pu). None uses each bus limit.

  • rating_set -- Branch rating column from 1 through 12.

  • top -- Maximum number of entries in each ranked list.

  • provenance -- Ledger operation labels that describe the change.

  • before_label -- Display name for the first case.

  • after_label -- Display name for the second case.

  • backend -- Backend name or None for automatic selection.

Returns:

A structured impact report. If either solve fails to converge, the voltage and thermal sections are None.

class ImpactReport(before_label, after_label, solved, converged, rating_set, band, topology, dispatch, voltage, thermal, health, provenance=None, notes=<factory>, sources=<factory>)#

Structured delta between two solved operating points. Sections keyed off a failed or skipped solve are None and named in notes; the record-derived sections (topology, load delta, islands) are always present.

to_dict()#

Return a JSON-compatible impact report.

to_json(indent=2)#

Return the impact report as JSON.

headline()#

The single most important grounded fact, one sentence's worth.