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
40 changes: 30 additions & 10 deletions src/openapi/shunt.jl
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
# None of `"shunt"`/`"switched_shunt"`/`"facts"` are native PowerModels
# sections, so `_make_per_unit!` never touches them; every field PFFP's own psse.jl
# parser writes is used exactly as written (either PSS/E-native per-unit-at-unity-voltage,
# `ShuntAdmittanceUnitBasis.COMPONENT_MVAR`, for shunt admittances, or a plain natural value
# for everything else) — no `sys_mbase` scaling anywhere in this file.
# `_make_per_unit!` divides the shunt admittances it recognizes by the case base:
# `shunt`'s `gs`/`bs`, and `switched_shunt`'s `gs`/`bs` and `y_increment`. Those four
# arrive here as system per-unit and are multiplied back by `base_power` before
# `set_value!`, the same undo load.jl performs, because
# `ShuntAdmittanceUnitBasis.COMPONENT_MVAR` declares a natural MW/MVAr-at-unity-voltage
# value — the RAW's own GL/BL and BINIT/Bi.
#
# Every other field this file writes is outside that rescale and is used exactly as PFFP's
# own psse.jl parser wrote it: `switched_shunt`'s `admittance_limits` (a voltage band, see
# `make_switched_admittance!`) and all of `facts`.

"""Fixed admittance (PSS/E `FIXED SHUNT`)."""
function make_fixed_admittance!(
Expand All @@ -12,14 +17,21 @@ function make_fixed_admittance!(
d::Dict,
bus_id::Int,
)
base_power = get_base_power(sys)

component = PO.FixedAdmittance()
set_value!(component, :id, register!(reg, "FixedAdmittance", name))
set_value!(component, :name, name)
set_value!(component, :available, Bool(d["status"]))
set_value!(component, :bus, bus_id)
set_value!(component, :base_power, get_base_power(sys), "MVA")
set_value!(component, :base_power, base_power, "MVA")
set_value!(component, :admittance_units, "COMPONENT_MVAR")
set_value!(component, :Y, (real = d["gs"], imag = d["bs"]), "MVAr")
set_value!(
component,
:Y,
(real = d["gs"] * base_power, imag = d["bs"] * base_power),
"MVAr",
)
add_component!(sys, component)
return
end
Expand Down Expand Up @@ -72,7 +84,9 @@ Switched admittance (PSS/E `SWITCHED SHUNT`).

`admittance_limits` mirrors PSCB's own field verbatim: PSS/E's `VSWLO`/`VSWHI` are a
controlled-voltage band, not an admittance band, despite the oracle's field name — a
pre-existing PSCB naming quirk reproduced faithfully, not fixed here.
pre-existing PSCB naming quirk reproduced faithfully, not fixed here. Being voltages, they
are outside `_make_per_unit!`'s admittance rescale and take no `base_power` factor, unlike
`Y` and `Y_increase`.
"""
function make_switched_admittance!(
sys::OpenAPISystem,
Expand All @@ -82,16 +96,22 @@ function make_switched_admittance!(
bus_id::Int,
)
control_mode = _switched_admittance_control_mode(Int(d["control_mode"]))
base_power = get_base_power(sys)

component = PO.SwitchedAdmittance()
set_value!(component, :id, register!(reg, "SwitchedAdmittance", name))
set_value!(component, :name, name)
set_value!(component, :available, Bool(d["status"]))
set_value!(component, :bus, bus_id)
set_value!(component, :admittance_units, "COMPONENT_MVAR")
set_value!(component, :Y, (real = d["gs"], imag = d["bs"]), "MVAr")
set_value!(
component,
:Y,
(real = d["gs"] * base_power, imag = d["bs"] * base_power),
"MVAr",
)
set_value!(component, :number_of_steps, d["step_number"])
_set_y_increase!(component, d["y_increment"], "MVAr")
_set_y_increase!(component, d["y_increment"] * base_power, "MVAr")
admittance_limits = d["admittance_limits"]
set_value!(component, :admittance_limits,
(min = admittance_limits[1], max = admittance_limits[2]), "MVAr")
Expand Down
27 changes: 21 additions & 6 deletions test/test_openapi_dc_shunt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,22 +55,30 @@ end
@test PFP.get_value(line, :base_power) == base
end

@testset "FixedAdmittance: PSS/E-native COMPONENT_MVAR Y, no scaling" begin
@testset "FixedAdmittance: COMPONENT_MVAR Y is natural, undoing the pm dict's system per-unit" begin
pm = fourteen_bus_pm_data()
sys = PFP.build_openapi_system(pm)
base = pm.data["baseMVA"]
d = only(v for v in values(pm.data["shunt"]) if v["shunt_bus"] == 111)
shunt = only(
s for s in PFP.get_components(sys, "FixedAdmittance") if
PFP.get_value(s, :bus) == PFP.get_bus_id(PFP.get_registry(sys), 111)
)
@test PFP.get_value(shunt, :available) == d["status"]
@test PFP.get_value(shunt, :admittance_units) == "COMPONENT_MVAR"
@test _matches_nt(PFP.get_value(shunt, :Y), (real = d["gs"], imag = d["bs"]))
@test _matches_nt(
PFP.get_value(shunt, :Y),
(real = d["gs"] * base, imag = d["bs"] * base),
)
# The fixture's bus-111 FIXED SHUNT record declares GL/BL as 100.000/200.000, so
# COMPONENT_MVAR must read back as the RAW's own MW/MVAr, not the pm dict's 1.0/2.0 pu.
@test _matches_nt(PFP.get_value(shunt, :Y), (real = 100.0, imag = 200.0))
end

@testset "SwitchedAdmittance: control mode mapping, Y_increase array conversion, admittance_limits passthrough" begin
@testset "SwitchedAdmittance: control mode mapping, natural Y/Y_increase, admittance_limits passthrough" begin
pm = fourteen_bus_pm_data()
sys = PFP.build_openapi_system(pm)
base = pm.data["baseMVA"]
d = only(v for v in values(pm.data["switched_shunt"]) if v["shunt_bus"] == 101)
@test d["control_mode"] == 1

Expand All @@ -79,15 +87,22 @@ end
PFP.get_value(s, :bus) == PFP.get_bus_id(PFP.get_registry(sys), 101)
)
@test PFP.get_value(shunt, :control_mode) == "DISCRETE_VOLTAGE"
@test _matches_nt(PFP.get_value(shunt, :Y), (real = d["gs"], imag = d["bs"]))
@test _matches_nt(
PFP.get_value(shunt, :Y),
(real = d["gs"] * base, imag = d["bs"] * base),
)
@test PFP.get_value(shunt, :number_of_steps) == d["step_number"]
y_increase = PFP.get_value(shunt, :Y_increase)
@test length(y_increase) == length(d["y_increment"])
@test all(
PFP.get_value(shunt, :Y_increase)[i].real == real(d["y_increment"][i]) &&
PFP.get_value(shunt, :Y_increase)[i].imag == imag(d["y_increment"][i]) for
y_increase[i].real == real(d["y_increment"][i]) * base &&
y_increase[i].imag == imag(d["y_increment"][i]) * base for
i in eachindex(d["y_increment"])
)
# 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))
@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]),
Expand Down
Loading