Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/openapi/device_base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ const _DEVICEBASE_INSTANCE_DISPATCHED = Dict{Tuple{String, Symbol}, Symbol}(
("SwitchedAdmittance", :Y) => :skip,
("SwitchedAdmittance", :Y_increase) => :skip,
("SwitchedAdmittance", :admittance_limits) => :skip,
# BINIT (PowerSystems.jl#1774) is the same COMPONENT_MVAR-on-system-base quantity as `Y`
# and `Y_increase` above -- PSY's own to_openapi scales it by the SYSTEM base in both
# document conventions -- so it takes their classification, not a device-base conversion.
("SwitchedAdmittance", :solved_admittance) => :skip,
# parameter_units/dc_voltage_units/admittance_units always "NATURAL_UNITS" for the
# PSS/E-native LCC/VSC fields (dc_branch.jl) -- fixed ohm/kV/S regardless of the
# run's power_units, the mirror image of the COMPONENT_BASE cases above.
Expand Down
15 changes: 13 additions & 2 deletions src/openapi/shunt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,19 @@ function make_switched_admittance!(
Int(get(d, "regulated_bus_number", 0)),
"1",
)
if haskey(d, "initial_status")
set_value!(component, :initial_status, d["initial_status"])
if haskey(d, "number_engaged")
set_value!(component, :number_engaged, d["number_engaged"])
end
# PSS/E BINIT. Always present in a SWITCHED SHUNT record (pti.jl defaults it to 0.0), so
# a parsed switched shunt always carries a solved admittance and downstream reads it
# rather than summing blocks -- which is what PSS/E itself does with the value.
if haskey(d, "solved_admittance")
set_value!(
component,
:solved_admittance,
d["solved_admittance"] * base_power,
"MVAr",
)
end
add_component!(sys, component)
set_component_ext!(sys, component, get(d, "ext", Dict{String, Any}()))
Expand Down
3 changes: 3 additions & 0 deletions src/pm_io/data.jl
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,9 @@ function _make_per_unit!(data::Dict{String, <:Any}, mva_base::Real)
_apply_func!(sw_shunt, "gs", rescale)
_apply_func!(sw_shunt, "bs", rescale)
_apply_func!(sw_shunt, "y_increment", rescale)
# BINIT is an admittance like the two above and must be per-unitized with them;
# skipping it here would leave it a factor of baseMVA too large downstream.
_apply_func!(sw_shunt, "solved_admittance", rescale)
end
end

Expand Down
26 changes: 17 additions & 9 deletions src/pm_io/psse.jl
Original file line number Diff line number Diff line change
Expand Up @@ -897,7 +897,14 @@ function _psse2pm_shunt!(pm_data::Dict, pti_data::Dict, import_all::Bool, nb)
get(sub_data, "sw_id", "1"),
)
sub_data["gs"] = 0.0
sub_data["bs"] = pop!(switched_shunt, "BINIT")
# A PSS/E switched shunt has no fixed base admittance: the record carries only
# BINIT (the solved/initial total susceptance) and the per-block increments. BINIT
# is therefore NOT a `bs` -- it is the device's solved admittance, and goes to its
# own field so downstream can tell "solved total" from "fixed base + blocks"
# instead of inferring it from a zeroed block-status vector.
# See PowerSystems.jl#1774.
sub_data["bs"] = 0.0
sub_data["solved_admittance"] = pop!(switched_shunt, "BINIT")
sub_data["status"] = _determine_injector_status(
switched_shunt,
pm_data,
Expand Down Expand Up @@ -944,18 +951,19 @@ function _psse2pm_shunt!(pm_data::Dict, pti_data::Dict, import_all::Bool, nb)
)
initial_ss_status_sorted =
sort(collect(keys(initial_ss_status)); by = x -> parse(Int, x[2:end]))
sub_data["initial_status"] =
sub_data["number_engaged"] =
[initial_ss_status[k] for k in initial_ss_status_sorted]
sub_data["initial_status"] =
sub_data["initial_status"][1:length(sub_data["step_number"])]
sub_data["number_engaged"] =
sub_data["number_engaged"][1:length(sub_data["step_number"])]

