Skip to content

Parse BINIT into solved_admittance, rename initial_status to number_engaged - #55

Merged
jd-lara merged 1 commit into
psy6from
lk/issue-1774-switched-shunt-binit
Sep 10, 2026
Merged

jd-lara merged 1 commit into
psy6from
lk/issue-1774-switched-shunt-binit

Conversation

@luke-kiernan

Copy link
Copy Markdown
Contributor

Parser half of the SwitchedAdmittance changes in Sienna-Platform/PowerSystems.jl#1774, following SiennaSchemas#44 (merged), which added solved_admittance and renamed initial_statusnumber_engaged.

Important

CI on this PR will fail until PowerOpenAPIModels is regenerated. PO.SwitchedAdmittance on main still has initial_status and no solved_admittance, and the generated units.jl has no unit declaration for the new field — so every OpenAPI-level test errors with SwitchedAdmittance.solved_admittance declares no unit. The pm_io-level tests pass regardless, since they never touch the generated models.

That regeneration is currently blocked: update-schema.yml begins with gh release view --repo Sienna-Platform/SiennaSchemas, SiennaSchemas has no releases or tags, and the job exits 1 immediately. Every scheduled run for the last several days is completed/failure, and .schema-version still holds a bare git SHA from a local run. Someone needs to cut a SiennaSchemas release for #44 to propagate. The same blocker holds PowerSystems.jl#1789.

Verified locally against a hand-patched copy of the operations models carrying the regenerated struct plus the units.jl declaration codegen will emit from #44's annotations: full suite green, 0 failures.

What was wrong

BINIT was parsed into bs, which shunt.jl writes to Y. A PSS/E switched shunt has no fixed base admittance — the SWITCHED SHUNT record carries only BINIT (the solved/initial total susceptance) and the per-block increments. Putting the solved total into Y forced everything downstream to disentangle the two again:

  • pre-v35 zeroed a full-length block-status vector so the blocks could not double-count what BINIT already included;
  • that zeroed vector then became the sentinel PowerFlows.jl read to decide which convention a shunt followed (ControlledSwitchedShunt.psse_convention);
  • and for v35, where S1..S8 are genuinely non-zero, the sentinel never matched, so the consumer added the engaged blocks on top of the total that already contained them.

What changed

file change
pm_io/psse.jl bs = 0.0; BINIT → sub_data["solved_admittance"]. v35 S1..S8number_engaged. Pre-v35 zero-fill kept, but its comment now says what it means — no per-block information — rather than every block out of service.
pm_io/data.jl _make_per_unit! per-unitizes solved_admittance alongside bs/y_increment. Without this the value reaches the component a factor of baseMVA too large.
openapi/shunt.jl emits :number_engaged and :solved_admittance (MVAr, via the unit-converting set_value!).
openapi/device_base.jl classifies ("SwitchedAdmittance", :solved_admittance) as :skip.

On the :skip classification

It inherits the admittance_units discriminator, which per that file's header is a representation switch (pu vs natural for the same field) rather than a natural-unit choice — and PSY's own to_openapi scales it by the SYSTEM base in both document conventions. So it takes the same classification as Y/Y_increase/admittance_limits, not a device-base conversion.

Worth noting the registry is total by design: an unlisted instance-dispatched pair errors by construction rather than falling through. Omitting this entry surfaced as a loud failure instead of a silently mis-scaled admittance in COMPONENT_BASE documents — the guard did its job.

One decision worth a reviewer's eye

solved_admittance is populated unconditionally, because a SWITCHED SHUNT record always carries BINIT (pti.jl defaults it to 0.0). A parsed shunt therefore always has a solved admittance, and downstream reads it instead of summing blocks — which is what PSS/E does with the value. The alternative reading of the manual's "locked" clause is to gate on MODSW == 0. Consequence of the current choice: a case with BINIT = 0 and in-service blocks yields zero susceptance rather than the block sum.

