Skip to content

Believe BINIT only where PSS/E would not have moved it - #59

Open
luke-kiernan wants to merge 2 commits into
psy6from
lk/issue-57-switched-shunt-binit-blocks
Open

luke-kiernan wants to merge 2 commits into
psy6from
lk/issue-57-switched-shunt-binit-blocks

Conversation

@luke-kiernan

Copy link
Copy Markdown
Contributor

Closes #57.

Si is a status, not a step count

A v35 SWITCHED SHUNT block carries a status Si — in service or out — so block i contributes Si * Ni * Bi. number_engaged counts engaged steps, and was being filled with the bare status, understating every multi-step block by a factor of Ni. An in-service block now engages all Ni of its steps; the RAW has no way to say a v35 block is partly in.

BINIT is believed only where PSS/E would not have moved it

BINIT is the device's actual admittance only where PSS/E would not have adjusted the shunt itself:

condition why
MODSW == 0 locked
bus type 3 PSS/E does not adjust a shunt on the swing bus, whatever MODSW says
MODSW == 2 continuous: the admittance is off the step ladder, so the block statuses could not express it even in principle
no Si column (pre-v35) nothing to reconstruct from
solved_case = true caller declares the RAW was written out after a converged power flow

Otherwise — a discretely adjusted shunt (MODSW 1/3/4/5) on a type 1/2 bus — BINIT is only where PSS/E's own solution started, while the engaged blocks are what the record asserts about the device. solved_admittance is then left off entirely, and PowerFlows.jl falls through to number_engaged .* Y_increase; _apply_func! and the OpenAPI reader are both already haskey-guarded, so the absent key needs no other handling.

solved_case (default false) is threaded through parse_fileparse_psse_pti_to_powermodels!, and is available as a PowerModelsData keyword.

Only v35 cases change behaviour. Pre-v35 records keep BINIT unconditionally and keep their zero-filled number_engaged.

Block reading follows the POM's terminator

_switched_shunt_blocks replaces _is_switched_shunt_block. The v36 DataFormats manual: "Number of steps for block i (1 through 8). The first zero value of Ni or Bi is interpreted as the end of the switched shunt blocks for bus I." The blocks are a contiguous run from block 1 ended by that terminator, so they are now read as one rather than by filtering zeros out of all eight Ni and Bi columns independently.

The two agree on every well-formed record. They differ only on a record with a gap (N2 = 0 alongside a nonzero B3), which the run now reads the way PSS/E does — one block — instead of promoting the columns past the terminator to blocks of their own.

Testing

New fixture test/fixtures/v35_switched_shunt.raw covers each branch: adjusted, locked, continuous, swing-bus, the terminator, and solved_case = true.

774/774 PSS/E parse tests pass (19 new), formatter clean.

⚠️ The OpenAPI half of the suite could not be run. test/Manifest.toml pointed at a deleted path, and re-resolving fails on OpenAPIPowerCoreOpenAPIModels requires 1.1.x while this branch's compat pins 0.2. That is what #58 fixes; these tests were run against the root project instead. Nothing here touches src/openapi/, so #58 and this PR should not conflict.

Related, not fixed here

PowerFlows.jl's v35 exporter has the mirror image of the Si bug: _build_switched_shunt_steps_v35 writes S_i = engaged[i], pushing a step count into a field PSS/E reads as a 0/1 status. Round-tripping number_engaged = [3, 0] emits S1 = 3.

🤖 Generated with Claude Code

Closes #57.

A v35 SWITCHED SHUNT block carries a status Si, not a step count, so block i
contributes Si * Ni * Bi. `number_engaged` counts engaged steps and was being
filled with the bare status, understating every multi-step block by a factor
of Ni.

BINIT is the device's actual admittance only where PSS/E would not have
adjusted the shunt itself: locked (MODSW 0), on a type 3 bus, continuously
controlled (MODSW 2, whose admittance is off the step ladder and cannot be
reconstructed from block statuses at all), or read from a version with no Si
column. For a discretely adjusted shunt (MODSW 1/3/4/5) on a type 1/2 bus,
BINIT is only where PSS/E's own solution started, and the engaged blocks are
what the record asserts; `solved_admittance` is then left off and PowerFlows
falls through to `number_engaged .* Y_increase`. A new `solved_case` keyword
on `parse_file`/`PowerModelsData` declares a RAW written out after a converged
power flow, restoring BINIT everywhere.

`_switched_shunt_blocks` replaces `_is_switched_shunt_block` and reads the
blocks as the POM defines them -- a contiguous run from block 1, ended by the
first zero Ni or Bi -- rather than filtering zeros out of all eight columns
independently. The two agree on every well-formed record; they differ only on
one with a gap, which the run now reads as PSS/E does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@luke-kiernan luke-kiernan linked an issue Sep 15, 2026 that may be closed by this pull request
@luke-kiernan
luke-kiernan requested review from jd-lara and mcllerena and a lite review from Copilot September 15, 2026 17:01

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Moderate findings remain on node-breaker source-bus handling and v35 remote-mode coverage, with one comment nit also outstanding.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Updates PSS/E v35 switched-shunt parsing for status semantics, BINIT handling, and block termination.

Changes:

  • Corrects engaged-step reconstruction and contiguous block parsing.
  • Adds conditional BINIT handling and solved_case propagation.
  • Adds regression fixtures and parser tests.
File summaries
File Summary
test/test_parse_psse.jl Adds v35 switched-shunt regression coverage.
test/fixtures/v35_switched_shunt.raw Provides cases for BINIT branches, termination, and solved_case.
src/power_models_data.jl Exposes the solved_case option.
src/pm_io/psse.jl Implements switched-shunt logic. Findings: moderate (3 votes) source-bus type handling; moderate (1 vote) v35 remote-mode coverage; nit (2 votes) malformed comment.
src/pm_io/common.jl Threads solved_case through parsing.
Review details

Suppressed comments (1)

src/pm_io/psse.jl:853

  • The new v35 behavior depends on all four discrete modes in this tuple, but the fixture only exercises v35 MODSW=1; the existing MODSW=3 test is pre-v35, where has_block_status short-circuits the decision and cannot validate this membership. Please add v35 cases for the remote discrete modes (at least 3, with 4/5 if supported) so a mode-specific regression cannot silently restore BINIT.
const _DISCRETE_SWITCHED_SHUNT_MODES = (1, 3, 4, 5)
  • Files reviewed: 5/5 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/pm_io/psse.jl Outdated
Comment thread src/pm_io/psse.jl Outdated
In a node-breaker case `_nb_target` routes a switched shunt onto an injected
node-bus, which `_prepare_node_breaker!` initializes to PQ; only
`_migrate_node_breaker_gen_bus_type!` promotes one back to PV/REF, and that
runs after this section. Reading the type off `shunt_bus` therefore dropped
BINIT for a shunt declared on the swing bus. PSS/E's rule is about the RAW's
own bus I, so it now reads `bus_number`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Switched Shunt BINIT and block-status handling

2 participants