PSS/E's BINIT is a switched shunt's current total in-service admittance — the value it is actually providing in the solved case. Bi/Ni/Si describe the adjustment range around it, not additions to it. The parser stores BINIT verbatim into PSY's Y, which is documented as "Initial admittance at N = 0", and on v35 additionally populates initial_status from S1..S8. Any consumer that follows the PSY docstring and adds the engaged blocks to Y therefore counts the engaged reactive twice. v33 escapes this only because #44 zeroes initial_status, so the block sum vanishes — the same workaround, tuned so the bug cancels rather than removed.
Concretely, this v35 record:
312045,'1 ', 0, 1, 1,1.10000,0.90000,312045, 0, 100.0,' ',-1320.0,
1, 1,-330.00, 1, 1,-330.00, 1, 1,-330.00, 1, 1,-330.00, 1, 1,-330.00, 1, 1,-330.00, 1, 1,-165.00
parses to bs = -13.2, y_increment = [-3.3 ×6, -1.65], initial_status = [1,1,1,1,1,1,1] (all per-unitization correct). A consumer then computes -13.2 + (-21.45) = -34.65 pu where the answer is -13.2 pu — a 2145 MVAr error at one bus, which is what surfaced this. The record also shows S_i is availability, not engagement: BINIT = -1320 is exactly 4 × (-330), yet all seven blocks carry S_i = 1. If S_i meant engaged, BINIT would have to be -2145.
Two findings
initial_status is read and written as step counts, not binary flags — by PowerFlows at least. Its discrete control computes d.block_n[k] = min(remaining, d.block_steps[k]), which exceeds 1 whenever a block has multiple steps, and write_device_settings! puts it straight into the field via PSY.set_initial_status!(sa, copy(d.block_n)). So a first-party consumer treats sum(initial_status .* Y_increase) as steps × increment, and partial-block engagement is representable. That is in tension with the PSY docstring ("one for in-service and zero for out-of-service"), and which one is authoritative is worth settling separately — but it does not affect this issue either way, since S_i is a 0/1 availability flag under both readings and so does not belong in this field regardless.
PowerFlows already has a convention detector, and v35 falls through the wrong side of it:
_is_binit_shunt(init_status) = !isempty(init_status) && all(iszero, init_status)
- v33 → status all zeros → BINIT branch →
b_fixed = 0, current = imag(Y0) → correct
- v35 → status
[1,1,1,1,1,1,1] → API branch → Y treated as the fixed base and blocks added → double count
So the same root cause bites twice: once in the injection at common.jl:139, once in the susceptance model that drives discrete control. Fixing the parser collapses both, and retires the psse_convention special-casing as a bonus.
Proposed change
Applied to all versions (v33's zeroing is the same workaround, just tuned so the sum happens to vanish):
engaged = reconstruct(BINIT, B_i, N_i) # greedy in record order, sign-matched, capped at N_i
Y = BINIT - Σ engaged_i * B_i # residual — 0 for an exactly-stepped solved case
initial_status = engaged # step counts
The invariant to assert is Y + Σ(initial_status .* Y_increase) == BINIT, exactly, for every record. The bank above gives engaged = [1,1,1,1,0,0,0], Y = 0 — no fixed part, which is physically right since a SWITCHED SHUNT record has no non-switchable portion.
Open questions
- Residual policy.
BINIT isn't always an exact block combination — MODSW=2 shunts sit between steps, and the existing 14-bus fixture has BINIT = 50 against a single B1 = 100 block. Leftover lands in Y, which preserves the total exactly and leaves that fixture's parsed values unchanged. This seems plainly right, but it does mean Y isn't purely an "N=0 base" in those cases.
- Reconstruction order. Greedy in record order, engaging only blocks whose sign matches
BINIT, capped at N_i steps. Mirrors how PSS/E switches them.
PSS/E's
BINITis a switched shunt's current total in-service admittance — the value it is actually providing in the solved case.Bi/Ni/Sidescribe the adjustment range around it, not additions to it. The parser storesBINITverbatim into PSY'sY, which is documented as "Initial admittance at N = 0", and on v35 additionally populatesinitial_statusfromS1..S8. Any consumer that follows the PSY docstring and adds the engaged blocks toYtherefore counts the engaged reactive twice. v33 escapes this only because #44 zeroesinitial_status, so the block sum vanishes — the same workaround, tuned so the bug cancels rather than removed.Concretely, this v35 record:
parses to
bs = -13.2,y_increment = [-3.3 ×6, -1.65],initial_status = [1,1,1,1,1,1,1](all per-unitization correct). A consumer then computes-13.2 + (-21.45) = -34.65 puwhere the answer is-13.2 pu— a 2145 MVAr error at one bus, which is what surfaced this. The record also showsS_iis availability, not engagement:BINIT = -1320is exactly4 × (-330), yet all seven blocks carryS_i = 1. IfS_imeant engaged,BINITwould have to be-2145.Two findings
initial_statusis read and written as step counts, not binary flags — by PowerFlows at least. Its discrete control computesd.block_n[k] = min(remaining, d.block_steps[k]), which exceeds 1 whenever a block has multiple steps, andwrite_device_settings!puts it straight into the field viaPSY.set_initial_status!(sa, copy(d.block_n)). So a first-party consumer treatssum(initial_status .* Y_increase)as steps × increment, and partial-block engagement is representable. That is in tension with the PSY docstring ("one for in-service and zero for out-of-service"), and which one is authoritative is worth settling separately — but it does not affect this issue either way, sinceS_iis a 0/1 availability flag under both readings and so does not belong in this field regardless.PowerFlows already has a convention detector, and v35 falls through the wrong side of it:
b_fixed = 0, current =imag(Y0)→ correct[1,1,1,1,1,1,1]→ API branch →Ytreated as the fixed base and blocks added → double countSo the same root cause bites twice: once in the injection at
common.jl:139, once in the susceptance model that drives discrete control. Fixing the parser collapses both, and retires thepsse_conventionspecial-casing as a bonus.Proposed change
Applied to all versions (v33's zeroing is the same workaround, just tuned so the sum happens to vanish):
The invariant to assert is
Y + Σ(initial_status .* Y_increase) == BINIT, exactly, for every record. The bank above givesengaged = [1,1,1,1,0,0,0],Y = 0— no fixed part, which is physically right since a SWITCHED SHUNT record has no non-switchable portion.Open questions
BINITisn't always an exact block combination — MODSW=2 shunts sit between steps, and the existing 14-bus fixture hasBINIT = 50against a singleB1 = 100block. Leftover lands inY, which preserves the total exactly and leaves that fixture's parsed values unchanged. This seems plainly right, but it does meanYisn't purely an "N=0 base" in those cases.BINIT, capped atN_isteps. Mirrors how PSS/E switches them.