Also note number_engaged parsed from v35 Si is a per-block status flag, not a step count, so the block-sum fallback under-counts any block with more than one step. It rarely matters because BINIT is always set and the exact branch wins.

Tests

  • pre-v35 testset renamed (its old name described sentinel semantics that no longer exist) and now pins gs == bs == 0.0 plus a per-unitized solved_admittance == 0.5 for the fixture's BINIT = 50 on a 100 MVA base.
  • the OpenAPI shunt test asserts Y == 0.0 + 0.0im with solved_admittance == 50.0 MVAr, where it previously asserted Y.imag == 50.0 — the clearest single expression of the change.

Related

🤖 Generated with Claude Code

https://claude.ai/code/session_01LP6eB1zx4eyE3hd7tpvSue

…ngaged

Parser half of the SwitchedAdmittance changes in PowerSystems.jl#1774, following
Sienna-Platform/SiennaSchemas#44 (merged), which added `solved_admittance` and
renamed `initial_status` to `number_engaged`.

BINIT was being parsed into `bs`, which `shunt.jl` writes to `Y`. A PSS/E switched
shunt has no fixed base admittance -- the SWITCHED SHUNT record carries only BINIT
(the solved/initial total susceptance) and the per-block increments -- so putting
the solved total into `Y` forced everything downstream to disentangle the two
again. Pre-v35 did it by zeroing a full-length block-status vector so the blocks
could not double-count what BINIT already included; that zeroed vector then became
the sentinel PowerFlows.jl read to decide which convention a shunt followed.

BINIT now goes to its own `solved_admittance` key with `bs = 0.0`, so "solved
total" and "fixed base + blocks" are distinguishable without inference, and the
pre-v35 zero-fill means what it says: no per-block information. v35's S1..S8 keeps
parsing to `number_engaged`.

Three details worth review:

- `_make_per_unit!` gains `solved_admittance` alongside `bs`/`y_increment`. BINIT is
  an admittance and must be per-unitized with them; without it the value reaches
  the component a factor of baseMVA too large.
- `device_base.jl` classifies `("SwitchedAdmittance", :solved_admittance)` as
  `:skip`, matching `Y`/`Y_increase`/`admittance_limits`. It inherits the
  `admittance_units` discriminator, which is a representation switch rather than a
  natural-unit choice, and PSY's `to_openapi` scales it by the SYSTEM base in both
  document conventions. The registry is total, so omitting it errored by
  construction rather than silently mis-scaling a COMPONENT_BASE document.
- `solved_admittance` is populated unconditionally, since a SWITCHED SHUNT record
  always carries BINIT (pti.jl defaults it to 0.0). A parsed shunt therefore always
  has a solved admittance and downstream reads it instead of summing blocks --
  which is what PSS/E does with the value. Gating on MODSW == 0 is the alternative
  reading of the manual's "locked" clause.

Tests updated: the pre-v35 testset now pins `gs == bs == 0.0` and a per-unitized
`solved_admittance`, and the OpenAPI shunt test asserts `Y` is zero with BINIT in
`solved_admittance` rather than `Y.imag == 50.0`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LP6eB1zx4eyE3hd7tpvSue
@luke-kiernan

luke-kiernan commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

solved_admittance is populated unconditionally, because a SWITCHED SHUNT record always carries BINIT (pti.jl defaults it to 0.0). A parsed shunt therefore always has a solved admittance, and downstream reads it instead of summing blocks — which is what PSS/E does with the value. The alternative reading of the manual's "locked" clause is to gate on MODSW == 0. Consequence of the current choice: a case with BINIT = 0 and in-service blocks yields zero susceptance rather than the block sum.

Oof I didn't notice that. @mcllerena any ideas on how to tell when we should use BINIT vs the block statuses? iirc the manual says "use BINIT when MODSW is zero or the RAW file should be interpreted as solved," but how do we tell if it meets that second criteria...

@jd-lara
jd-lara merged commit cf53c9a into psy6 Sep 10, 2026
2 of 7 checks passed
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.

2 participants