From 3ac408bc036f024bd06ece29540779f2a6f446d3 Mon Sep 17 00:00:00 2001 From: m-bossart Date: Sun, 22 Jun 2025 08:47:31 -0700 Subject: [PATCH 01/10] add correction for bad tap data to match psse --- src/parsers/pm_io/psse.jl | 22 ++++++++++++++++++++-- test/test_parse_psse.jl | 11 +++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/parsers/pm_io/psse.jl b/src/parsers/pm_io/psse.jl index 6576ca9c2f..c53c763274 100644 --- a/src/parsers/pm_io/psse.jl +++ b/src/parsers/pm_io/psse.jl @@ -769,10 +769,28 @@ 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") + if abs(transformer["COD1"]) ∈ [1, 2] + tap_data = pop!(transformer, "WINDV1") / pop!(transformer, "WINDV2") + tap_positions = collect( + range( + transformer["RMI1"], + transformer["RMA1"]; + length = Int(transformer["NTP1"]), + ), + ) + closest_tap_ix = argmin(abs.(tap_positions .- tap_data)) + if !isapprox(tap_data, tap_positions[closest_tap_ix]) + @warn "Transformer winding tap setting is not on a step; converting original data ($tap_data) to nearest step ($(tap_positions[closest_tap_ix]))" + sub_data["tap"] = tap_positions[closest_tap_ix] + else + sub_data["tap"] = tap_data + end + else + sub_data["tap"] = + pop!(transformer, "WINDV1") / pop!(transformer, "WINDV2") + end 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) / diff --git a/test/test_parse_psse.jl b/test/test_parse_psse.jl index e4953f5fcb..5bd063422c 100644 --- a/test/test_parse_psse.jl +++ b/test/test_parse_psse.jl @@ -353,6 +353,17 @@ end @test IS.compare_values(original_sys, deserialized_sys) end +@testset "PSSE transformer tap position correction testing" begin + sys = build_system( + PSSEParsingTestSystems, + "transformer_correction_test_system"; + force_build = true, + ) + trf = get_component(TapTransformer, sys, "Bus 4 HV-Bus 7 ZV-i_1") + tap = get_tap(trf) + @test isapprox(tap, 0.979937; atol = 1e-6) # Test corrected value matches PSSE +end + @testset "PSSE isolated bus handling (unavailable vs topologically isolated)" begin sys = build_system(PSSEParsingTestSystems, "isolated_bus_test_system"; force_build = true) From fcc06286c7419da49dd9a1ff908a216424cfb8b4 Mon Sep 17 00:00:00 2001 From: m-bossart Date: Sun, 22 Jun 2025 09:03:09 -0700 Subject: [PATCH 02/10] additional requirement for CW --- src/parsers/pm_io/psse.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parsers/pm_io/psse.jl b/src/parsers/pm_io/psse.jl index c53c763274..f31e06134d 100644 --- a/src/parsers/pm_io/psse.jl +++ b/src/parsers/pm_io/psse.jl @@ -769,7 +769,7 @@ function _psse2pm_transformer!(pm_data::Dict, pti_data::Dict, import_all::Bool) sub_data["nomv2"] = transformer["NOMV2"] end - if abs(transformer["COD1"]) ∈ [1, 2] + if abs(transformer["COD1"]) ∈ [1, 2] && transformer["CW"] ∈ [1, 3] tap_data = pop!(transformer, "WINDV1") / pop!(transformer, "WINDV2") tap_positions = collect( range( From df67e130eec6e3a02d75253bd577c56b7ddd1736 Mon Sep 17 00:00:00 2001 From: m-bossart Date: Mon, 23 Jun 2025 14:40:16 -0700 Subject: [PATCH 03/10] fix for correcting windv1, not ratio --- src/parsers/pm_io/psse.jl | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/parsers/pm_io/psse.jl b/src/parsers/pm_io/psse.jl index f31e06134d..1cb9f25b7e 100644 --- a/src/parsers/pm_io/psse.jl +++ b/src/parsers/pm_io/psse.jl @@ -769,8 +769,8 @@ function _psse2pm_transformer!(pm_data::Dict, pti_data::Dict, import_all::Bool) sub_data["nomv2"] = transformer["NOMV2"] end + windv1 = pop!(transformer, "WINDV1") if abs(transformer["COD1"]) ∈ [1, 2] && transformer["CW"] ∈ [1, 3] - tap_data = pop!(transformer, "WINDV1") / pop!(transformer, "WINDV2") tap_positions = collect( range( transformer["RMI1"], @@ -778,17 +778,13 @@ function _psse2pm_transformer!(pm_data::Dict, pti_data::Dict, import_all::Bool) length = Int(transformer["NTP1"]), ), ) - closest_tap_ix = argmin(abs.(tap_positions .- tap_data)) - if !isapprox(tap_data, tap_positions[closest_tap_ix]) - @warn "Transformer winding tap setting is not on a step; converting original data ($tap_data) to nearest step ($(tap_positions[closest_tap_ix]))" - sub_data["tap"] = tap_positions[closest_tap_ix] - else - sub_data["tap"] = tap_data + closest_tap_ix = argmin(abs.(tap_positions .- windv1)) + if !isapprox(windv1, tap_positions[closest_tap_ix]; atol = 1e-5) + @warn "Transformer winding tap setting is not on a step; converting original data ($windv1) to nearest step ($(tap_positions[closest_tap_ix]))" + windv1 = tap_positions[closest_tap_ix] end - else - sub_data["tap"] = - pop!(transformer, "WINDV1") / pop!(transformer, "WINDV2") end + sub_data["tap"] = windv1 / pop!(transformer, "WINDV2") sub_data["shift"] = pop!(transformer, "ANG1") if transformer["CW"] != 1 # NOT "for off-nominal turns ratio in pu of winding bus base voltage" From 8c6625219596ab2afeb545391a57c89c92972aec Mon Sep 17 00:00:00 2001 From: mcllerena Date: Tue, 24 Jun 2025 13:23:13 -0600 Subject: [PATCH 04/10] add tap correction for each 3WT winding --- src/parsers/pm_io/psse.jl | 74 +++++++++++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 6 deletions(-) diff --git a/src/parsers/pm_io/psse.jl b/src/parsers/pm_io/psse.jl index 1cb9f25b7e..0a9e974159 100644 --- a/src/parsers/pm_io/psse.jl +++ b/src/parsers/pm_io/psse.jl @@ -574,6 +574,33 @@ function _psse2pm_shunt!(pm_data::Dict, pti_data::Dict, import_all::Bool) end end +function apply_tap_correction!( + windv_value, + transformer, + cod_key, + rmi_key, + rma_key, + ntp_key, + cw_value, + winding_name, +) + 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 = 1e-5) + @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) @@ -1096,17 +1123,52 @@ 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"] + windv1 / sub_data["base_voltage_primary"] sub_data["secondary_turns_ratio"] = - transformer["WINDV2"] / sub_data["base_voltage_secondary"] + windv2 / sub_data["base_voltage_secondary"] sub_data["tertiary_turns_ratio"] = - transformer["WINDV3"] / sub_data["base_voltage_tertiary"] + windv3 / sub_data["base_voltage_tertiary"] end sub_data["circuit"] = strip(transformer["CKT"]) From cddb95ed723735b34843aefb3905218be4ea864c Mon Sep 17 00:00:00 2001 From: mcllerena Date: Tue, 24 Jun 2025 13:23:29 -0600 Subject: [PATCH 05/10] add test case for 3WT tap pos correction --- test/test_parse_psse.jl | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/test_parse_psse.jl b/test/test_parse_psse.jl index 5bd063422c..bb4a9d1fc7 100644 --- a/test/test_parse_psse.jl +++ b/test/test_parse_psse.jl @@ -364,6 +364,22 @@ end @test isapprox(tap, 0.979937; atol = 1e-6) # Test corrected value matches PSSE end +@testset "PSSE transformer 3w tap position correction testing" begin + sys = build_system( + PSSEParsingTestSystems, + "case14_3wt_tap_correction"; + force_build = true, + ) + trf_3w = get_component(TapTransformer, 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) From 4f4ccdffa85bd862e5778ce2aa4fb69d37cf4082 Mon Sep 17 00:00:00 2001 From: mcllerena Date: Tue, 24 Jun 2025 13:26:14 -0600 Subject: [PATCH 06/10] fix typos when cw value is different than 1 --- src/parsers/pm_io/psse.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/parsers/pm_io/psse.jl b/src/parsers/pm_io/psse.jl index 0a9e974159..906bd27d6a 100644 --- a/src/parsers/pm_io/psse.jl +++ b/src/parsers/pm_io/psse.jl @@ -1164,11 +1164,11 @@ function _psse2pm_transformer!(pm_data::Dict, pti_data::Dict, import_all::Bool) sub_data["tertiary_turns_ratio"] = windv3 else sub_data["primary_turns_ratio"] = - windv1 / sub_data["base_voltage_primary"] + transformer["WINDV1"] / sub_data["base_voltage_primary"] sub_data["secondary_turns_ratio"] = - windv2 / sub_data["base_voltage_secondary"] + transformer["WINDV2"] / sub_data["base_voltage_secondary"] sub_data["tertiary_turns_ratio"] = - windv3 / sub_data["base_voltage_tertiary"] + transformer["WINDV3"] / sub_data["base_voltage_tertiary"] end sub_data["circuit"] = strip(transformer["CKT"]) From 176f51d3f14922b585a489d2cf5382878eabc778 Mon Sep 17 00:00:00 2001 From: mcllerena Date: Wed, 25 Jun 2025 14:21:26 -0600 Subject: [PATCH 07/10] fix test and apply tap correction function to transformer2w --- src/parsers/pm_io/psse.jl | 24 ++++++++++-------------- test/test_parse_psse.jl | 2 +- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/parsers/pm_io/psse.jl b/src/parsers/pm_io/psse.jl index 906bd27d6a..1525b32b28 100644 --- a/src/parsers/pm_io/psse.jl +++ b/src/parsers/pm_io/psse.jl @@ -797,20 +797,16 @@ function _psse2pm_transformer!(pm_data::Dict, pti_data::Dict, import_all::Bool) end windv1 = pop!(transformer, "WINDV1") - if abs(transformer["COD1"]) ∈ [1, 2] && transformer["CW"] ∈ [1, 3] - tap_positions = collect( - range( - transformer["RMI1"], - transformer["RMA1"]; - length = Int(transformer["NTP1"]), - ), - ) - closest_tap_ix = argmin(abs.(tap_positions .- windv1)) - if !isapprox(windv1, tap_positions[closest_tap_ix]; atol = 1e-5) - @warn "Transformer winding tap setting is not on a step; converting original data ($windv1) to nearest step ($(tap_positions[closest_tap_ix]))" - windv1 = tap_positions[closest_tap_ix] - end - end + 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") diff --git a/test/test_parse_psse.jl b/test/test_parse_psse.jl index bb4a9d1fc7..035f0caa2f 100644 --- a/test/test_parse_psse.jl +++ b/test/test_parse_psse.jl @@ -370,7 +370,7 @@ end "case14_3wt_tap_correction"; force_build = true, ) - trf_3w = get_component(TapTransformer, sys, "BUS 109-BUS 104-BUS 107-i_1") + 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) From 2b745fe28421eb235e60d20789401ca32878ebe6 Mon Sep 17 00:00:00 2001 From: m-bossart Date: Fri, 27 Jun 2025 12:36:43 -0700 Subject: [PATCH 08/10] move tol to definitions --- src/definitions.jl | 1 + src/parsers/pm_io/psse.jl | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) 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 1525b32b28..e1365bcdfa 100644 --- a/src/parsers/pm_io/psse.jl +++ b/src/parsers/pm_io/psse.jl @@ -593,7 +593,11 @@ function apply_tap_correction!( ), ) closest_tap_ix = argmin(abs.(tap_positions .- windv_value)) - if !isapprox(windv_value, tap_positions[closest_tap_ix]; atol = 1e-5) + 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 From 86ce3e5345fef8d492269da1c696099d08cc1ce1 Mon Sep 17 00:00:00 2001 From: m-bossart Date: Fri, 27 Jun 2025 12:36:59 -0700 Subject: [PATCH 09/10] add types --- src/parsers/pm_io/psse.jl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/parsers/pm_io/psse.jl b/src/parsers/pm_io/psse.jl index e1365bcdfa..3659dc2436 100644 --- a/src/parsers/pm_io/psse.jl +++ b/src/parsers/pm_io/psse.jl @@ -575,14 +575,14 @@ function _psse2pm_shunt!(pm_data::Dict, pti_data::Dict, import_all::Bool) end function apply_tap_correction!( - windv_value, - transformer, - cod_key, - rmi_key, - rma_key, - ntp_key, - cw_value, - winding_name, + 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( From 832981f4d9ed1fea728aa0707a2aa1ab99c8db78 Mon Sep 17 00:00:00 2001 From: m-bossart Date: Fri, 27 Jun 2025 12:37:21 -0700 Subject: [PATCH 10/10] combine 2w and 3w tests to use single system --- test/test_parse_psse.jl | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/test/test_parse_psse.jl b/test/test_parse_psse.jl index 035f0caa2f..8564c81c5b 100644 --- a/test/test_parse_psse.jl +++ b/test/test_parse_psse.jl @@ -356,25 +356,19 @@ end @testset "PSSE transformer tap position correction testing" begin sys = build_system( PSSEParsingTestSystems, - "transformer_correction_test_system"; + "psse_14_tap_correction_test_system"; force_build = true, ) - trf = get_component(TapTransformer, sys, "Bus 4 HV-Bus 7 ZV-i_1") + #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 corrected value matches PSSE -end + @test isapprox(tap, 0.979937; atol = 1e-6) -@testset "PSSE transformer 3w tap position correction testing" begin - sys = build_system( - PSSEParsingTestSystems, - "case14_3wt_tap_correction"; - force_build = true, - ) + #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)