diff --git a/src/openapi/shunt.jl b/src/openapi/shunt.jl index 04b9f01..b622ae9 100644 --- a/src/openapi/shunt.jl +++ b/src/openapi/shunt.jl @@ -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!( @@ -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 @@ -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, @@ -82,6 +96,7 @@ 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)) @@ -89,9 +104,14 @@ function make_switched_admittance!( 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") diff --git a/test/test_openapi_dc_shunt.jl b/test/test_openapi_dc_shunt.jl index 73a5de2..fc34569 100644 --- a/test/test_openapi_dc_shunt.jl +++ b/test/test_openapi_dc_shunt.jl @@ -55,9 +55,10 @@ 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 @@ -65,12 +66,19 @@ end ) @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 @@ -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]),