From 26605905f7e643c11eb8075764fb3aa755de905a Mon Sep 17 00:00:00 2001 From: Santiago Figueroa Manrique Date: Fri, 21 Aug 2026 12:29:02 +0200 Subject: [PATCH 01/23] link output initial implementation Signed-off-by: Santiago Figueroa Manrique --- .../power_grid_model/main_core/output.hpp | 45 ++++++++++++++ .../main_core/test_main_core_output.cpp | 58 +++++++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp index 34b33cc13f..a3da2f3d94 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp @@ -84,8 +84,31 @@ constexpr auto output_result(Component const& node, MainModelState Component, class ComponentContainer, steady_state_solver_output_type SolverOutputType> + requires model_component_state_c +constexpr auto output_result(Component const& link, MainModelState const& /* state */, + MathOutput> const& math_output, Idx2D const& topo_id) { + using sym = decode_symmetry_v; + + if (!link.branch_status() || topo_id.group == disconnected) { + return link.template get_null_output(); + } + return link.template get_output(math_output.supernode_output[topo_id.group].link[topo_id.pos]); +} +template Component, class ComponentContainer, short_circuit_solver_output_type SolverOutputType> + requires model_component_state_c +inline auto output_result(Component const& link, MainModelState const& /* state */, + MathOutput> const& math_output, Idx2D const& topo_id) { + if (!link.branch_status() || topo_id.group == disconnected) { + return link.get_null_sc_output(); + } + return link.get_sc_output(math_output.supernode_output[topo_id.group].link[topo_id.pos]); +} + // output branch template Component, steady_state_solver_output_type SolverOutputType> + requires(!std::same_as) constexpr auto output_result(Component const& branch, std::vector const& solver_output, Idx2D math_id) { using sym = decode_symmetry_v; @@ -96,6 +119,7 @@ constexpr auto output_result(Component const& branch, std::vector(solver_output[math_id.group].branch[math_id.pos]); } template Component, short_circuit_solver_output_type SolverOutputType> + requires(!std::same_as) inline auto output_result(Component const& branch, std::vector const& solver_output, Idx2D math_id) { if (math_id.group == disconnected) { return branch.get_null_sc_output(); @@ -429,6 +453,27 @@ constexpr auto output_result(Component const& voltage_regulator, MainModelState< return voltage_regulator.get_null_sc_output(); } +template Component, class ComponentContainer, solver_output_type SolverOutputType, + non_owning_view_c ComponentOutput> + requires model_component_state_c +constexpr void output_result(MainModelState const& state, + MathOutput> const& math_output, ComponentOutput output) { + auto const& link_topo_ids = state.reduced_topology->topo_node_coup.coupling.user_links_to_topo_nodes; + if (std::ranges::ssize(link_topo_ids) == get_component_size(state.components)) { + std::ranges::transform( + get_component_citer(state.components), link_topo_ids, std::ranges::begin(output), + [&state, &math_output](Component const& link, Idx2D const& topo_id) { + return output_result(link, state, math_output, topo_id); + }); + return; + } + + detail::produce_output(state, output, + [&math_output](Component const& link, Idx2D const& math_id) { + return output_result(link, math_output.solver_output, math_id); + }); +} + // output base component template Component, class ComponentContainer, solver_output_type SolverOutputType, non_owning_view_c ComponentOutput> diff --git a/tests/cpp_unit_tests/main_core/test_main_core_output.cpp b/tests/cpp_unit_tests/main_core/test_main_core_output.cpp index be4564b24e..f6d35d46c3 100644 --- a/tests/cpp_unit_tests/main_core/test_main_core_output.cpp +++ b/tests/cpp_unit_tests/main_core/test_main_core_output.cpp @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include #include @@ -25,6 +27,62 @@ namespace power_grid_model::main_core { TEST_CASE("Test main core output") { + SUBCASE("Link") { + using ComponentContainer = Container, Link>; + using State = MainModelState; + + State state; + emplace_component(state.components, 0, LinkInput{.id = 0, .from_status = IntS{1}, .to_status = IntS{1}}, + 10e3, 20e3); + emplace_component(state.components, 1, + LinkInput{.id = 1, .from_status = IntS{1}, .to_status = status_off}, 10e3, 20e3); + emplace_component(state.components, 2, + LinkInput{.id = 2, .from_status = status_off, .to_status = status_off}, 10e3, 20e3); + state.components.set_construction_complete(); + + auto reduced_topology = std::make_shared(); + reduced_topology->topo_node_coup.coupling.user_links_to_topo_nodes = { + {.group = 0, .pos = 0}, {.group = 0, .pos = 1}, {.group = disconnected, .pos = disconnected}}; + state.reduced_topology = std::make_shared(std::move(*reduced_topology)); + + SUBCASE("Steady state output") { + MathOutput>> const math_output{ + .supernode_output = { + {.bus_injection = {}, + .link = {{.s_f = {1.0, 2.0}, .s_t = {-1.0, -2.0}}, {.s_f = {3.0, 4.0}, .s_t = {-3.0, -4.0}}}}}}; + std::vector output(3); + + output_result(state, math_output, output); + + CHECK(output[0].id == 0); + CHECK(output[0].energized == IntS{1}); + CHECK(output[0].p_from == doctest::Approx(base_power_3p)); + CHECK(output[0].q_from == doctest::Approx(2.0 * base_power_3p)); + CHECK(output[1].id == 1); + CHECK(output[1].energized == status_off); + CHECK(output[2].id == 2); + CHECK(output[2].energized == status_off); + } + + SUBCASE("Short circuit output") { + MathOutput>> const math_output{ + .supernode_output = { + {.link = {{.i_f = {1.0, 0.0}, .i_t = {-2.0, 0.0}}, {.i_f = {3.0, 0.0}, .i_t = {-4.0, 0.0}}}}}}; + std::vector output(3); + + output_result(state, math_output, output); + + CHECK(output[0].id == 0); + CHECK(output[0].energized == IntS{1}); + CHECK(output[0].i_from(0) == doctest::Approx(base_power_3p / 10e3 / sqrt3)); + CHECK(output[0].i_to(0) == doctest::Approx(2.0 * base_power_3p / 20e3 / sqrt3)); + CHECK(output[1].id == 1); + CHECK(output[1].energized == status_off); + CHECK(output[2].id == 2); + CHECK(output[2].energized == status_off); + } + } + SUBCASE("TransformerTapRegulator") { using ComponentContainer = Container, TransformerTapRegulator>; using State = MainModelState; From c90f79a9ae970808df15151e22981d40d5296357 Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Mon, 24 Aug 2026 14:45:38 +0200 Subject: [PATCH 02/23] cache state Signed-off-by: Martijn Govers --- .../include/power_grid_model/main_core/output.hpp | 4 +++- tests/cpp_unit_tests/main_core/test_main_core_output.cpp | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp index a3da2f3d94..1103806407 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp @@ -459,7 +459,9 @@ template Component, class ComponentContainer, solver_output_ constexpr void output_result(MainModelState const& state, MathOutput> const& math_output, ComponentOutput output) { auto const& link_topo_ids = state.reduced_topology->topo_node_coup.coupling.user_links_to_topo_nodes; - if (std::ranges::ssize(link_topo_ids) == get_component_size(state.components)) { + if (std::ranges::ssize(link_topo_ids) == + get_component_size( + state.components)) { // TODO(mgovers): cleanup v2: this should be the only code path remaining std::ranges::transform( get_component_citer(state.components), link_topo_ids, std::ranges::begin(output), [&state, &math_output](Component const& link, Idx2D const& topo_id) { diff --git a/tests/cpp_unit_tests/main_core/test_main_core_output.cpp b/tests/cpp_unit_tests/main_core/test_main_core_output.cpp index f6d35d46c3..e1e8ac661d 100644 --- a/tests/cpp_unit_tests/main_core/test_main_core_output.cpp +++ b/tests/cpp_unit_tests/main_core/test_main_core_output.cpp @@ -49,7 +49,8 @@ TEST_CASE("Test main core output") { MathOutput>> const math_output{ .supernode_output = { {.bus_injection = {}, - .link = {{.s_f = {1.0, 2.0}, .s_t = {-1.0, -2.0}}, {.s_f = {3.0, 4.0}, .s_t = {-3.0, -4.0}}}}}}; + .link = {{.s_f = {1.0, 2.0}, .s_t = {-1.0, -1.5}, .i_f = {3.0, 4.0}, .i_t = {-3.0, -4.0}}, + {.s_f = {3.0, 4.0}, .s_t = {-3.0, -4.0}, .i_f = {5.0, 6.0}, .i_t = {-5.0, -5.5}}}}}}; std::vector output(3); output_result(state, math_output, output); @@ -58,6 +59,10 @@ TEST_CASE("Test main core output") { CHECK(output[0].energized == IntS{1}); CHECK(output[0].p_from == doctest::Approx(base_power_3p)); CHECK(output[0].q_from == doctest::Approx(2.0 * base_power_3p)); + CHECK(output[0].p_to == doctest::Approx(-1.0 * base_power_3p)); + CHECK(output[0].q_to == doctest::Approx(-1.5 * base_power_3p)); + CHECK(output[0].i_from == doctest::Approx(5.0 * base_power_3p / 10e3 / sqrt3)); + CHECK(output[0].i_to == doctest::Approx(5.0 * base_power_3p / 20e3 / sqrt3)); CHECK(output[1].id == 1); CHECK(output[1].energized == status_off); CHECK(output[2].id == 2); From 71a9bd8b884d378fb9f81931b3670675064d351a Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Tue, 25 Aug 2026 10:13:36 +0200 Subject: [PATCH 03/23] fix clang Signed-off-by: Martijn Govers --- tests/cpp_unit_tests/main_core/test_main_core_output.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/cpp_unit_tests/main_core/test_main_core_output.cpp b/tests/cpp_unit_tests/main_core/test_main_core_output.cpp index e1e8ac661d..ae8eecbf46 100644 --- a/tests/cpp_unit_tests/main_core/test_main_core_output.cpp +++ b/tests/cpp_unit_tests/main_core/test_main_core_output.cpp @@ -47,6 +47,8 @@ TEST_CASE("Test main core output") { SUBCASE("Steady state output") { MathOutput>> const math_output{ + .solver_output = {}, + .optimizer_output = {}, .supernode_output = { {.bus_injection = {}, .link = {{.s_f = {1.0, 2.0}, .s_t = {-1.0, -1.5}, .i_f = {3.0, 4.0}, .i_t = {-3.0, -4.0}}, @@ -71,6 +73,8 @@ TEST_CASE("Test main core output") { SUBCASE("Short circuit output") { MathOutput>> const math_output{ + .solver_output = {}, + .optimizer_output = {}, .supernode_output = { {.link = {{.i_f = {1.0, 0.0}, .i_t = {-2.0, 0.0}}, {.i_f = {3.0, 0.0}, .i_t = {-4.0, 0.0}}}}}}; std::vector output(3); From 0f5c411d8f0c0ff4c04a8977490c27554e62eb42 Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Tue, 25 Aug 2026 11:07:30 +0200 Subject: [PATCH 04/23] sonar-cloud Signed-off-by: Martijn Govers --- .../include/power_grid_model/main_core/output.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp index 1103806407..14c1b0a2bc 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp @@ -458,8 +458,8 @@ template Component, class ComponentContainer, solver_output_ requires model_component_state_c constexpr void output_result(MainModelState const& state, MathOutput> const& math_output, ComponentOutput output) { - auto const& link_topo_ids = state.reduced_topology->topo_node_coup.coupling.user_links_to_topo_nodes; - if (std::ranges::ssize(link_topo_ids) == + if (auto const& link_topo_ids = state.reduced_topology->topo_node_coup.coupling.user_links_to_topo_nodes; + std::ranges::ssize(link_topo_ids) == get_component_size( state.components)) { // TODO(mgovers): cleanup v2: this should be the only code path remaining std::ranges::transform( From 5233fc755f66b4e1cf0d3ce5414ec543b2028f2c Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Tue, 25 Aug 2026 11:09:54 +0200 Subject: [PATCH 05/23] make code paths clearer Signed-off-by: Martijn Govers --- .../include/power_grid_model/main_core/output.hpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp index 14c1b0a2bc..c8618ef09e 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp @@ -467,13 +467,12 @@ constexpr void output_result(MainModelState const& state, [&state, &math_output](Component const& link, Idx2D const& topo_id) { return output_result(link, state, math_output, topo_id); }); - return; + } else { + detail::produce_output( + state, output, [&math_output](Component const& link, Idx2D const& math_id) { + return output_result(link, math_output.solver_output, math_id); + }); } - - detail::produce_output(state, output, - [&math_output](Component const& link, Idx2D const& math_id) { - return output_result(link, math_output.solver_output, math_id); - }); } // output base component From 6ce6e36a6dc0fd72b1c9f58ff5d877d86ef99bda Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Wed, 26 Aug 2026 10:36:28 +0200 Subject: [PATCH 06/23] fix compilation Signed-off-by: Martijn Govers --- .../include/power_grid_model/main_core/output.hpp | 13 ++++++++----- .../main_core/test_main_core_output.cpp | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp index 6e4bcb5133..1eb87644b4 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp @@ -135,7 +135,7 @@ constexpr auto output_result(Component const& link, MainModelState> const& math_output, Idx2D const& topo_id) { using sym = decode_symmetry_v; - if (!link.branch_status() || topo_id.group == disconnected) { + if (!link.edge_status() || topo_id.group == disconnected) { return link.template get_null_output(); } return link.template get_output(math_output.supernode_output[topo_id.group].link[topo_id.pos]); @@ -144,14 +144,15 @@ template Component, class ComponentContainer, short_circuit_ requires model_component_state_c inline auto output_result(Component const& link, MainModelState const& /* state */, MathOutput> const& math_output, Idx2D const& topo_id) { - if (!link.branch_status() || topo_id.group == disconnected) { + if (!link.edge_status() || topo_id.group == disconnected) { return link.get_null_sc_output(); } return link.get_sc_output(math_output.supernode_output[topo_id.group].link[topo_id.pos]); } // output branch -template Component, steady_state_solver_output_type SolverOutputType> +template Component, steady_state_solver_output_type SolverOutputType> + requires(!std::same_as) // TODO(mgovers): cleanup v2: change back to only derived_from constexpr auto output_result(Component const& branch, std::vector const& solver_output, Idx2D math_id) { using sym = decode_symmetry_v; @@ -161,7 +162,9 @@ constexpr auto output_result(Component const& branch, std::vector(solver_output[math_id.group].branch[math_id.pos]); } -template Component, short_circuit_solver_output_type SolverOutputType> +// TODO(mgovers): cleanup v2: change back to only derived_from +template Component, short_circuit_solver_output_type SolverOutputType> + requires(!std::same_as) // TODO(mgovers): cleanup v2: change back to only derived_from inline auto output_result(Component const& branch, std::vector const& solver_output, Idx2D math_id) { if (math_id.group == disconnected) { return branch.get_null_sc_output(); @@ -512,7 +515,7 @@ constexpr void output_result(MainModelState const& state, } else { detail::produce_output( state, output, [&math_output](Component const& link, Idx2D const& math_id) { - return output_result(link, math_output.solver_output, math_id); + return output_result(link, math_output.solver_output, math_id); }); } } diff --git a/tests/cpp_unit_tests/main_core/test_main_core_output.cpp b/tests/cpp_unit_tests/main_core/test_main_core_output.cpp index 247f6b3740..32e9928b88 100644 --- a/tests/cpp_unit_tests/main_core/test_main_core_output.cpp +++ b/tests/cpp_unit_tests/main_core/test_main_core_output.cpp @@ -224,7 +224,7 @@ template void check_null_current_sensor_output(CurrentSensorO TEST_CASE("Test main core output") { SUBCASE("Link") { - using ComponentContainer = Container, Link>; + using ComponentContainer = Container, Link>; using State = MainModelState; State state; From cb4c1edaaf7b8cdf00dc85e1c3a0c6ca568e8224 Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Thu, 27 Aug 2026 08:09:45 +0200 Subject: [PATCH 07/23] deduce contributing types instead of exhaustive list Signed-off-by: Martijn Govers --- .../include/power_grid_model/container.hpp | 1 + .../main_core/topological_node_output.hpp | 30 +++++++++++----- .../test_topological_node_output.cpp | 34 ++++++++++++++++--- 3 files changed, 51 insertions(+), 14 deletions(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/container.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/container.hpp index 4e280e2890..0d87415062 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/container.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/container.hpp @@ -68,6 +68,7 @@ template class Container; template class Container, StorageableTypes...> { public: + using storageable_types = std::tuple; using gettable_types = std::tuple; static constexpr size_t num_storageable = sizeof...(StorageableTypes); diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topological_node_output.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topological_node_output.hpp index bc060310ae..b8a2997569 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topological_node_output.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topological_node_output.hpp @@ -144,15 +144,30 @@ struct AddApplianceInjection { } }; +struct user_node_contribution_tag_t {}; +struct ContributesToSteadyStateUserNodeInjection : public user_node_contribution_tag_t { + template + static constexpr bool value = + std::derived_from || std::derived_from || + std::derived_from; +}; +struct ContributesToShortCircuitUserNodeInjection : public user_node_contribution_tag_t { + template + static constexpr bool value = std::derived_from || + std::derived_from || std::derived_from; +}; + constexpr auto add_appliance_injection = AddApplianceInjection{}; -template +template ContributesToUserNodeInjection, main_model_state_c State, + solver_output_type SolverOutput, typename AddToTarget> inline void add_flows(State const& state, MathOutput> const& math_output, AddToTarget accumulate_injection) { - utils::run_functor_with_tuple_return_void( + using Container = decltype(state.components); + + utils::run_functor_with_tuple_return_void( [&state, &math_output, &accumulate_injection]() { - if constexpr (decltype(state.components)::template is_storageable_v) { + if constexpr (ContributesToUserNodeInjection::template value) { add_appliance_injection.template operator()(state, math_output, accumulate_injection); } }); @@ -240,12 +255,9 @@ solve_topological_nodes(LinkSolver link_solver, State const& state, }; if constexpr (steady_state_solver_output_type) { - using InjectionComponentTypesTuple = - std::tuple; - add_flows(state, math_output, accumulate_injection); + add_flows(state, math_output, accumulate_injection); } else if constexpr (short_circuit_solver_output_type) { - using InjectionComponentTypesTuple = std::tuple; - add_flows(state, math_output, accumulate_injection); + add_flows(state, math_output, accumulate_injection); } auto result = diff --git a/tests/cpp_unit_tests/main_core/test_topological_node_output.cpp b/tests/cpp_unit_tests/main_core/test_topological_node_output.cpp index b81337043e..717f7da742 100644 --- a/tests/cpp_unit_tests/main_core/test_topological_node_output.cpp +++ b/tests/cpp_unit_tests/main_core/test_topological_node_output.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +45,30 @@ using ComponentContainer = Container; using State = MainModelState; +static_assert(detail::ContributesToSteadyStateUserNodeInjection::template value); +static_assert(detail::ContributesToSteadyStateUserNodeInjection::template value); +static_assert(detail::ContributesToSteadyStateUserNodeInjection::template value); +static_assert(detail::ContributesToSteadyStateUserNodeInjection::template value); +static_assert(detail::ContributesToSteadyStateUserNodeInjection::template value); +static_assert(detail::ContributesToSteadyStateUserNodeInjection::template value); +static_assert(detail::ContributesToSteadyStateUserNodeInjection::template value); +static_assert(detail::ContributesToSteadyStateUserNodeInjection::template value); +static_assert(detail::ContributesToSteadyStateUserNodeInjection::template value); +static_assert(!detail::ContributesToSteadyStateUserNodeInjection::template value); +static_assert(!detail::ContributesToSteadyStateUserNodeInjection::template value); + +static_assert(detail::ContributesToShortCircuitUserNodeInjection::template value); +static_assert(detail::ContributesToShortCircuitUserNodeInjection::template value); +static_assert(detail::ContributesToShortCircuitUserNodeInjection::template value); +static_assert(detail::ContributesToShortCircuitUserNodeInjection::template value); +static_assert(detail::ContributesToShortCircuitUserNodeInjection::template value); +static_assert(detail::ContributesToShortCircuitUserNodeInjection::template value); +static_assert(!detail::ContributesToShortCircuitUserNodeInjection::template value); +static_assert(!detail::ContributesToShortCircuitUserNodeInjection::template value); +static_assert(!detail::ContributesToShortCircuitUserNodeInjection::template value); +static_assert(!detail::ContributesToShortCircuitUserNodeInjection::template value); +static_assert(!detail::ContributesToShortCircuitUserNodeInjection::template value); + double constexpr dummy_value = 123.321; constexpr ComplexValue dummy_complex_value_sym() { return {2.14, 3.71}; } ComplexValue dummy_complex_value_asym() { return {{1.0, 2.0}, {-3.0, -4.0}, {5.0, -6.0}}; } @@ -358,10 +383,9 @@ TEST_CASE("Test topological node output") { SUBCASE("Steady state output") { auto const math_output = make_steady_state_math_output_sym(); - using ComponentTypes = - std::tuple; - detail::add_flows(state, math_output, accumulator.accumulator()); + detail::add_flows(state, math_output, + accumulator.accumulator()); CHECK(accumulator.net_node_injections.size() == 3); CHECK(accumulator.net_node_injections.at(Idx2D{.group = 0, .pos = 0}) == dummy_complex_value_sym()); @@ -376,9 +400,9 @@ TEST_CASE("Test topological node output") { SUBCASE("Short circuit output") { auto const math_output = make_short_circuit_math_output_sym(); - using ComponentTypes = std::tuple; - detail::add_flows(state, math_output, accumulator.accumulator()); + detail::add_flows(state, math_output, + accumulator.accumulator()); CHECK(accumulator.net_node_injections.size() == 2); CHECK(accumulator.net_node_injections.at(Idx2D{.group = 0, .pos = 0}) == 2.0 * dummy_complex_value_sym()); From 184252247e68eaf04f049115645aceb938b9713e Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Thu, 27 Aug 2026 15:33:03 +0200 Subject: [PATCH 08/23] topological_node_output with shunt and branch3 Signed-off-by: Martijn Govers --- .../main_core/topological_node_output.hpp | 23 ++++++++++--------- .../test_topological_node_output.cpp | 8 ++++--- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topological_node_output.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topological_node_output.hpp index b8a2997569..7b99115df8 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topological_node_output.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topological_node_output.hpp @@ -22,12 +22,12 @@ namespace detail { template struct SuperNodeSolverInput { std::span links; ComplexValueVector node_injection; - ComplexValueVector node_flow_from_branch; + ComplexValueVector node_flow_through_grid; ComplexValueVector get_total_injection_per_node() const { - assert(node_injection.size() == node_flow_from_branch.size()); + assert(node_injection.size() == node_flow_through_grid.size()); - return std::views::zip(node_injection, node_flow_from_branch) | std::views::transform([](auto const& pair) { + return std::views::zip(node_injection, node_flow_through_grid) | std::views::transform([](auto const& pair) { auto const& [node_inj, branch_flow] = pair; return ComplexValue(node_inj + branch_flow); }) | @@ -147,14 +147,14 @@ struct AddApplianceInjection { struct user_node_contribution_tag_t {}; struct ContributesToSteadyStateUserNodeInjection : public user_node_contribution_tag_t { template - static constexpr bool value = - std::derived_from || std::derived_from || - std::derived_from; + static constexpr bool value = std::derived_from || + std::derived_from || std::derived_from; }; struct ContributesToShortCircuitUserNodeInjection : public user_node_contribution_tag_t { template - static constexpr bool value = std::derived_from || - std::derived_from || std::derived_from; + static constexpr bool value = std::derived_from || std::derived_from || + std::derived_from || + std::derived_from || std::derived_from; }; constexpr auto add_appliance_injection = AddApplianceInjection{}; @@ -241,14 +241,15 @@ solve_topological_nodes(LinkSolver link_solver, State const& state, auto const node_number = topo_node.user_nodes.size(); return {.links = std::span{topo_node.user_links}, .node_injection = ComplexValueVector(node_number), - .node_flow_from_branch = ComplexValueVector(node_number)}; + .node_flow_through_grid = ComplexValueVector(node_number)}; }) | std::ranges::to(); auto const accumulate_injection = [&link_solver_input](Idx2D const& user_topo_id, ComplexValue const& injection) { - if constexpr (std::derived_from) { - link_solver_input[user_topo_id.group].node_flow_from_branch[user_topo_id.pos] += injection; + if constexpr (std::derived_from || std::derived_from || + std::derived_from) { + link_solver_input[user_topo_id.group].node_flow_through_grid[user_topo_id.pos] += injection; } else { link_solver_input[user_topo_id.group].node_injection[user_topo_id.pos] += injection; } diff --git a/tests/cpp_unit_tests/main_core/test_topological_node_output.cpp b/tests/cpp_unit_tests/main_core/test_topological_node_output.cpp index 717f7da742..5505ae6312 100644 --- a/tests/cpp_unit_tests/main_core/test_topological_node_output.cpp +++ b/tests/cpp_unit_tests/main_core/test_topological_node_output.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -34,7 +35,6 @@ #include #include #include -#include #include #include #include @@ -54,7 +54,8 @@ static_assert(detail::ContributesToSteadyStateUserNodeInjection::template value< static_assert(detail::ContributesToSteadyStateUserNodeInjection::template value); static_assert(detail::ContributesToSteadyStateUserNodeInjection::template value); static_assert(detail::ContributesToSteadyStateUserNodeInjection::template value); -static_assert(!detail::ContributesToSteadyStateUserNodeInjection::template value); +static_assert(detail::ContributesToSteadyStateUserNodeInjection::template value); +static_assert(detail::ContributesToSteadyStateUserNodeInjection::template value); static_assert(!detail::ContributesToSteadyStateUserNodeInjection::template value); static_assert(detail::ContributesToShortCircuitUserNodeInjection::template value); @@ -63,7 +64,8 @@ static_assert(detail::ContributesToShortCircuitUserNodeInjection::template value static_assert(detail::ContributesToShortCircuitUserNodeInjection::template value); static_assert(detail::ContributesToShortCircuitUserNodeInjection::template value); static_assert(detail::ContributesToShortCircuitUserNodeInjection::template value); -static_assert(!detail::ContributesToShortCircuitUserNodeInjection::template value); +static_assert(detail::ContributesToShortCircuitUserNodeInjection::template value); +static_assert(detail::ContributesToShortCircuitUserNodeInjection::template value); static_assert(!detail::ContributesToShortCircuitUserNodeInjection::template value); static_assert(!detail::ContributesToShortCircuitUserNodeInjection::template value); static_assert(!detail::ContributesToShortCircuitUserNodeInjection::template value); From 48241fdf6b72b47d6d9d14f6984a3d9431b914c4 Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Thu, 27 Aug 2026 15:36:49 +0200 Subject: [PATCH 09/23] fix compilation part 1 Signed-off-by: Martijn Govers --- .../main_core/test_topological_node_output.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/cpp_unit_tests/main_core/test_topological_node_output.cpp b/tests/cpp_unit_tests/main_core/test_topological_node_output.cpp index 5505ae6312..f2ab18ab2e 100644 --- a/tests/cpp_unit_tests/main_core/test_topological_node_output.cpp +++ b/tests/cpp_unit_tests/main_core/test_topological_node_output.cpp @@ -424,8 +424,8 @@ TEST_CASE("Test topological node output") { detail::SuperNodeSolverInput const input{ .links = links, .node_injection = {dummy_complex_value_sym(), dummy_complex_value_sym(), dummy_complex_value_sym()}, - .node_flow_from_branch = {dummy_complex_value_sym(), dummy_complex_value_sym(), - dummy_complex_value_sym()}}; + .node_flow_through_grid = {dummy_complex_value_sym(), dummy_complex_value_sym(), + dummy_complex_value_sym()}}; auto const total = input.get_total_injection_per_node(); REQUIRE(total.size() == 3); @@ -436,8 +436,8 @@ TEST_CASE("Test topological node output") { detail::SuperNodeSolverInput const input{ .links = links, .node_injection = {dummy_complex_value_asym(), dummy_complex_value_asym(), dummy_complex_value_asym()}, - .node_flow_from_branch = {dummy_complex_value_asym(), dummy_complex_value_asym(), - dummy_complex_value_asym()}}; + .node_flow_through_grid = {dummy_complex_value_asym(), dummy_complex_value_asym(), + dummy_complex_value_asym()}}; auto const total = input.get_total_injection_per_node(); REQUIRE(total.size() == 3); @@ -453,8 +453,8 @@ TEST_CASE("Test topological node output") { detail::SuperNodeSolverInput const input{ .links = links, .node_injection = {dummy_complex_value_sym(), DoubleComplex{}, 2.0 * dummy_complex_value_sym()}, - .node_flow_from_branch = {DoubleComplex{}, 3.0 * dummy_complex_value_sym(), - -dummy_complex_value_sym()}}; + .node_flow_through_grid = {DoubleComplex{}, 3.0 * dummy_complex_value_sym(), + -dummy_complex_value_sym()}}; LinkSolverMock mock{.return_values = { {2.0 * dummy_complex_value_sym(), -dummy_complex_value_sym()}, @@ -476,8 +476,8 @@ TEST_CASE("Test topological node output") { .links = links, .node_injection = {dummy_complex_value_asym(), ComplexValue{}, 2.0 * dummy_complex_value_asym()}, - .node_flow_from_branch = {ComplexValue{}, 3.0 * dummy_complex_value_asym(), - -dummy_complex_value_asym()}}; + .node_flow_through_grid = {ComplexValue{}, 3.0 * dummy_complex_value_asym(), + -dummy_complex_value_asym()}}; LinkSolverMock mock{.return_values = {{dummy_complex_value_asym()(0), 2.0 * dummy_complex_value_asym()(0)}, {-dummy_complex_value_asym()(1), 3.0 * dummy_complex_value_asym()(1)}, From 865c8e39c094509aacefc49aef65ec420ff8aeab Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Thu, 27 Aug 2026 16:51:55 +0200 Subject: [PATCH 10/23] add shunt + 3w transformer to topo node ouptut Signed-off-by: Martijn Govers --- .../main_core/topological_node_output.hpp | 44 +++++- .../test_topological_node_output.cpp | 138 ++++++++++++++++-- 2 files changed, 165 insertions(+), 17 deletions(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topological_node_output.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topological_node_output.hpp index 7b99115df8..f7f749add5 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topological_node_output.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topological_node_output.hpp @@ -85,6 +85,9 @@ inline Idx get_node_sequence_idx(main_model_state_c auto const& state, Idx compo return state.comp_topo ->load_gen_node_idx[get_component_sequence_offset(state.components) + component_idx]; + } else if constexpr (std::derived_from) { + return state.comp_topo + ->shunt_node_idx[get_component_sequence_offset(state.components) + component_idx]; } else if constexpr (std::same_as) { auto const& fault = get_component_by_sequence(state.components, component_idx); return get_component_sequence_idx(state.components, fault.get_fault_object()); @@ -99,10 +102,16 @@ inline BranchIdx const& get_branch_sequence_idx(main_model_state_c auto const& s ->branch_node_idx[get_component_sequence_offset(state.components) + component_idx]; } +template ComponentType> +inline Branch3Idx const& get_branch3_sequence_idx(main_model_state_c auto const& state, Idx component_idx) { + return state.comp_topo + ->branch3_node_idx[get_component_sequence_offset(state.components) + component_idx]; +} + struct AddApplianceInjection { template requires flow_accumulator_c && - (is_in_list_c || + (std::derived_from || (std::same_as && short_circuit_solver_output_type)) void operator()(main_model_state_c auto const& state, MathOutput> const& math_output, AddToTarget accumulate_injection) const { @@ -133,8 +142,8 @@ struct AddApplianceInjection { auto const& component_output = get_component_output(math_output, component_math_id); auto const& branch_node_idx = get_branch_sequence_idx(state, component_idx); - for (auto const side : {BranchSide::from, BranchSide::to}) { - auto const& user_node_idx = branch_node_idx[std::to_underlying(side)]; + for (auto&& [side, user_node_idx] : + std::views::zip(std::array{BranchSide::from, BranchSide::to}, branch_node_idx)) { auto const& user_topo_id = state.reduced_topology->topo_node_coup.coupling.user_nodes_to_topo_nodes[user_node_idx]; accumulate_injection.template operator()(user_topo_id, @@ -142,8 +151,35 @@ struct AddApplianceInjection { } } } + + template + requires flow_accumulator_c && + std::derived_from + void operator()(main_model_state_c auto const& state, MathOutput> const& math_output, + AddToTarget accumulate_injection) const { + for (auto const& [component_idx, component_math_id] : enumerate(comp_base_sequence(state))) { + if (component_math_id.group == disconnected) { + continue; + } + + auto const& branch3_node_idx = get_branch3_sequence_idx(state, component_idx); + for (auto&& [side_pos, user_node_idx] : std::views::zip(component_math_id.pos, branch3_node_idx)) { + if (side_pos == disconnected) { + continue; + } + auto const& component_output = + get_component_output(math_output, {component_math_id.group, side_pos}); + auto const& user_topo_id = + state.reduced_topology->topo_node_coup.coupling.user_nodes_to_topo_nodes[user_node_idx]; + accumulate_injection.template operator()( + user_topo_id, get_injection(component_output, BranchSide::from)); + } + } + } }; +constexpr auto add_appliance_injection = AddApplianceInjection{}; + struct user_node_contribution_tag_t {}; struct ContributesToSteadyStateUserNodeInjection : public user_node_contribution_tag_t { template @@ -157,8 +193,6 @@ struct ContributesToShortCircuitUserNodeInjection : public user_node_contributio std::derived_from || std::derived_from; }; -constexpr auto add_appliance_injection = AddApplianceInjection{}; - template ContributesToUserNodeInjection, main_model_state_c State, solver_output_type SolverOutput, typename AddToTarget> inline void add_flows(State const& state, MathOutput> const& math_output, diff --git a/tests/cpp_unit_tests/main_core/test_topological_node_output.cpp b/tests/cpp_unit_tests/main_core/test_topological_node_output.cpp index f2ab18ab2e..816f9d3db3 100644 --- a/tests/cpp_unit_tests/main_core/test_topological_node_output.cpp +++ b/tests/cpp_unit_tests/main_core/test_topological_node_output.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -41,8 +42,8 @@ namespace power_grid_model::main_core { namespace { -using ComponentContainer = Container, AsymLoad, - SymLoad, Fault, Line, Node, Source, Shunt>; +using ComponentContainer = Container, + AsymLoad, SymLoad, Fault, Line, Node, Source, Shunt, ThreeWindingTransformer>; using State = MainModelState; static_assert(detail::ContributesToSteadyStateUserNodeInjection::template value); @@ -80,6 +81,41 @@ void check_close(ComplexValue const& x, ComplexValue c CHECK(x.imag() == doctest::Approx(y.imag())); } +inline ThreeWindingTransformerInput make_three_winding_transformer_input() { + return {.id = 11, + .node_1 = 101, + .node_2 = 102, + .node_3 = 103, + .status_1 = 1, + .status_2 = 1, + .status_3 = 1, + .u1 = 10e3, + .u2 = 10e3, + .u3 = 10e3, + .sn_1 = 10e6, + .sn_2 = 10e6, + .sn_3 = 10e6, + .uk_12 = 0.1, + .uk_13 = 0.1, + .uk_23 = 0.1, + .pk_12 = 10e3, + .pk_13 = 10e3, + .pk_23 = 10e3, + .i0 = 0.0, + .p0 = 0.0, + .winding_1 = WindingType::wye_n, + .winding_2 = WindingType::wye_n, + .winding_3 = WindingType::wye_n, + .clock_12 = 0, + .clock_13 = 0, + .tap_side = Branch3Side::side_1, + .tap_pos = 0, + .tap_min = 0, + .tap_max = 0, + .tap_nom = 0, + .tap_size = 0.0}; +} + inline State make_state() { State state; state.comp_topo = std::make_shared([]() { @@ -89,6 +125,7 @@ inline State make_state() { comp_topo.shunt_node_idx = {Idx{1}}; comp_topo.load_gen_node_idx = {Idx{1}, Idx{2}}; comp_topo.branch_node_idx = {{Idx{0}, Idx{1}}, {Idx{1}, Idx{1}}, {Idx{1}, Idx{0}}, {Idx{2}, Idx{3}}}; + comp_topo.branch3_node_idx = {{Idx{1}, Idx{2}, Idx{3}}}; comp_topo.link_node_idx = {{Idx{0}, Idx{1}}, {Idx{1}, Idx{2}}}; return comp_topo; }()); @@ -114,6 +151,7 @@ inline State make_state() { {.group = 0, .pos = 1}, {.group = 0, .pos = disconnected}, {.group = disconnected, .pos = disconnected}}; + topo_comp_coup.branch3 = {{.group = 0, .pos = {4, 5, 6}}}; return topo_comp_coup; }()); @@ -136,6 +174,8 @@ inline State make_state() { emplace_component(state.components, 8, LineInput{}, dummy_value, dummy_value, dummy_value); emplace_component(state.components, 9, LineInput{}, dummy_value, dummy_value, dummy_value); emplace_component(state.components, 10, LineInput{}, dummy_value, dummy_value, dummy_value); + emplace_component(state.components, 11, make_three_winding_transformer_input(), + dummy_value, dummy_value, dummy_value); state.components.set_construction_complete(); return state; }; @@ -155,7 +195,10 @@ inline MathOutput>> make_steady_state_math .i_f = 0.5 * dummy_complex_value_sym(), .i_t = 0.5 * dummy_complex_value_sym()}, {}, - {}}, + {}, + {.s_f = dummy_complex_value_sym()}, + {.s_f = 2.0 * dummy_complex_value_sym()}, + {.s_f = 3.0 * dummy_complex_value_sym()}}, .source = {{.s = dummy_complex_value_sym(), .i = dummy_complex_value_sym()}, {}}, .shunt = {{.s = dummy_complex_value_sym(), .i = dummy_complex_value_sym()}}, .load_gen = {{.s = dummy_complex_value_sym(), .i = dummy_complex_value_sym()}, @@ -172,7 +215,10 @@ inline MathOutput>> make_short .branch = {{.i_f = dummy_complex_value_sym(), .i_t = dummy_complex_value_sym()}, {.i_f = 0.5 * dummy_complex_value_sym(), .i_t = 0.5 * dummy_complex_value_sym()}, {}, - {}}, + {}, + {.i_f = dummy_complex_value_sym()}, + {.i_f = 2.0 * dummy_complex_value_sym()}, + {.i_f = 3.0 * dummy_complex_value_sym()}}, .source = {{.i = dummy_complex_value_sym()}, {}}, .shunt = {{.i = dummy_complex_value_sym()}}}); return math_output; @@ -181,7 +227,10 @@ inline MathOutput>> make_short template struct InjectionAccumulator { auto accumulator() { return [this](Idx2D const& math_id, ComplexValue const& injection) { - auto& target_map = std::derived_from ? branch_flow_into_nodes : net_node_injections; + auto& target_map = (std::derived_from || std::derived_from || + std::derived_from) + ? branch_flow_into_nodes + : net_node_injections; if (auto [it, inserted] = target_map.try_emplace(math_id, injection); !inserted) { it->second += injection; @@ -298,6 +347,13 @@ TEST_CASE("Test topological node output") { CHECK(detail::get_node_sequence_idx(state, 0) == Idx{0}); CHECK(detail::get_node_sequence_idx(state, 1) == Idx{1}); } + SUBCASE("Shunt") { CHECK(detail::get_node_sequence_idx(state, 0) == Idx{1}); } + SUBCASE("ThreeWindingTransformer") { + auto const& branch3_node_idx = detail::get_branch3_sequence_idx(state, 0); + CHECK(branch3_node_idx[0] == Idx{1}); + CHECK(branch3_node_idx[1] == Idx{2}); + CHECK(branch3_node_idx[2] == Idx{3}); + } } SUBCASE("add_appliance_injection") { auto const state = make_state(); @@ -378,6 +434,54 @@ TEST_CASE("Test topological node output") { CHECK(!accumulator.branch_flow_into_nodes.contains(Idx2D{.group = 0, .pos = 2})); CHECK(!accumulator.branch_flow_into_nodes.contains(Idx2D{.group = 1, .pos = 0})); } + + // Shunt and ThreeWindingTransformer feed the link accumulation, never the node injection + SUBCASE("Shunt steady state output") { + auto const math_output = make_steady_state_math_output_sym(); + + detail::add_appliance_injection.template operator()(state, math_output, accumulator.accumulator()); + + CHECK(accumulator.net_node_injections.empty()); + CHECK(accumulator.branch_flow_into_nodes.size() == 1); + CHECK(accumulator.branch_flow_into_nodes.at(Idx2D{.group = 0, .pos = 1}) == dummy_complex_value_sym()); + } + SUBCASE("Shunt short circuit output") { + auto const math_output = make_short_circuit_math_output_sym(); + + detail::add_appliance_injection.template operator()(state, math_output, accumulator.accumulator()); + + CHECK(accumulator.net_node_injections.empty()); + CHECK(accumulator.branch_flow_into_nodes.size() == 1); + CHECK(accumulator.branch_flow_into_nodes.at(Idx2D{.group = 0, .pos = 1}) == dummy_complex_value_sym()); + } + SUBCASE("ThreeWindingTransformer steady state output") { + auto const math_output = make_steady_state_math_output_sym(); + + detail::add_appliance_injection.template operator()(state, math_output, + accumulator.accumulator()); + + CHECK(accumulator.net_node_injections.empty()); + CHECK(accumulator.branch_flow_into_nodes.size() == 3); + CHECK(accumulator.branch_flow_into_nodes.at(Idx2D{.group = 0, .pos = 1}) == -dummy_complex_value_sym()); + CHECK(accumulator.branch_flow_into_nodes.at(Idx2D{.group = 0, .pos = 2}) == + -2.0 * dummy_complex_value_sym()); + CHECK(accumulator.branch_flow_into_nodes.at(Idx2D{.group = 1, .pos = 0}) == + -3.0 * dummy_complex_value_sym()); + } + SUBCASE("ThreeWindingTransformer short circuit output") { + auto const math_output = make_short_circuit_math_output_sym(); + + detail::add_appliance_injection.template operator()(state, math_output, + accumulator.accumulator()); + + CHECK(accumulator.net_node_injections.empty()); + CHECK(accumulator.branch_flow_into_nodes.size() == 3); + CHECK(accumulator.branch_flow_into_nodes.at(Idx2D{.group = 0, .pos = 1}) == -dummy_complex_value_sym()); + CHECK(accumulator.branch_flow_into_nodes.at(Idx2D{.group = 0, .pos = 2}) == + -2.0 * dummy_complex_value_sym()); + CHECK(accumulator.branch_flow_into_nodes.at(Idx2D{.group = 1, .pos = 0}) == + -3.0 * dummy_complex_value_sym()); + } } SUBCASE("add_flows") { auto const state = make_state(); @@ -394,10 +498,15 @@ TEST_CASE("Test topological node output") { CHECK(accumulator.net_node_injections.at(Idx2D{.group = 0, .pos = 1}) == dummy_complex_value_sym()); CHECK(accumulator.net_node_injections.at(Idx2D{.group = 0, .pos = 2}) == dummy_complex_value_sym()); - CHECK(accumulator.branch_flow_into_nodes.size() == 2); + // {0,1} = lines (-2) + shunt (+1) + 3w side 1 (-1); {0,2} and {1,0} are 3w-only + CHECK(accumulator.branch_flow_into_nodes.size() == 4); CHECK(accumulator.branch_flow_into_nodes.at(Idx2D{.group = 0, .pos = 0}) == -dummy_complex_value_sym()); CHECK(accumulator.branch_flow_into_nodes.at(Idx2D{.group = 0, .pos = 1}) == -2.0 * dummy_complex_value_sym()); + CHECK(accumulator.branch_flow_into_nodes.at(Idx2D{.group = 0, .pos = 2}) == + -2.0 * dummy_complex_value_sym()); + CHECK(accumulator.branch_flow_into_nodes.at(Idx2D{.group = 1, .pos = 0}) == + -3.0 * dummy_complex_value_sym()); } SUBCASE("Short circuit output") { @@ -410,10 +519,15 @@ TEST_CASE("Test topological node output") { CHECK(accumulator.net_node_injections.at(Idx2D{.group = 0, .pos = 0}) == 2.0 * dummy_complex_value_sym()); CHECK(accumulator.net_node_injections.at(Idx2D{.group = 0, .pos = 1}) == dummy_complex_value_sym()); - CHECK(accumulator.branch_flow_into_nodes.size() == 2); + // {0,1} = lines (-2) + shunt (+1) + 3w side 1 (-1); {0,2} and {1,0} are 3w-only + CHECK(accumulator.branch_flow_into_nodes.size() == 4); CHECK(accumulator.branch_flow_into_nodes.at(Idx2D{.group = 0, .pos = 0}) == -dummy_complex_value_sym()); CHECK(accumulator.branch_flow_into_nodes.at(Idx2D{.group = 0, .pos = 1}) == -2.0 * dummy_complex_value_sym()); + CHECK(accumulator.branch_flow_into_nodes.at(Idx2D{.group = 0, .pos = 2}) == + -2.0 * dummy_complex_value_sym()); + CHECK(accumulator.branch_flow_into_nodes.at(Idx2D{.group = 1, .pos = 0}) == + -3.0 * dummy_complex_value_sym()); } } SUBCASE("SuperNodeSolverInput::get_total_injection_per_node") { @@ -554,8 +668,8 @@ TEST_CASE("Test topological node output") { CHECK(mock.recorded_edges[0] == links); CHECK(mock.recorded_edges[1].empty()); CHECK(mock.recorded_loads[0] == - ComplexVector{DoubleComplex{}, -dummy_complex_value_sym(), dummy_complex_value_sym()}); - CHECK(mock.recorded_loads[1] == ComplexVector{DoubleComplex{}}); + ComplexVector{DoubleComplex{}, -dummy_complex_value_sym(), -dummy_complex_value_sym()}); + CHECK(mock.recorded_loads[1] == ComplexVector{-3.0 * dummy_complex_value_sym()}); REQUIRE(result.size() == 2); CHECK(result[0].bus_injection == @@ -587,9 +701,9 @@ TEST_CASE("Test topological node output") { REQUIRE(mock.call_count == 2); CHECK(mock.recorded_edges[0] == links); CHECK(mock.recorded_edges[1].empty()); - CHECK(mock.recorded_loads[0] == - ComplexVector{dummy_complex_value_sym(), -dummy_complex_value_sym(), DoubleComplex{}}); - CHECK(mock.recorded_loads[1] == ComplexVector{DoubleComplex{}}); + CHECK(mock.recorded_loads[0] == ComplexVector{dummy_complex_value_sym(), -dummy_complex_value_sym(), + -2.0 * dummy_complex_value_sym()}); + CHECK(mock.recorded_loads[1] == ComplexVector{-3.0 * dummy_complex_value_sym()}); REQUIRE(result.size() == 2); REQUIRE(result[0].link.size() == 2); From 30ad695b21dd0066964a1892b6584e144eeacd84 Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Thu, 27 Aug 2026 17:32:12 +0200 Subject: [PATCH 11/23] make link output more consistent Signed-off-by: Martijn Govers --- .../include/power_grid_model/component/link.hpp | 12 ++++++++++++ .../include/power_grid_model/main_core/output.hpp | 10 ++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/component/link.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/component/link.hpp index 7cdbfc4900..e142423680 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/component/link.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/component/link.hpp @@ -30,6 +30,18 @@ class Link final : public Edge { double loading(double /* max_s */, double /* max_i */) const override { return 0.0; }; double phase_shift() const override { return 0.0; } + template BranchOutput get_energized_zero_output() const { + BranchOutput output{}; + static_cast(output) = base_output(true); + return output; + } + + BranchShortCircuitOutput get_energized_zero_sc_output() const { + BranchShortCircuitOutput output = get_null_sc_output(); + static_cast(output) = base_output(true); + return output; + } + private: double base_i_from_; double base_i_to_; diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp index 1eb87644b4..1a36615c18 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp @@ -135,7 +135,10 @@ constexpr auto output_result(Component const& link, MainModelState> const& math_output, Idx2D const& topo_id) { using sym = decode_symmetry_v; - if (!link.edge_status() || topo_id.group == disconnected) { + if (!link.edge_status()) { + return link.template get_energized_zero_output(); + } + if (topo_id.group == disconnected) { return link.template get_null_output(); } return link.template get_output(math_output.supernode_output[topo_id.group].link[topo_id.pos]); @@ -144,7 +147,10 @@ template Component, class ComponentContainer, short_circuit_ requires model_component_state_c inline auto output_result(Component const& link, MainModelState const& /* state */, MathOutput> const& math_output, Idx2D const& topo_id) { - if (!link.edge_status() || topo_id.group == disconnected) { + if (!link.edge_status()) { + return link.get_energized_zero_sc_output(); + } + if (topo_id.group == disconnected) { return link.get_null_sc_output(); } return link.get_sc_output(math_output.supernode_output[topo_id.group].link[topo_id.pos]); From 365c8b7f38aa030b82aac41a5fd006b04e97e463 Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Thu, 27 Aug 2026 17:38:55 +0200 Subject: [PATCH 12/23] fix output Signed-off-by: Martijn Govers --- .../include/power_grid_model/calculation_parameters.hpp | 3 ++- tests/cpp_unit_tests/main_core/test_main_core_output.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/calculation_parameters.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/calculation_parameters.hpp index 429372b30a..bc3779d7e7 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/calculation_parameters.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/calculation_parameters.hpp @@ -19,7 +19,8 @@ namespace power_grid_model { constexpr Idx disconnected = -1; -constexpr Idx status_off = 0; +constexpr IntS status_on = 1; +constexpr IntS status_off = 0; // Entry of YBus, node addmittance matrix struct YBusElement { diff --git a/tests/cpp_unit_tests/main_core/test_main_core_output.cpp b/tests/cpp_unit_tests/main_core/test_main_core_output.cpp index 32e9928b88..085788ff28 100644 --- a/tests/cpp_unit_tests/main_core/test_main_core_output.cpp +++ b/tests/cpp_unit_tests/main_core/test_main_core_output.cpp @@ -228,10 +228,10 @@ TEST_CASE("Test main core output") { using State = MainModelState; State state; - emplace_component(state.components, 0, LinkInput{.id = 0, .from_status = IntS{1}, .to_status = IntS{1}}, + emplace_component(state.components, 0, LinkInput{.id = 0, .from_status = status_on, .to_status = status_on}, 10e3, 20e3); emplace_component(state.components, 1, - LinkInput{.id = 1, .from_status = IntS{1}, .to_status = status_off}, 10e3, 20e3); + LinkInput{.id = 1, .from_status = status_on, .to_status = status_off}, 10e3, 20e3); emplace_component(state.components, 2, LinkInput{.id = 2, .from_status = status_off, .to_status = status_off}, 10e3, 20e3); state.components.set_construction_complete(); @@ -262,9 +262,9 @@ TEST_CASE("Test main core output") { CHECK(output[0].i_from == doctest::Approx(5.0 * base_power_3p / 10e3 / sqrt3)); CHECK(output[0].i_to == doctest::Approx(5.0 * base_power_3p / 20e3 / sqrt3)); CHECK(output[1].id == 1); - CHECK(output[1].energized == status_off); + CHECK(output[1].energized == status_on); // connected rest of grid but one of the ends is off CHECK(output[2].id == 2); - CHECK(output[2].energized == status_off); + CHECK(output[2].energized == status_off); // completely disconnected from rest of grid } SUBCASE("Short circuit output") { From 68744d81e688626ef00731430baefecfc51da9f2 Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Thu, 27 Aug 2026 17:39:05 +0200 Subject: [PATCH 13/23] fix output Signed-off-by: Martijn Govers --- tests/cpp_unit_tests/main_core/test_main_core_output.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/cpp_unit_tests/main_core/test_main_core_output.cpp b/tests/cpp_unit_tests/main_core/test_main_core_output.cpp index 085788ff28..ee419d0b90 100644 --- a/tests/cpp_unit_tests/main_core/test_main_core_output.cpp +++ b/tests/cpp_unit_tests/main_core/test_main_core_output.cpp @@ -228,8 +228,8 @@ TEST_CASE("Test main core output") { using State = MainModelState; State state; - emplace_component(state.components, 0, LinkInput{.id = 0, .from_status = status_on, .to_status = status_on}, - 10e3, 20e3); + emplace_component(state.components, 0, + LinkInput{.id = 0, .from_status = status_on, .to_status = status_on}, 10e3, 20e3); emplace_component(state.components, 1, LinkInput{.id = 1, .from_status = status_on, .to_status = status_off}, 10e3, 20e3); emplace_component(state.components, 2, From e61d096ddfb36aa0d0038a0fcafe4001a8916d8c Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Fri, 28 Aug 2026 08:44:46 +0200 Subject: [PATCH 14/23] fix possibly dangling reference Signed-off-by: Martijn Govers --- .../main_core/math_output_queries.hpp | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/math_output_queries.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/math_output_queries.hpp index e5c08b7dc9..a5b7e5e26b 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/math_output_queries.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/math_output_queries.hpp @@ -24,24 +24,20 @@ constexpr auto const& get_component_output(MathOutput auto const& { - // TODO(mgovers): cleanup v2: change back to std::derived_from - if constexpr (std::derived_from || std::derived_from) { - return solver_output.branch; - } else if constexpr (std::same_as && requires { solver_output.source; }) { - return solver_output.source; - } else if constexpr (std::same_as && requires { solver_output.shunt; }) { - return solver_output.shunt; - } else if constexpr (std::derived_from && requires { solver_output.load_gen; }) { - return solver_output.load_gen; - } else if constexpr (std::same_as && requires { solver_output.fault; }) { - return solver_output.fault; - } else { - static_assert(false, "Unsupported component type for output retrieval"); - } - }(); - - return component_type_output[math_id.pos]; + // TODO(mgovers): cleanup v2: change back to std::derived_from + if constexpr (std::derived_from || std::derived_from) { + return solver_output.branch[math_id.pos]; + } else if constexpr (std::same_as && requires { solver_output.source; }) { + return solver_output.source[math_id.pos]; + } else if constexpr (std::same_as && requires { solver_output.shunt; }) { + return solver_output.shunt[math_id.pos]; + } else if constexpr (std::derived_from && requires { solver_output.load_gen; }) { + return solver_output.load_gen[math_id.pos]; + } else if constexpr (std::same_as && requires { solver_output.fault; }) { + return solver_output.fault[math_id.pos]; + } else { + static_assert(false, "Unsupported component type for output retrieval"); + } } } // namespace power_grid_model::main_core From bd7ec89389d6ea671dc71556c7146828b810a035 Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Fri, 28 Aug 2026 08:56:33 +0200 Subject: [PATCH 15/23] add validation case to prevent 'energized' regression Signed-off-by: Martijn Govers --- .../data/power_flow/dummy-test-link/README.md | 34 ++++++++++ .../dummy-test-link/asym_output_batch.json | 68 +++++++++++++++++++ .../asym_output_batch.json.license | 3 + .../power_flow/dummy-test-link/input.json | 46 +++++++++++++ .../dummy-test-link/input.json.license | 3 + .../power_flow/dummy-test-link/params.json | 5 ++ .../dummy-test-link/params.json.license | 3 + .../dummy-test-link/sym_output_batch.json | 68 +++++++++++++++++++ .../sym_output_batch.json.license | 3 + .../dummy-test-link/update_batch.json | 44 ++++++++++++ .../dummy-test-link/update_batch.json.license | 3 + 11 files changed, 280 insertions(+) create mode 100644 tests/data/power_flow/dummy-test-link/README.md create mode 100644 tests/data/power_flow/dummy-test-link/asym_output_batch.json create mode 100644 tests/data/power_flow/dummy-test-link/asym_output_batch.json.license create mode 100644 tests/data/power_flow/dummy-test-link/input.json create mode 100644 tests/data/power_flow/dummy-test-link/input.json.license create mode 100644 tests/data/power_flow/dummy-test-link/params.json create mode 100644 tests/data/power_flow/dummy-test-link/params.json.license create mode 100644 tests/data/power_flow/dummy-test-link/sym_output_batch.json create mode 100644 tests/data/power_flow/dummy-test-link/sym_output_batch.json.license create mode 100644 tests/data/power_flow/dummy-test-link/update_batch.json create mode 100644 tests/data/power_flow/dummy-test-link/update_batch.json.license diff --git a/tests/data/power_flow/dummy-test-link/README.md b/tests/data/power_flow/dummy-test-link/README.md new file mode 100644 index 0000000000..5881f10eb3 --- /dev/null +++ b/tests/data/power_flow/dummy-test-link/README.md @@ -0,0 +1,34 @@ + + +# Test case with line into itself + +A line into itself is a line that is connected to the same node on both ends. It acts as a shunt: + +$$ +Y_{\text{shunt}} = 2 \pi f c \left(\tan \delta +\mathrm{j}\right) +$$ + +Note that $Z_{\text{series}}$ is equivalent to $0$ (no net power flow). + +This is tested as follows: + +1. Create some complicated grid. +2. Add 4 components to the grid: 2 lines into itself and 2 shunts on the same node. + The attributes are chosen such, that $Y_{\text{shunt}}$ is the same for both components: + 1. $c_{1,\text{line}} = \frac{b_{1,\text{shunt}}}{2 \pi f}$ + 2. $\tan \delta_{1,\text{line}} = \frac{b_{1,\text{shunt}}}{g_{1,\text{shunt}}}$ + 3. $c_{0,\text{line}} = \frac{b_{0,\text{shunt}}}{2 \pi f}$ + 4. $\tan \delta_{0,\text{line}} = \frac{b_{0,\text{shunt}}}{g_{0,\text{shunt}}}$ +3. Create a batch update with the following scenarios: + 1. Benchmark case: the lines are fully disconnected, the shunts are connected. + 2. Actual test case: the lines into itself are connected on both ends, the shunts are disconnected. +4. The output of both scenarios should be the same, except for the lines into itself and shunts. + For those, it is true that $p_{\text{line},\text{from}} = p_{\text{line},\text{to}} = \frac{1}{2}p_{\text{shunt}}$, + $q_{\text{line},\text{from}} = q_{\text{line},\text{to}} = \frac{1}{2}q_{\text{shunt}}$, + $s_{\text{line},\text{from}} = s_{\text{line},\text{to}} = \frac{1}{2}s_{\text{shunt}}$ and + $i_{\text{line},\text{from}} = i_{\text{line},\text{to}} = \frac{1}{2}i_{\text{shunt}}$. + To verify, the output can be generated from a normal run and eyeballed to compare. diff --git a/tests/data/power_flow/dummy-test-link/asym_output_batch.json b/tests/data/power_flow/dummy-test-link/asym_output_batch.json new file mode 100644 index 0000000000..cd521b82d3 --- /dev/null +++ b/tests/data/power_flow/dummy-test-link/asym_output_batch.json @@ -0,0 +1,68 @@ +{ + "version": "1.0", + "type": "sym_output", + "is_batch": true, + "attributes": {}, + "data": [ + { + "node": [ + {"id": 1, "energized": 1, "u_pu": 1.050002653570703, "u": 10500.026535707029, "u_angle": -1.4793528305044663e-06, "p": 1345653.3392703342, "q": -2934719.4520267113}, + {"id": 2, "energized": 1, "u_pu": 1.0485445661489112, "u": 10485.445661489111, "u_angle": -0.0032736161797103387, "p": 0.017979197783649453, "q": -0.0054018350275461787} + ], + "link": [ + {"id": 3, "energized": 1, "loading": 0, "p_from": 1326242.8673741999, "q_from": 590587.18028971716, "i_from": 79.938915663603737, "s_from": 1451796.5975933056, "p_to": -1326242.8577888545, "q_to": -590587.18987506256, "i_to": 79.938915663603737, "s_to": 1451796.5927362107} + ], + "source": [ + {"id": 4, "energized": 1, "p": 1345653.339270334, "q": -2934719.4520267108, "i": 177.52228086868132, "s": 3228523.0635064943, "pf": 0.41680152589922087} + ], + "asym_load": [ + {"id": 5, "energized": 1, "p": 659667.41990631109, "q": 329833.70995315554, "i": 40.60995628864648, "s": 737530.59672620485, "pf": 0.89442719099991574} + ] + }, + { + "node": [ + {"id": 1, "energized": 1, "u_pu": 1.050002653570703, "u": 10500.026535707029, "u_angle": -1.4793528305044663e-06, "p": 1345653.3392703342, "q": -2934719.4520267113}, + {"id": 2, "energized": 0, "u_pu": 0, "u": 0, "u_angle": 0} + ], + "link": [ + {"id": 3, "energized": 1, "loading": 0, "p_from": [0, 0, 0], "q_from": [0, 0, 0], "p_to": [0, 0, 0], "q_to": [0, 0, 0]} + ], + "source": [ + {"id": 4, "energized": 1, "p": [0, 0, 0], "q": [0, 0, 0]} + ], + "asym_load": [ + {"id": 5, "energized": 1, "p": [0, 0, 0], "q": [0, 0, 0]} + ] + }, + { + "node": [ + {"id": 1, "energized": 1, "u_pu": 1.050002653570703, "u": 10500.026535707029, "u_angle": -1.4793528305044663e-06, "p": 1345653.3392703342, "q": -2934719.4520267113}, + {"id": 2, "energized": 0, "u_pu": 0, "u": 0, "u_angle": 0} + ], + "link": [ + {"id": 3, "energized": 1, "loading": 0, "p_from": [0, 0, 0], "q_from": [0, 0, 0], "p_to": [0, 0, 0], "q_to": [0, 0, 0]} + ], + "source": [ + {"id": 4, "energized": 1, "p": [0, 0, 0], "q": [0, 0, 0]} + ], + "asym_load": [ + {"id": 5, "energized": 1, "p": [0, 0, 0], "q": [0, 0, 0]} + ] + }, + { + "node": [ + {"id": 1, "energized": 1, "u_pu": 1.050002653570703, "u": 10500.026535707029, "u_angle": -1.4793528305044663e-06, "p": 1345653.3392703342, "q": -2934719.4520267113}, + {"id": 2, "energized": 0, "u_pu": [0, 0, 0], "u": [0, 0, 0], "u_angle": [0, 0, 0]} + ], + "link": [ + {"id": 3, "energized": 1, "loading": 0, "p_from": [0, 0, 0], "q_from": [0, 0, 0], "p_to": [0, 0, 0], "q_to": [0, 0, 0]} + ], + "source": [ + {"id": 4, "energized": 1, "p": [0, 0, 0], "q": [0, 0, 0]} + ], + "asym_load": [ + {"id": 5, "energized": 1, "p": [0, 0, 0], "q": [0, 0, 0]} + ] + } + ] +} \ No newline at end of file diff --git a/tests/data/power_flow/dummy-test-link/asym_output_batch.json.license b/tests/data/power_flow/dummy-test-link/asym_output_batch.json.license new file mode 100644 index 0000000000..7601059167 --- /dev/null +++ b/tests/data/power_flow/dummy-test-link/asym_output_batch.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: Contributors to the Power Grid Model project + +SPDX-License-Identifier: MPL-2.0 diff --git a/tests/data/power_flow/dummy-test-link/input.json b/tests/data/power_flow/dummy-test-link/input.json new file mode 100644 index 0000000000..55c862990b --- /dev/null +++ b/tests/data/power_flow/dummy-test-link/input.json @@ -0,0 +1,46 @@ +{ + "version": "1.0", + "type": "input", + "is_batch": false, + "attributes": {}, + "data": { + "node": [ + { + "id": 1, + "u_rated": 10e3 + }, + { + "id": 2, + "u_rated": 10e3 + } + ], + "link": [ + { + "id": 3, + "from_node": 1, + "to_node": 2, + "from_status": 1, + "to_status": 1 + } + ], + "source": [ + { + "id": 4, + "node": 1, + "status": 1, + "u_ref": 1.05, + "sk": 1e12 + } + ], + "asym_load": [ + { + "id": 5, + "node": 2, + "status": 1, + "type": 1, + "p_specified": [2.0e5, 1.0e5, 3.0e5], + "q_specified": [1.0e5, 1.0e5, 1.0e5] + } + ] + } +} \ No newline at end of file diff --git a/tests/data/power_flow/dummy-test-link/input.json.license b/tests/data/power_flow/dummy-test-link/input.json.license new file mode 100644 index 0000000000..7601059167 --- /dev/null +++ b/tests/data/power_flow/dummy-test-link/input.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: Contributors to the Power Grid Model project + +SPDX-License-Identifier: MPL-2.0 diff --git a/tests/data/power_flow/dummy-test-link/params.json b/tests/data/power_flow/dummy-test-link/params.json new file mode 100644 index 0000000000..2f096834b9 --- /dev/null +++ b/tests/data/power_flow/dummy-test-link/params.json @@ -0,0 +1,5 @@ +{ + "calculation_method": ["newton_raphson", "iterative_current"], + "rtol": 1e-5, + "atol": 1e-5 +} diff --git a/tests/data/power_flow/dummy-test-link/params.json.license b/tests/data/power_flow/dummy-test-link/params.json.license new file mode 100644 index 0000000000..7601059167 --- /dev/null +++ b/tests/data/power_flow/dummy-test-link/params.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: Contributors to the Power Grid Model project + +SPDX-License-Identifier: MPL-2.0 diff --git a/tests/data/power_flow/dummy-test-link/sym_output_batch.json b/tests/data/power_flow/dummy-test-link/sym_output_batch.json new file mode 100644 index 0000000000..c9e40de719 --- /dev/null +++ b/tests/data/power_flow/dummy-test-link/sym_output_batch.json @@ -0,0 +1,68 @@ +{ + "version": "1.0", + "type": "sym_output", + "is_batch": true, + "attributes": {}, + "data": [ + { + "node": [ + {"id": 1, "energized": 1, "u_pu": 1.05, "u": 10500, "u_angle": 0, "p": 661500, "q": 330750}, + {"id": 2, "energized": 1, "u_pu": 1.05, "u": 10500, "u_angle": 0, "p": -661500, "q": -330750} + ], + "link": [ + {"id": 3, "energized": 1, "loading": 0, "p_from": 661500, "q_from": 330750, "p_to": -661500, "q_to": -330750} + ], + "source": [ + {"id": 4, "energized": 1, "p": 661500, "q": 330750} + ], + "asym_load": [ + {"id": 5, "energized": 1, "p": 661500, "q": 330750} + ] + }, + { + "node": [ + {"id": 1, "energized": 1, "u_pu": 1.05, "u": 10500, "u_angle": 0, "p": 0, "q": 0}, + {"id": 2, "energized": 0, "u_pu": 0, "u": 0, "u_angle": 0} + ], + "link": [ + {"id": 3, "energized": 1, "loading": 0, "p_from": 0, "q_from": 0, "p_to": 0, "q_to": 0} + ], + "source": [ + {"id": 4, "energized": 1, "p": 0, "q": 0} + ], + "asym_load": [ + {"id": 5, "energized": 0, "p": 0, "q": 0} + ] + }, + { + "node": [ + {"id": 1, "energized": 1, "u_pu": 1.05, "u": 10500, "u_angle": 0, "p": 0, "q": 0}, + {"id": 2, "energized": 0, "u_pu": 0, "u": 0, "u_angle": 0} + ], + "link": [ + {"id": 3, "energized": 0, "loading": 0, "p_from": 0, "q_from": 0, "p_to": 0, "q_to": 0} + ], + "source": [ + {"id": 4, "energized": 1, "p": 0, "q": 0} + ], + "asym_load": [ + {"id": 5, "energized": 0, "p": 0, "q": 0} + ] + }, + { + "node": [ + {"id": 1, "energized": 1, "u_pu": 1.05, "u": 10500, "u_angle": 0, "p": 0, "q": 0}, + {"id": 2, "energized": 0, "u_pu": 0, "u": 0, "u_angle": 0} + ], + "link": [ + {"id": 3, "energized": 0, "loading": 0, "p_from": 0, "q_from": 0, "p_to": 0, "q_to": 0} + ], + "source": [ + {"id": 4, "energized": 1, "p": 0, "q": 0} + ], + "asym_load": [ + {"id": 5, "energized": 0, "p": 0, "q": 0} + ] + } + ] +} \ No newline at end of file diff --git a/tests/data/power_flow/dummy-test-link/sym_output_batch.json.license b/tests/data/power_flow/dummy-test-link/sym_output_batch.json.license new file mode 100644 index 0000000000..7601059167 --- /dev/null +++ b/tests/data/power_flow/dummy-test-link/sym_output_batch.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: Contributors to the Power Grid Model project + +SPDX-License-Identifier: MPL-2.0 diff --git a/tests/data/power_flow/dummy-test-link/update_batch.json b/tests/data/power_flow/dummy-test-link/update_batch.json new file mode 100644 index 0000000000..bd5d3e7baa --- /dev/null +++ b/tests/data/power_flow/dummy-test-link/update_batch.json @@ -0,0 +1,44 @@ +{ + "version": "1.0", + "type": "update", + "is_batch": true, + "attributes": {}, + "data": [ + { + "link": [ + { + "id": 3, + "from_status": 1, + "to_status": 1 + } + ] + }, + { + "link": [ + { + "id": 3, + "from_status": 1, + "to_status": 0 + } + ] + }, + { + "link": [ + { + "id": 3, + "from_status": 0, + "to_status": 1 + } + ] + }, + { + "link": [ + { + "id": 3, + "from_status": 0, + "to_status": 0 + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/data/power_flow/dummy-test-link/update_batch.json.license b/tests/data/power_flow/dummy-test-link/update_batch.json.license new file mode 100644 index 0000000000..7601059167 --- /dev/null +++ b/tests/data/power_flow/dummy-test-link/update_batch.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: Contributors to the Power Grid Model project + +SPDX-License-Identifier: MPL-2.0 From 77ebd44a530169ddf39a83a19fa7f0a93a2135e8 Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Fri, 28 Aug 2026 09:03:30 +0200 Subject: [PATCH 16/23] fix asym Signed-off-by: Martijn Govers --- .../dummy-test-link/asym_output_batch.json | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/data/power_flow/dummy-test-link/asym_output_batch.json b/tests/data/power_flow/dummy-test-link/asym_output_batch.json index cd521b82d3..777afc1a23 100644 --- a/tests/data/power_flow/dummy-test-link/asym_output_batch.json +++ b/tests/data/power_flow/dummy-test-link/asym_output_batch.json @@ -1,28 +1,28 @@ { "version": "1.0", - "type": "sym_output", + "type": "asym_output", "is_batch": true, "attributes": {}, "data": [ { "node": [ - {"id": 1, "energized": 1, "u_pu": 1.050002653570703, "u": 10500.026535707029, "u_angle": -1.4793528305044663e-06, "p": 1345653.3392703342, "q": -2934719.4520267113}, - {"id": 2, "energized": 1, "u_pu": 1.0485445661489112, "u": 10485.445661489111, "u_angle": -0.0032736161797103387, "p": 0.017979197783649453, "q": -0.0054018350275461787} + {"id": 1, "energized": 1, "u_pu": [1.05, 1.05, 1.05], "u": [6062.18778, 6062.18778, 6062.18778], "u_angle": [0, 0, 0], "p": [220500, 110250, 330751], "q": [110250, 110250, 110250]}, + {"id": 2, "energized": 1, "u_pu": [1.05, 1.05, 1.05], "u": [6062.18778, 6062.18778, 6062.18778], "u_angle": [0, 0, 0], "p": [-220500, -110250, -330751], "q": [-110250, -110250, -110250]} ], "link": [ - {"id": 3, "energized": 1, "loading": 0, "p_from": 1326242.8673741999, "q_from": 590587.18028971716, "i_from": 79.938915663603737, "s_from": 1451796.5975933056, "p_to": -1326242.8577888545, "q_to": -590587.18987506256, "i_to": 79.938915663603737, "s_to": 1451796.5927362107} + {"id": 3, "energized": 1, "loading": 0, "p_from": [220500, 110250, 330751], "q_from": [110250, 110250, 110250], "p_to": [-220500, -110250, -330751], "q_to": [-110250, -110250, -110250]} ], "source": [ - {"id": 4, "energized": 1, "p": 1345653.339270334, "q": -2934719.4520267108, "i": 177.52228086868132, "s": 3228523.0635064943, "pf": 0.41680152589922087} + {"id": 4, "energized": 1, "p": [220500, 110250, 330751], "q": [110250, 110250, 110250]} ], "asym_load": [ - {"id": 5, "energized": 1, "p": 659667.41990631109, "q": 329833.70995315554, "i": 40.60995628864648, "s": 737530.59672620485, "pf": 0.89442719099991574} + {"id": 5, "energized": 1, "p": [220500, 110250, 330751], "q": [110250, 110250, 110250]} ] }, { "node": [ - {"id": 1, "energized": 1, "u_pu": 1.050002653570703, "u": 10500.026535707029, "u_angle": -1.4793528305044663e-06, "p": 1345653.3392703342, "q": -2934719.4520267113}, - {"id": 2, "energized": 0, "u_pu": 0, "u": 0, "u_angle": 0} + {"id": 1, "energized": 1, "u_pu": [1.05, 1.05, 1.05], "u": [6062.18778, 6062.18778, 6062.18778], "u_angle": [0, 0, 0], "p": [0, 0, 0], "q": [0, 0, 0]}, + {"id": 2, "energized": 0, "u_pu": [0, 0, 0], "u": [0, 0, 0], "u_angle": [0, 0, 0]} ], "link": [ {"id": 3, "energized": 1, "loading": 0, "p_from": [0, 0, 0], "q_from": [0, 0, 0], "p_to": [0, 0, 0], "q_to": [0, 0, 0]} @@ -31,37 +31,37 @@ {"id": 4, "energized": 1, "p": [0, 0, 0], "q": [0, 0, 0]} ], "asym_load": [ - {"id": 5, "energized": 1, "p": [0, 0, 0], "q": [0, 0, 0]} + {"id": 5, "energized": 0, "p": [0, 0, 0], "q": [0, 0, 0]} ] }, { "node": [ - {"id": 1, "energized": 1, "u_pu": 1.050002653570703, "u": 10500.026535707029, "u_angle": -1.4793528305044663e-06, "p": 1345653.3392703342, "q": -2934719.4520267113}, - {"id": 2, "energized": 0, "u_pu": 0, "u": 0, "u_angle": 0} + {"id": 1, "energized": 1, "u_pu": [1.05, 1.05, 1.05], "u": [6062.18778, 6062.18778, 6062.18778], "u_angle": [0, 0, 0], "p": [0, 0, 0], "q": [0, 0, 0]}, + {"id": 2, "energized": 0, "u_pu": [0, 0, 0], "u": [0, 0, 0], "u_angle": [0, 0, 0]} ], "link": [ - {"id": 3, "energized": 1, "loading": 0, "p_from": [0, 0, 0], "q_from": [0, 0, 0], "p_to": [0, 0, 0], "q_to": [0, 0, 0]} + {"id": 3, "energized": 0, "loading": 0, "p_from": [0, 0, 0], "q_from": [0, 0, 0], "p_to": [0, 0, 0], "q_to": [0, 0, 0]} ], "source": [ {"id": 4, "energized": 1, "p": [0, 0, 0], "q": [0, 0, 0]} ], "asym_load": [ - {"id": 5, "energized": 1, "p": [0, 0, 0], "q": [0, 0, 0]} + {"id": 5, "energized": 0, "p": [0, 0, 0], "q": [0, 0, 0]} ] }, { "node": [ - {"id": 1, "energized": 1, "u_pu": 1.050002653570703, "u": 10500.026535707029, "u_angle": -1.4793528305044663e-06, "p": 1345653.3392703342, "q": -2934719.4520267113}, + {"id": 1, "energized": 1, "u_pu": [1.05, 1.05, 1.05], "u": [6062.18778, 6062.18778, 6062.18778], "u_angle": [0, 0, 0], "p": [0, 0, 0], "q": [0, 0, 0]}, {"id": 2, "energized": 0, "u_pu": [0, 0, 0], "u": [0, 0, 0], "u_angle": [0, 0, 0]} ], "link": [ - {"id": 3, "energized": 1, "loading": 0, "p_from": [0, 0, 0], "q_from": [0, 0, 0], "p_to": [0, 0, 0], "q_to": [0, 0, 0]} + {"id": 3, "energized": 0, "loading": 0, "p_from": [0, 0, 0], "q_from": [0, 0, 0], "p_to": [0, 0, 0], "q_to": [0, 0, 0]} ], "source": [ {"id": 4, "energized": 1, "p": [0, 0, 0], "q": [0, 0, 0]} ], "asym_load": [ - {"id": 5, "energized": 1, "p": [0, 0, 0], "q": [0, 0, 0]} + {"id": 5, "energized": 0, "p": [0, 0, 0], "q": [0, 0, 0]} ] } ] From 72343005faa9f518fa4de38eb7900eb494044c22 Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Fri, 28 Aug 2026 09:04:16 +0200 Subject: [PATCH 17/23] fix pre-commit-config.yaml Signed-off-by: Martijn Govers --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4a2a1833a8..8035e23264 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: entry: uv run --frozen pytest language: system pass_filenames: false - types_or: [ python, c++, c ] + types_or: [ python, c++, c, json ] - id: ruff-format name: ruff-format entry: uv run --frozen ruff format From 58a3f57234550f38815afcc195c29aeb4945169f Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Fri, 28 Aug 2026 09:05:15 +0200 Subject: [PATCH 18/23] remove unused Signed-off-by: Martijn Govers --- .../data/power_flow/dummy-test-link/README.md | 34 ------------------- 1 file changed, 34 deletions(-) delete mode 100644 tests/data/power_flow/dummy-test-link/README.md diff --git a/tests/data/power_flow/dummy-test-link/README.md b/tests/data/power_flow/dummy-test-link/README.md deleted file mode 100644 index 5881f10eb3..0000000000 --- a/tests/data/power_flow/dummy-test-link/README.md +++ /dev/null @@ -1,34 +0,0 @@ - - -# Test case with line into itself - -A line into itself is a line that is connected to the same node on both ends. It acts as a shunt: - -$$ -Y_{\text{shunt}} = 2 \pi f c \left(\tan \delta +\mathrm{j}\right) -$$ - -Note that $Z_{\text{series}}$ is equivalent to $0$ (no net power flow). - -This is tested as follows: - -1. Create some complicated grid. -2. Add 4 components to the grid: 2 lines into itself and 2 shunts on the same node. - The attributes are chosen such, that $Y_{\text{shunt}}$ is the same for both components: - 1. $c_{1,\text{line}} = \frac{b_{1,\text{shunt}}}{2 \pi f}$ - 2. $\tan \delta_{1,\text{line}} = \frac{b_{1,\text{shunt}}}{g_{1,\text{shunt}}}$ - 3. $c_{0,\text{line}} = \frac{b_{0,\text{shunt}}}{2 \pi f}$ - 4. $\tan \delta_{0,\text{line}} = \frac{b_{0,\text{shunt}}}{g_{0,\text{shunt}}}$ -3. Create a batch update with the following scenarios: - 1. Benchmark case: the lines are fully disconnected, the shunts are connected. - 2. Actual test case: the lines into itself are connected on both ends, the shunts are disconnected. -4. The output of both scenarios should be the same, except for the lines into itself and shunts. - For those, it is true that $p_{\text{line},\text{from}} = p_{\text{line},\text{to}} = \frac{1}{2}p_{\text{shunt}}$, - $q_{\text{line},\text{from}} = q_{\text{line},\text{to}} = \frac{1}{2}q_{\text{shunt}}$, - $s_{\text{line},\text{from}} = s_{\text{line},\text{to}} = \frac{1}{2}s_{\text{shunt}}$ and - $i_{\text{line},\text{from}} = i_{\text{line},\text{to}} = \frac{1}{2}i_{\text{shunt}}$. - To verify, the output can be generated from a normal run and eyeballed to compare. From cbc43fb60c9c78ddac4e4744c2ffac7c2b1feb77 Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Fri, 28 Aug 2026 09:16:46 +0200 Subject: [PATCH 19/23] fix u_angle Signed-off-by: Martijn Govers --- .../dummy-test-link/asym_output_batch.json | 16 ++++++++-------- .../dummy-test-link/sym_output_batch.json | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/data/power_flow/dummy-test-link/asym_output_batch.json b/tests/data/power_flow/dummy-test-link/asym_output_batch.json index 777afc1a23..70c2ea0855 100644 --- a/tests/data/power_flow/dummy-test-link/asym_output_batch.json +++ b/tests/data/power_flow/dummy-test-link/asym_output_batch.json @@ -6,8 +6,8 @@ "data": [ { "node": [ - {"id": 1, "energized": 1, "u_pu": [1.05, 1.05, 1.05], "u": [6062.18778, 6062.18778, 6062.18778], "u_angle": [0, 0, 0], "p": [220500, 110250, 330751], "q": [110250, 110250, 110250]}, - {"id": 2, "energized": 1, "u_pu": [1.05, 1.05, 1.05], "u": [6062.18778, 6062.18778, 6062.18778], "u_angle": [0, 0, 0], "p": [-220500, -110250, -330751], "q": [-110250, -110250, -110250]} + {"id": 1, "energized": 1, "u_pu": [1.05, 1.05, 1.05], "u": [6062.18778, 6062.18778, 6062.18778], "u_angle": [0, -2.0944, 2.0944], "p": [220500, 110250, 330751], "q": [110250, 110250, 110250]}, + {"id": 2, "energized": 1, "u_pu": [1.05, 1.05, 1.05], "u": [6062.18778, 6062.18778, 6062.18778], "u_angle": [0, -2.0944, 2.0944], "p": [-220500, -110250, -330751], "q": [-110250, -110250, -110250]} ], "link": [ {"id": 3, "energized": 1, "loading": 0, "p_from": [220500, 110250, 330751], "q_from": [110250, 110250, 110250], "p_to": [-220500, -110250, -330751], "q_to": [-110250, -110250, -110250]} @@ -21,8 +21,8 @@ }, { "node": [ - {"id": 1, "energized": 1, "u_pu": [1.05, 1.05, 1.05], "u": [6062.18778, 6062.18778, 6062.18778], "u_angle": [0, 0, 0], "p": [0, 0, 0], "q": [0, 0, 0]}, - {"id": 2, "energized": 0, "u_pu": [0, 0, 0], "u": [0, 0, 0], "u_angle": [0, 0, 0]} + {"id": 1, "energized": 1, "u_pu": [1.05, 1.05, 1.05], "u": [6062.18778, 6062.18778, 6062.18778], "u_angle": [0, -2.0944, 2.0944], "p": [0, 0, 0], "q": [0, 0, 0]}, + {"id": 2, "energized": 0, "u_pu": [0, 0, 0], "u": [0, 0, 0], "u_angle": [0, -2.0944, 2.0944], "p": [0, 0, 0], "q": [0, 0, 0]} ], "link": [ {"id": 3, "energized": 1, "loading": 0, "p_from": [0, 0, 0], "q_from": [0, 0, 0], "p_to": [0, 0, 0], "q_to": [0, 0, 0]} @@ -36,8 +36,8 @@ }, { "node": [ - {"id": 1, "energized": 1, "u_pu": [1.05, 1.05, 1.05], "u": [6062.18778, 6062.18778, 6062.18778], "u_angle": [0, 0, 0], "p": [0, 0, 0], "q": [0, 0, 0]}, - {"id": 2, "energized": 0, "u_pu": [0, 0, 0], "u": [0, 0, 0], "u_angle": [0, 0, 0]} + {"id": 1, "energized": 1, "u_pu": [1.05, 1.05, 1.05], "u": [6062.18778, 6062.18778, 6062.18778], "u_angle": [0, -2.0944, 2.0944], "p": [0, 0, 0], "q": [0, 0, 0]}, + {"id": 2, "energized": 0, "u_pu": [0, 0, 0], "u": [0, 0, 0], "u_angle": [0, -2.0944, 2.0944], "p": [0, 0, 0], "q": [0, 0, 0]} ], "link": [ {"id": 3, "energized": 0, "loading": 0, "p_from": [0, 0, 0], "q_from": [0, 0, 0], "p_to": [0, 0, 0], "q_to": [0, 0, 0]} @@ -51,8 +51,8 @@ }, { "node": [ - {"id": 1, "energized": 1, "u_pu": [1.05, 1.05, 1.05], "u": [6062.18778, 6062.18778, 6062.18778], "u_angle": [0, 0, 0], "p": [0, 0, 0], "q": [0, 0, 0]}, - {"id": 2, "energized": 0, "u_pu": [0, 0, 0], "u": [0, 0, 0], "u_angle": [0, 0, 0]} + {"id": 1, "energized": 1, "u_pu": [1.05, 1.05, 1.05], "u": [6062.18778, 6062.18778, 6062.18778], "u_angle": [0, -2.0944, 2.0944], "p": [0, 0, 0], "q": [0, 0, 0]}, + {"id": 2, "energized": 0, "u_pu": [0, 0, 0], "u": [0, 0, 0], "u_angle": [0, -2.0944, 2.0944], "p": [0, 0, 0], "q": [0, 0, 0]} ], "link": [ {"id": 3, "energized": 0, "loading": 0, "p_from": [0, 0, 0], "q_from": [0, 0, 0], "p_to": [0, 0, 0], "q_to": [0, 0, 0]} diff --git a/tests/data/power_flow/dummy-test-link/sym_output_batch.json b/tests/data/power_flow/dummy-test-link/sym_output_batch.json index c9e40de719..64528c504d 100644 --- a/tests/data/power_flow/dummy-test-link/sym_output_batch.json +++ b/tests/data/power_flow/dummy-test-link/sym_output_batch.json @@ -22,7 +22,7 @@ { "node": [ {"id": 1, "energized": 1, "u_pu": 1.05, "u": 10500, "u_angle": 0, "p": 0, "q": 0}, - {"id": 2, "energized": 0, "u_pu": 0, "u": 0, "u_angle": 0} + {"id": 2, "energized": 0, "u_pu": 0, "u": 0, "u_angle": 0, "p": 0, "q": 0} ], "link": [ {"id": 3, "energized": 1, "loading": 0, "p_from": 0, "q_from": 0, "p_to": 0, "q_to": 0} @@ -37,7 +37,7 @@ { "node": [ {"id": 1, "energized": 1, "u_pu": 1.05, "u": 10500, "u_angle": 0, "p": 0, "q": 0}, - {"id": 2, "energized": 0, "u_pu": 0, "u": 0, "u_angle": 0} + {"id": 2, "energized": 0, "u_pu": 0, "u": 0, "u_angle": 0, "p": 0, "q": 0} ], "link": [ {"id": 3, "energized": 0, "loading": 0, "p_from": 0, "q_from": 0, "p_to": 0, "q_to": 0} @@ -52,7 +52,7 @@ { "node": [ {"id": 1, "energized": 1, "u_pu": 1.05, "u": 10500, "u_angle": 0, "p": 0, "q": 0}, - {"id": 2, "energized": 0, "u_pu": 0, "u": 0, "u_angle": 0} + {"id": 2, "energized": 0, "u_pu": 0, "u": 0, "u_angle": 0, "p": 0, "q": 0} ], "link": [ {"id": 3, "energized": 0, "loading": 0, "p_from": 0, "q_from": 0, "p_to": 0, "q_to": 0} From b1b6d96cf2fef5878be1ec12244848187251dbca Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Fri, 28 Aug 2026 09:21:22 +0200 Subject: [PATCH 20/23] fix pytest warnings Signed-off-by: Martijn Govers --- tests/unit/utils.py | 14 +++++++++----- tests/unit/validation/test_validation_functions.py | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/unit/utils.py b/tests/unit/utils.py index a62ef162fb..4a932f29c1 100644 --- a/tests/unit/utils.py +++ b/tests/unit/utils.py @@ -211,16 +211,20 @@ def _add_cases(case_dir: Path, calculation_type: str, **kwargs): def pytest_cases(get_batch_cases: bool = False, data_dir: str | None = None, test_cases: list[str] | None = None): relevant_calculations = [data_dir] if data_dir is not None else ["power_flow", "state_estimation", "short_circuit"] + cases = [] for calculation_type in relevant_calculations: test_case_paths = get_test_case_paths(calculation_type=calculation_type, test_cases=test_cases) for case_name, case_dir in test_case_paths.items(): - yield from _add_cases( - case_name=case_name, - case_dir=case_dir, - calculation_type=calculation_type, - is_batch=get_batch_cases, + cases.extend( + _add_cases( + case_name=case_name, + case_dir=case_dir, + calculation_type=calculation_type, + is_batch=get_batch_cases, + ) ) + return cases def bool_params(true_id: str, false_id: str | None = None, **kwargs): diff --git a/tests/unit/validation/test_validation_functions.py b/tests/unit/validation/test_validation_functions.py index 9cb6ebb69b..88be0c2eae 100644 --- a/tests/unit/validation/test_validation_functions.py +++ b/tests/unit/validation/test_validation_functions.py @@ -468,7 +468,7 @@ def test_validate_required_values_asym_calculation(): assert MissingValueError(CT.shunt, AT.b0, [NaN]) in required_values_errors -@pytest.mark.parametrize("fault_types", product(list(FaultType), list(FaultType))) +@pytest.mark.parametrize("fault_types", list(product(list(FaultType), list(FaultType)))) def test_validate_fault_sc_calculation(fault_types): line = initialize_array(DatasetType.input, CT.line, 1) shunt = initialize_array(DatasetType.input, CT.shunt, 1) From 98bc7487a6165cdee9f6ef5c15fcef19df29587e Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Fri, 28 Aug 2026 09:36:21 +0200 Subject: [PATCH 21/23] fix order of operations Signed-off-by: Martijn Govers --- .../include/power_grid_model/main_core/output.hpp | 12 ++++++------ .../main_core/test_main_core_output.cpp | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp index 1a36615c18..781fb84eb4 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/output.hpp @@ -135,24 +135,24 @@ constexpr auto output_result(Component const& link, MainModelState> const& math_output, Idx2D const& topo_id) { using sym = decode_symmetry_v; - if (!link.edge_status()) { - return link.template get_energized_zero_output(); - } if (topo_id.group == disconnected) { return link.template get_null_output(); } + if (!link.edge_status()) { + return link.template get_energized_zero_output(); + } return link.template get_output(math_output.supernode_output[topo_id.group].link[topo_id.pos]); } template Component, class ComponentContainer, short_circuit_solver_output_type SolverOutputType> requires model_component_state_c inline auto output_result(Component const& link, MainModelState const& /* state */, MathOutput> const& math_output, Idx2D const& topo_id) { - if (!link.edge_status()) { - return link.get_energized_zero_sc_output(); - } if (topo_id.group == disconnected) { return link.get_null_sc_output(); } + if (!link.edge_status()) { + return link.get_energized_zero_sc_output(); + } return link.get_sc_output(math_output.supernode_output[topo_id.group].link[topo_id.pos]); } diff --git a/tests/cpp_unit_tests/main_core/test_main_core_output.cpp b/tests/cpp_unit_tests/main_core/test_main_core_output.cpp index ee419d0b90..bccfb3a94b 100644 --- a/tests/cpp_unit_tests/main_core/test_main_core_output.cpp +++ b/tests/cpp_unit_tests/main_core/test_main_core_output.cpp @@ -282,7 +282,7 @@ TEST_CASE("Test main core output") { CHECK(output[0].i_from(0) == doctest::Approx(base_power_3p / 10e3 / sqrt3)); CHECK(output[0].i_to(0) == doctest::Approx(2.0 * base_power_3p / 20e3 / sqrt3)); CHECK(output[1].id == 1); - CHECK(output[1].energized == status_off); + CHECK(output[1].energized == status_on); CHECK(output[2].id == 2); CHECK(output[2].energized == status_off); } From 0f45f212dbf45e8cf61d4d15adff40bc3de03deb Mon Sep 17 00:00:00 2001 From: Martijn Govers Date: Fri, 28 Aug 2026 09:37:50 +0200 Subject: [PATCH 22/23] fix temporary Signed-off-by: Martijn Govers --- .../include/power_grid_model/main_core/math_output_queries.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/math_output_queries.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/math_output_queries.hpp index a5b7e5e26b..15d30677d6 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/math_output_queries.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/math_output_queries.hpp @@ -21,7 +21,7 @@ namespace power_grid_model::main_core { template constexpr auto const& get_component_output(MathOutput> const& math_output, - Idx2D const& math_id) { + Idx2D math_id) { auto const& solver_output = math_output.solver_output[math_id.group]; // TODO(mgovers): cleanup v2: change back to std::derived_from From afc3a1d291129c9920aaade6e5a64344d3abcd42 Mon Sep 17 00:00:00 2001 From: Santiago Figueroa Manrique Date: Mon, 31 Aug 2026 14:49:28 +0200 Subject: [PATCH 23/23] fix link steady state output Signed-off-by: Santiago Figueroa Manrique --- .../include/power_grid_model/component/link.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/component/link.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/component/link.hpp index e142423680..c531a9c9a5 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/component/link.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/component/link.hpp @@ -31,7 +31,7 @@ class Link final : public Edge { double phase_shift() const override { return 0.0; } template BranchOutput get_energized_zero_output() const { - BranchOutput output{}; + BranchOutput output = get_null_output(); static_cast(output) = base_output(true); return output; }