sub_data["ext"]["NREG"] = pop!(switched_shunt, "NREG")
elseif pm_data["source_version"] ∈ ("30", "32", "33")
# Pre-v35 SWITCHED SHUNT records carry no per-block status field. BINIT
# already holds the total in-service admittance, which `bs` above passes
# on, so every block must start out of service whatever MODSW says —
# counting any of them in would double-count that same admittance.
sub_data["initial_status"] = zeros(Int, length(sub_data["y_increment"]))
# Pre-v35 SWITCHED SHUNT records carry no per-block status field, so how
# many steps of each block are engaged is simply unknown. The device's
# actual admittance is BINIT, now carried in `solved_admittance` above, so
# nothing has to be reconstructed from the blocks; zeros here record "no
# per-block information", not "every block is out of service".
sub_data["number_engaged"] = zeros(Int, length(sub_data["y_increment"]))
else
error("Unsupported PSS(R)E source version: $(pm_data["source_version"])")
end
Expand Down
7 changes: 5 additions & 2 deletions test/test_openapi_dc_shunt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,16 @@ end
)
# The fixture's bus-101 SWITCHED SHUNT record declares BINIT = 50.00 and B1 = 100.00,
# so both must read back in the RAW's own MVAr rather than the pm dict's 0.5/1.0 pu.
@test _matches_nt(PFP.get_value(shunt, :Y), (real = 0.0, imag = 50.0))
# BINIT is the SOLVED admittance, not a fixed base: it lands in `solved_admittance` and
# `Y` stays zero, since a PSS/E switched shunt has no fixed base term (#1774).
@test _matches_nt(PFP.get_value(shunt, :Y), (real = 0.0, imag = 0.0))
@test PFP.get_value(shunt, :solved_admittance) == 50.0
@test only(y_increase).imag == 100.0
@test _matches_nt(
PFP.get_value(shunt, :admittance_limits),
(min = d["admittance_limits"][1], max = d["admittance_limits"][2]),
)
@test PFP.get_value(shunt, :initial_status) == d["initial_status"]
@test PFP.get_value(shunt, :number_engaged) == d["number_engaged"]
end

@testset "_switched_admittance_control_mode rejects an unrecognized MODSW code" begin
Expand Down
21 changes: 14 additions & 7 deletions test/test_parse_psse.jl
Original file line number Diff line number Diff line change
Expand Up @@ -146,19 +146,24 @@ end
@test !haskey(pm_v33["bus"][3], "area_slack")
end

@testset "PSSE pre-v35 switched shunt blocks start out of service" begin
# Pre-v35 SWITCHED SHUNT records have no per-block status field, so the parser has to
# fabricate one. BINIT already carries the total in-service admittance into `bs`, so
# every block must start at zero whatever MODSW says; an in-service block would
# double-count the admittance BINIT has already contributed.
@testset "PSSE pre-v35 switched shunts carry BINIT separately from the blocks" begin
# Pre-v35 SWITCHED SHUNT records have no per-block status field, so how many steps of
# each block are engaged is unknown and `number_engaged` is zero-filled to record that.
# BINIT is the device's actual admittance and now lands in its own `solved_admittance`
# key rather than in `bs`, so nothing has to be reconstructed from the blocks and there
# is no double-count to avoid. See PowerSystems.jl#1774.
raw = read_fixture(FOURTEEN_BUS_FIXTURE)
pm_data = parse_file(IOBuffer(raw); filetype = "raw")
@test pm_data["source_version"] == "33"

shunts = collect(values(pm_data["switched_shunt"]))
@test !isempty(shunts)
for shunt in shunts
@test shunt["initial_status"] == zeros(Int, length(shunt["y_increment"]))
@test shunt["number_engaged"] == zeros(Int, length(shunt["y_increment"]))
# BINIT no longer masquerades as a fixed base admittance.
@test shunt["gs"] == 0.0
@test shunt["bs"] == 0.0
@test haskey(shunt, "solved_admittance")
end

# Bus 101's record is MODSW=1, which an earlier mode-specific patch already zeroed.
Expand All @@ -171,7 +176,9 @@ end
@test shunt_101["control_mode"] == 3
@test shunt_101["step_number"] == [5]
@test length(shunt_101["y_increment"]) == 1
@test shunt_101["initial_status"] == [0]
@test shunt_101["number_engaged"] == [0]
# BINIT = 50 MVAr on a 100 MVA base, per-unitized alongside bs/y_increment.
@test shunt_101["solved_admittance"] == 0.5
end

@testset "PSSE transformer CM=2 magnetizing susceptance is inductive" begin
Expand Down
Loading