diff --git a/src/definitions.jl b/src/definitions.jl index ea47e9e68f..5a6ea49002 100644 --- a/src/definitions.jl +++ b/src/definitions.jl @@ -181,6 +181,7 @@ const BRANCH_BUS_VOLTAGE_DIFFERENCE_TOL = 0.01 const PSSE_PARSER_TAP_RATIO_UBOUND = 1.5 const PSSE_PARSER_TAP_RATIO_LBOUND = 0.5 +const PARSER_TAP_RATIO_CORRECTION_TOL = 1e-5 const WINDING_NAMES = Dict( WindingCategory.PRIMARY_WINDING => "primary", diff --git a/src/parsers/pm_io/psse.jl b/src/parsers/pm_io/psse.jl index 6576ca9c2f..3659dc2436 100644 --- a/src/parsers/pm_io/psse.jl +++ b/src/parsers/pm_io/psse.jl @@ -574,6 +574,37 @@ function _psse2pm_shunt!(pm_data::Dict, pti_data::Dict, import_all::Bool) end end +function apply_tap_correction!( + windv_value::Float64, + transformer::Dict{String, Any}, + cod_key::String, + rmi_key::String, + rma_key::String, + ntp_key::String, + cw_value::Int64, + winding_name::String, +) + if abs(transformer[cod_key]) ∈ [1, 2] && cw_value ∈ [1, 3] + tap_positions = collect( + range( + transformer[rmi_key], + transformer[rma_key]; + length = Int(transformer[ntp_key]), + ), + ) + closest_tap_ix = argmin(abs.(tap_positions .- windv_value)) + if !isapprox( + windv_value, + tap_positions[closest_tap_ix]; + atol = PARSER_TAP_RATIO_CORRECTION_TOL, + ) + @warn "Transformer $winding_name winding tap setting is not on a step; $windv_value set to $(tap_positions[closest_tap_ix])" + return tap_positions[closest_tap_ix] + end + end + return windv_value +end + """ _psse2pm_transformer!(pm_data, pti_data) @@ -769,10 +800,20 @@ function _psse2pm_transformer!(pm_data::Dict, pti_data::Dict, import_all::Bool) sub_data["nomv2"] = transformer["NOMV2"] end - sub_data["tap"] = pop!(transformer, "WINDV1") / pop!(transformer, "WINDV2") + windv1 = pop!(transformer, "WINDV1") + windv1 = apply_tap_correction!( + windv1, + transformer, + "COD1", + "RMI1", + "RMA1", + "NTP1", + transformer["CW"], + "primary", + ) + sub_data["tap"] = windv1 / pop!(transformer, "WINDV2") sub_data["shift"] = pop!(transformer, "ANG1") - # Unit Transformations if transformer["CW"] != 1 # NOT "for off-nominal turns ratio in pu of winding bus base voltage" sub_data["tap"] *= _get_bus_value(transformer["J"], "base_kv", pm_data) / @@ -1082,10 +1123,45 @@ function _psse2pm_transformer!(pm_data::Dict, pti_data::Dict, import_all::Bool) sub_data["secondary_correction_table"] = transformer["TAB2"] sub_data["tertiary_correction_table"] = transformer["TAB3"] + windv1 = transformer["WINDV1"] + windv2 = transformer["WINDV2"] + windv3 = transformer["WINDV3"] + + windv1 = apply_tap_correction!( + windv1, + transformer, + "COD1", + "RMI1", + "RMA1", + "NTP1", + transformer["CW"], + "primary", + ) + windv2 = apply_tap_correction!( + windv2, + transformer, + "COD2", + "RMI2", + "RMA2", + "NTP2", + transformer["CW"], + "secondary", + ) + windv3 = apply_tap_correction!( + windv3, + transformer, + "COD3", + "RMI3", + "RMA3", + "NTP3", + transformer["CW"], + "tertiary", + ) + if transformer["CW"] == 1 - sub_data["primary_turns_ratio"] = transformer["WINDV1"] - sub_data["secondary_turns_ratio"] = transformer["WINDV2"] - sub_data["tertiary_turns_ratio"] = transformer["WINDV3"] + sub_data["primary_turns_ratio"] = windv1 + sub_data["secondary_turns_ratio"] = windv2 + sub_data["tertiary_turns_ratio"] = windv3 else sub_data["primary_turns_ratio"] = transformer["WINDV1"] / sub_data["base_voltage_primary"] diff --git a/test/test_parse_psse.jl b/test/test_parse_psse.jl index e4953f5fcb..8564c81c5b 100644 --- a/test/test_parse_psse.jl +++ b/test/test_parse_psse.jl @@ -353,6 +353,27 @@ end @test IS.compare_values(original_sys, deserialized_sys) end +@testset "PSSE transformer tap position correction testing" begin + sys = build_system( + PSSEParsingTestSystems, + "psse_14_tap_correction_test_system"; + force_build = true, + ) + #test 2W correction matches PSSE + trf = get_component(TapTransformer, sys, "BUS 104-BUS 107-i_1") + tap = get_tap(trf) + @test isapprox(tap, 0.979937; atol = 1e-6) + + #test 3W correction matches PSSE + trf_3w = get_component(Transformer3W, sys, "BUS 109-BUS 104-BUS 107-i_1") + tap1 = get_primary_turns_ratio(trf_3w) + tap2 = get_secondary_turns_ratio(trf_3w) + tap3 = get_tertiary_turns_ratio(trf_3w) + @test isapprox(tap1, 0.98750; atol = 1e-6) + @test isapprox(tap2, 0.97500; atol = 1e-6) + @test isapprox(tap3, 0.96250; atol = 1e-6) +end + @testset "PSSE isolated bus handling (unavailable vs topologically isolated)" begin sys = build_system(PSSEParsingTestSystems, "isolated_bus_test_system"; force_build = true)