From 67be1cb19f5e786c732b8af330673fc2682db8bb Mon Sep 17 00:00:00 2001 From: Jerry Jinfeng Guo Date: Thu, 8 Oct 2026 12:43:35 +0200 Subject: [PATCH 1/3] added boundary handling in binary search Signed-off-by: Jerry Jinfeng Guo --- .../power_grid_model/optimizer/tap_position_optimizer.hpp | 6 ++++++ .../achievable-voltage-above-band-fast-any/params.json | 4 ---- .../auto-tap-changer-meshed-fast-any-max-iter/params.json | 6 +----- .../params.json | 6 +----- .../sym_output.json | 4 ++-- 5 files changed, 10 insertions(+), 16 deletions(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/optimizer/tap_position_optimizer.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/optimizer/tap_position_optimizer.hpp index 820271a1fc..f15ae9a390 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/optimizer/tap_position_optimizer.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/optimizer/tap_position_optimizer.hpp @@ -900,6 +900,12 @@ class TapPositionOptimizerImpl, StateCalculator, bool const prefer_higher = control_at_tap_side_ != prefer_higher_; auto const primary_bound = prefer_higher ? upper_bound_ : lower_bound_; auto const secondary_bound = prefer_higher ? lower_bound_ : upper_bound_; + auto const current_at_bound = current_ == primary_bound || current_ == secondary_bound; + bool const is_one_apart = std::abs(static_cast(primary_bound) - secondary_bound) == 1; + // Handle edge case: 1 off and current already not satisfying + if (is_one_apart && current_at_bound) { + return current_ == primary_bound ? secondary_bound : primary_bound; + } return std::midpoint(primary_bound, secondary_bound); } diff --git a/tests/data/power_flow/automatic-tap-regulator/achievable-voltage-above-band-fast-any/params.json b/tests/data/power_flow/automatic-tap-regulator/achievable-voltage-above-band-fast-any/params.json index 799ff822cc..db835303c1 100644 --- a/tests/data/power_flow/automatic-tap-regulator/achievable-voltage-above-band-fast-any/params.json +++ b/tests/data/power_flow/automatic-tap-regulator/achievable-voltage-above-band-fast-any/params.json @@ -5,9 +5,5 @@ "atol": { "default": 1e-5, "u": 100.0 - }, - "xfail": { - "raises": "MaxIterationReached", - "reason": "Bug in fast any tap changing strategy in this edge case: https://github.com/PowerGridModel/power-grid-model/issues/1215" } } diff --git a/tests/data/power_flow/automatic-tap-regulator/auto-tap-changer-meshed-fast-any-max-iter/params.json b/tests/data/power_flow/automatic-tap-regulator/auto-tap-changer-meshed-fast-any-max-iter/params.json index 703a6403b4..b3f76100fa 100644 --- a/tests/data/power_flow/automatic-tap-regulator/auto-tap-changer-meshed-fast-any-max-iter/params.json +++ b/tests/data/power_flow/automatic-tap-regulator/auto-tap-changer-meshed-fast-any-max-iter/params.json @@ -2,9 +2,5 @@ "calculation_method": "newton_raphson", "tap_changing_strategy": "fast_any_tap", "rtol": 1e-5, - "atol": 1e-5, - "raises": { - "raises": "MaxIterationReached", - "reason": "TapPositionOptimizer::iterate" - } + "atol": 1e-5 } diff --git a/tests/data/power_flow/automatic-tap-regulator/auto-tap-changer-small-u-band-max-iter-fast-any/params.json b/tests/data/power_flow/automatic-tap-regulator/auto-tap-changer-small-u-band-max-iter-fast-any/params.json index d137faeb48..b3f76100fa 100644 --- a/tests/data/power_flow/automatic-tap-regulator/auto-tap-changer-small-u-band-max-iter-fast-any/params.json +++ b/tests/data/power_flow/automatic-tap-regulator/auto-tap-changer-small-u-band-max-iter-fast-any/params.json @@ -2,9 +2,5 @@ "calculation_method": "newton_raphson", "tap_changing_strategy": "fast_any_tap", "rtol": 1e-5, - "atol": 1e-5, - "raises": { - "raises": "MaxIterationReached", - "reason": "TapPositionOptimizer::binary_search" - } + "atol": 1e-5 } diff --git a/tests/data/power_flow/automatic-tap-regulator/auto-tap-changer-small-u-band-max-iter-fast-any/sym_output.json b/tests/data/power_flow/automatic-tap-regulator/auto-tap-changer-small-u-band-max-iter-fast-any/sym_output.json index 021aa4b297..f08a846aa4 100644 --- a/tests/data/power_flow/automatic-tap-regulator/auto-tap-changer-small-u-band-max-iter-fast-any/sym_output.json +++ b/tests/data/power_flow/automatic-tap-regulator/auto-tap-changer-small-u-band-max-iter-fast-any/sym_output.json @@ -6,10 +6,10 @@ "data": { "node": [ {"id": 1, "u_pu": 1, "u": 9999.9}, - {"id": 2, "u_pu": 1.02456, "u": 409.8260092982474} + {"id": 2, "u_pu": 0.9989, "u": 399.5626} ], "transformer_tap_regulator": [ - {"id": 6, "tap_pos": 2} + {"id": 6, "tap_pos": 3} ] } } \ No newline at end of file From 80eea98d0b3febc400e29a83b590124fbd26c6dd Mon Sep 17 00:00:00 2001 From: Jerry Jinfeng Guo Date: Sat, 17 Oct 2026 15:58:06 +0200 Subject: [PATCH 2/3] address comment on outdated in code comment Signed-off-by: Jerry Jinfeng Guo --- .../power_grid_model/optimizer/tap_position_optimizer.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/optimizer/tap_position_optimizer.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/optimizer/tap_position_optimizer.hpp index f15ae9a390..de93348455 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/optimizer/tap_position_optimizer.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/optimizer/tap_position_optimizer.hpp @@ -893,10 +893,12 @@ class TapPositionOptimizerImpl, StateCalculator, IntS search(bool prefer_higher_) const { // This logic is used to determine which of the middle points could be of interest // given strategy used in optimization: - // Since in BinarySearch we insist on absolute upper and lower bounds, we only need to + // In BinarySearch we rely on upper and lower bounds to // find the corresponding mid point. std::midpoint returns always the lower mid point // if the range is of even length. This is why we need to adjust bounds accordingly. // Not because upper bound and lower bound might be reversed, which is not possible. + // In the case the optimization target is beyond the range, both boundaries should be + // considered, especially in the final iteration of the binary search. bool const prefer_higher = control_at_tap_side_ != prefer_higher_; auto const primary_bound = prefer_higher ? upper_bound_ : lower_bound_; auto const secondary_bound = prefer_higher ? lower_bound_ : upper_bound_; From b15c3220d575bbfb2c36727e97d28045c0bf17a5 Mon Sep 17 00:00:00 2001 From: Jerry Jinfeng Guo Date: Sat, 17 Oct 2026 16:01:46 +0200 Subject: [PATCH 3/3] minor adition Signed-off-by: Jerry Jinfeng Guo --- .../power_grid_model/optimizer/tap_position_optimizer.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/optimizer/tap_position_optimizer.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/optimizer/tap_position_optimizer.hpp index de93348455..6629a218e7 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/optimizer/tap_position_optimizer.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/optimizer/tap_position_optimizer.hpp @@ -893,8 +893,8 @@ class TapPositionOptimizerImpl, StateCalculator, IntS search(bool prefer_higher_) const { // This logic is used to determine which of the middle points could be of interest // given strategy used in optimization: - // In BinarySearch we rely on upper and lower bounds to - // find the corresponding mid point. std::midpoint returns always the lower mid point + // In BinarySearch we rely on upper and lower bounds to find the corresponding mid point. + // std::midpoint returns always the relative 'lower' mid point // if the range is of even length. This is why we need to adjust bounds accordingly. // Not because upper bound and lower bound might be reversed, which is not possible. // In the case the optimization target is beyond the range, both boundaries should be