From 07f87d10c88a8737b67d21e93ace440ef8645fb0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Aug 2026 04:04:52 +0000 Subject: [PATCH 01/15] Bump astral-sh/setup-uv in the all-dependencies group Bumps the all-dependencies group with 1 update: [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv). Updates `astral-sh/setup-uv` from 9.0.0 to 10.0.1 - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/c771a70e6277c0a99b617c7a806ffedaca235ff9...20cfd1bf945f4377ade1205e4dbc17946fc9a30d) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-version: 10.0.1 dependency-type: direct:production update-type: version-update:semver-major dependency-group: all-dependencies ... Signed-off-by: dependabot[bot] --- .github/workflows/build-test-release.yml | 8 ++++---- .github/workflows/check-build-reproducibility.yml | 6 +++--- .github/workflows/check-code-quality.yml | 2 +- .github/workflows/clang-tidy.yml | 2 +- .github/workflows/sonar.yml | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-test-release.yml b/.github/workflows/build-test-release.yml index 799cceb1e5..5d97b6a285 100644 --- a/.github/workflows/build-test-release.yml +++ b/.github/workflows/build-test-release.yml @@ -25,7 +25,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install uv - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 - name: Set PyPI Version uses: PowerGridModel/pgm-version-bump@65909ad17d7dd3d5330b31894bb17b57024d593f # v0.1.5 @@ -77,7 +77,7 @@ jobs: sudo ln -s /usr/bin/g++-14 /usr/local/bin/g++ - name: Install uv - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 - name: Install pgm-build-deps run: | @@ -101,7 +101,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install uv - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 - name: Install pgm-build-deps run: | @@ -149,7 +149,7 @@ jobs: xcode-version: latest-stable - name: Install uv - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 - name: Install pgm-build-deps run: | diff --git a/.github/workflows/check-build-reproducibility.yml b/.github/workflows/check-build-reproducibility.yml index 23f07628cb..1ec11e62b1 100644 --- a/.github/workflows/check-build-reproducibility.yml +++ b/.github/workflows/check-build-reproducibility.yml @@ -41,7 +41,7 @@ jobs: sudo ln -s /usr/bin/g++-14 /usr/local/bin/g++ - name: Install uv - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 - name: Install pgm-build-deps run: | @@ -89,7 +89,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install uv - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 - name: Install pgm-build-deps run: | @@ -153,7 +153,7 @@ jobs: xcode-version: latest-stable - name: Install uv - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 - name: Install pgm-build-deps run: | diff --git a/.github/workflows/check-code-quality.yml b/.github/workflows/check-code-quality.yml index 6c002a626d..b751c39f72 100644 --- a/.github/workflows/check-code-quality.yml +++ b/.github/workflows/check-code-quality.yml @@ -28,7 +28,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install uv - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 - name: Install most linters from uv run: | diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index 6745d504fc..1320ead0f3 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -59,7 +59,7 @@ jobs: sudo ln -s /usr/bin/clang-tidy-18 /usr/local/bin/clang-tidy - name: Install uv - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 - name: Install pgm-build-deps run: | diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 55174c864c..7f4ab89db9 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -38,7 +38,7 @@ jobs: sudo ln -s /usr/bin/clang-18 /usr/local/bin/clang sudo ln -s /usr/bin/clang++-18 /usr/local/bin/clang++ - name: Install uv - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 - name: Install pgm-build-deps run: | uv tool install pgm-build-deps From e7bcce64ff87f39afe73e50a9285d831d81b758e Mon Sep 17 00:00:00 2001 From: Jerry Jinfeng Guo Date: Tue, 25 Aug 2026 10:25:07 +0200 Subject: [PATCH 02/15] initial attempt; some tests are failing ofc Signed-off-by: Jerry Jinfeng Guo --- .../power_grid_model/main_core/topology.hpp | 43 ++++++++++++++++--- .../power_grid_model/main_model_impl.hpp | 11 ++++- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topology.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topology.hpp index f2dca1df70..ef7dcf9f3f 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topology.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topology.hpp @@ -201,6 +201,26 @@ constexpr void register_topology_components(ComponentContainer const& components [](Regulator const& regulator) { return regulator.regulated_object_type(); }); } +// new path: only Branch-derived types (not Link) in branch_node_idx +template Component, class ComponentContainer> + requires common::component_container_c +constexpr void register_topology_components(ComponentContainer const& components, ComponentTopology& comp_topo) { + apply_registration(components, comp_topo.branch_node_idx, [&components](Branch const& branch) { + return BranchIdx{get_component_sequence_idx(components, branch.from_node()), + get_component_sequence_idx(components, branch.to_node())}; + }); +} + +// new path: Link in link_node_idx so topology builder can create super-nodes +template Component, class ComponentContainer> + requires common::component_container_c +constexpr void register_topology_components(ComponentContainer const& components, ComponentTopology& comp_topo) { + apply_registration(components, comp_topo.link_node_idx, [&components](Link const& link) { + return BranchIdx{get_component_sequence_idx(components, link.from_node()), + get_component_sequence_idx(components, link.to_node())}; + }); +} + template Component, class ComponentContainer> requires common::component_container_c constexpr void register_connections_components(ComponentContainer const& components, ComponentConnections& comp_conn) { @@ -232,16 +252,27 @@ constexpr void register_connections_components(ComponentContainer const& compone } // namespace detail template - requires common::component_container_c -ComponentTopology construct_topology(typename ModelType::ComponentContainer const& components) { + requires common::component_container_c +ComponentTopology construct_topology(typename ModelType::ComponentContainer const& components, + bool has_node_injection_sensors = false) { ComponentTopology comp_topo; using TopologyTypesTuple = ModelType::TopologyTypesTuple; main_core::utils::run_functor_with_tuple_return_void( - [&components, &comp_topo]() { - detail::register_topology_components(components, comp_topo); + [&components, &comp_topo, has_node_injection_sensors]() { + if constexpr (!std::same_as) { + detail::register_topology_components(components, comp_topo); + } else if (has_node_injection_sensors) { + // old path: all edges (branches + links) in branch_node_idx + detail::register_topology_components(components, comp_topo); + } }); + if (!has_node_injection_sensors) { + // new path: branches only in branch_node_idx, links in link_node_idx + detail::register_topology_components(components, comp_topo); + detail::register_topology_components(components, comp_topo); + } return comp_topo; } diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp index 22f1f6ef1c..73eeca8e51 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_model_impl.hpp @@ -207,9 +207,15 @@ class MainModelImpl { construction_complete_ = true; #endif // !NDEBUG state_.components.set_construction_complete(); + has_node_injection_sensors_ = detect_node_injection_sensors(); + state_.comp_topo = std::make_shared( + main_core::construct_topology(state_.components, has_node_injection_sensors_)); + } - state_.comp_topo = - std::make_shared(main_core::construct_topology(state_.components)); + bool detect_node_injection_sensors() const { + return std::ranges::any_of( + state_.components.template citer(), + [](GenericPowerSensor const& sensor) { return sensor.get_terminal_type() == MeasuredTerminalType::node; }); } public: @@ -474,6 +480,7 @@ class MainModelImpl { OwnedUpdateDataset cached_inverse_update_{}; UpdateChange cached_state_changes_{}; + bool has_node_injection_sensors_{false}; #ifndef NDEBUG // construction_complete is used for debug assertions only bool construction_complete_{false}; From 58d7218c08633fbb96c69d6c176278d7b4e1a37b Mon Sep 17 00:00:00 2001 From: Jerry Guo Date: Tue, 1 Sep 2026 10:15:59 +0200 Subject: [PATCH 03/15] api all updates still failing Signed-off-by: Jerry Guo --- .../calculation_preparation.hpp | 7 +++- .../power_grid_model/main_core/topology.hpp | 40 +++++++++++++++++-- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/calculation_preparation.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/calculation_preparation.hpp index 55fd039fbc..d30c678858 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/calculation_preparation.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/calculation_preparation.hpp @@ -134,7 +134,12 @@ inline void rebuild_topology(typename ModelType::MainModelState& state, SolverPr // clear old solvers reset_solvers(state, solver_context, solvers_cache_status); - ComponentConnections const comp_conn = main_core::construct_components_connections(state.components); + // Determine the path based on whether link_node_idx is populated (new path) or empty (old path) + // link_node_idx being non-empty means new path where links are separated (no node injection sensors) + // link_node_idx being empty means old path where all edges treated as branches (has node injection sensors) + bool const has_node_injection_sensors = state.comp_topo->link_node_idx.empty(); + ComponentConnections const comp_conn = + main_core::construct_components_connections(state.components, has_node_injection_sensors); // re build assert((state.comp_topo->link_node_idx.empty() || diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topology.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topology.hpp index ef7dcf9f3f..ef58eceef1 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topology.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topology.hpp @@ -249,6 +249,27 @@ constexpr void register_connections_components(ComponentContainer const& compone [](Source const& source) { return source.status(); }); } +// new path: only Branch-derived types (not Link) in branch_connected +template Component, class ComponentContainer> + requires common::component_container_c +constexpr void register_connections_components(ComponentContainer const& components, ComponentConnections& comp_conn) { + apply_registration(components, comp_conn.branch_connected, [](Branch const& branch) { + return BranchConnected{status_to_int(branch.from_status()), status_to_int(branch.to_status())}; + }); + apply_registration(components, comp_conn.branch_phase_shift, + [](Branch const& branch) { return branch.phase_shift(); }); +} + +// new path: Link in link_connected so topology builder can handle super-nodes +template Component, class ComponentContainer> + requires common::component_container_c +constexpr void register_connections_components(ComponentContainer const& components, ComponentConnections& comp_conn) { + apply_registration(components, comp_conn.link_connected, [](Link const& link) { + return BranchConnected{status_to_int(link.from_status()), status_to_int(link.to_status())}; + }); + // Note: Links always have zero phase shift, so no phase_shift registration needed +} + } // namespace detail template @@ -277,14 +298,25 @@ ComponentTopology construct_topology(typename ModelType::ComponentContainer cons } template - requires common::component_container_c -ComponentConnections construct_components_connections(typename ModelType::ComponentContainer const& components) { + requires common::component_container_c +ComponentConnections construct_components_connections(typename ModelType::ComponentContainer const& components, + bool has_node_injection_sensors = false) { ComponentConnections comp_conn; using TopologyConnectionTypesTuple = ModelType::TopologyConnectionTypesTuple; main_core::utils::run_functor_with_tuple_return_void( - [&components, &comp_conn]() { - detail::register_connections_components(components, comp_conn); + [&components, &comp_conn, has_node_injection_sensors]() { + if constexpr (!std::same_as) { + detail::register_connections_components(components, comp_conn); + } else if (has_node_injection_sensors) { + // old path: all edges (branches + links) in branch_connected + detail::register_connections_components(components, comp_conn); + } }); + if (!has_node_injection_sensors) { + // new path: branches only in branch_connected, links in link_connected + detail::register_connections_components(components, comp_conn); + detail::register_connections_components(components, comp_conn); + } return comp_conn; } From 42317eeb7f677afb157b27d7143451cf81ddb546 Mon Sep 17 00:00:00 2001 From: Jerry Guo Date: Tue, 1 Sep 2026 11:11:31 +0200 Subject: [PATCH 04/15] touched on the topological node output; validation tests are still failing Signed-off-by: Jerry Guo --- .../calculation_parameters.hpp | 7 +- .../power_grid_model/main_core/output.hpp | 25 ++++++ .../main_core/state_queries.hpp | 19 ++++- .../main_core/topological_node_output.hpp | 80 ++++++++++++++++++- .../include/power_grid_model/topology.hpp | 4 + 5 files changed, 128 insertions(+), 7 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 778743f508..8adfc91186 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 @@ -410,13 +410,13 @@ template struct SupernodeOutput; template struct SupernodeOutput { using sym = decode_symmetry_v; - ComplexValueVector bus_injection; // user bus output - BranchSolverOutput branch; // user link + ComplexValueVector bus_injection; // user bus output + std::vector> link; // user link }; template struct SupernodeOutput { using sym = decode_symmetry_v; - BranchShortCircuitSolverOutput branch; // user link + std::vector> link; // user link }; template struct MathOutput { @@ -568,6 +568,7 @@ struct ComponentToMathCoupling { struct TopologicalComponentToMathCoupling { std::vector node; std::vector branch; + std::vector link; // NEW path: links have topo-node coupling instead of math coupling std::vector branch3; std::vector shunt; std::vector load_gen; 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 32cc803bf1..8493b48d7b 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 @@ -87,6 +87,7 @@ constexpr auto output_result(Component const& node, MainModelState 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; @@ -97,6 +98,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(); @@ -104,6 +106,29 @@ inline auto output_result(Component const& branch, std::vector return branch.get_sc_output(solver_output[math_id.group].branch[math_id.pos]); } +// output link - uses supernode output +template 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 (topo_id.group == disconnected) { + return link.template get_null_output(); + } + // Access link output from supernode_output, not solver_output.branch + 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 (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 branch3 template Component, steady_state_solver_output_type SolverOutputType> constexpr auto output_result(Component const& branch3, std::vector const& solver_output, diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/state_queries.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/state_queries.hpp index f6529e5eac..0b19da7f30 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/state_queries.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/state_queries.hpp @@ -57,11 +57,20 @@ constexpr auto get_math_id(MainModelState const& state, Idx } template ComponentType, class ComponentContainer> - requires model_component_state_c + requires model_component_state_c && + (!std::same_as) constexpr auto get_math_id(MainModelState const& state, Idx topology_sequence_idx) { return state.topo_comp_coup->branch[topology_sequence_idx]; } +template ComponentType, class ComponentContainer> + requires model_component_state_c +constexpr auto get_math_id(MainModelState const& state, Idx topology_sequence_idx) { + // Links don't have math IDs; they map to topological nodes instead + // Return the topo-node coupling for this link + return state.reduced_topology->topo_node_coup.coupling.user_links_to_topo_nodes[topology_sequence_idx]; +} + template ComponentType, class ComponentContainer> requires model_component_state_c constexpr auto get_math_id(MainModelState const& state, Idx topology_sequence_idx) { @@ -93,11 +102,17 @@ constexpr auto comp_base_sequence_cbegin(MainModelState cons } template Component, class ComponentContainer> - requires model_component_state_c + requires model_component_state_c && (!std::same_as) constexpr auto comp_base_sequence_cbegin(MainModelState const& state) { return state.topo_comp_coup->branch.cbegin() + get_component_sequence_offset(state.components); } +template Component, class ComponentContainer> + requires model_component_state_c +constexpr auto comp_base_sequence_cbegin(MainModelState const& state) { + return state.reduced_topology->topo_node_coup.coupling.user_links_to_topo_nodes.cbegin(); +} + template Component, class ComponentContainer> requires model_component_state_c constexpr auto comp_base_sequence_cbegin(MainModelState const& state) { 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 4f2565430a..cd5128dcfd 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 @@ -75,9 +75,13 @@ solve_topological_nodes(State const& state, MathOutput std::vector> supernode_output = state.reduced_topology->topo_node_coup.topo_nodes | std::views::transform([](auto const& topo_node) -> SupernodeOutput { - (void)topo_node; // suppress unused variable warning when not steady-state solver output if constexpr (steady_state_solver_output_type) { - return {.bus_injection = ComplexValueVector(topo_node.user_nodes.size()), .branch = {}}; + auto const n_links = narrow_cast(std::ranges::ssize(topo_node.user_links)); + return {.bus_injection = ComplexValueVector(topo_node.user_nodes.size()), + .link = std::vector>(n_links)}; + } else if constexpr (short_circuit_solver_output_type) { + auto const n_links = narrow_cast(std::ranges::ssize(topo_node.user_links)); + return {.link = std::vector>(n_links)}; } else { return {}; } @@ -104,6 +108,78 @@ solve_topological_nodes(State const& state, MathOutput add_appliance_injection(state, math_output, accumulate_injection); } }); + + // Solve for link flows within each topological node + for (auto const& [topo_idx, topo_node] : enumerate(state.reduced_topology->topo_node_coup.topo_nodes)) { + if (topo_node.user_links.empty()) { + continue; // No links in this topological node + } + + // Build mapping from global user node index to local index within this topological node + std::unordered_map global_to_local; + for (auto const& [local_idx, global_idx] : enumerate(topo_node.user_nodes)) { + global_to_local[global_idx] = local_idx; + } + + // Remap user_links to use local indices, filtering out disconnected links + std::vector local_links; + std::vector link_mapping; // Maps local_links indices to original user_links indices + local_links.reserve(topo_node.user_links.size()); + link_mapping.reserve(topo_node.user_links.size()); + + for (auto const& [link_idx, link] : enumerate(topo_node.user_links)) { + auto const& [from_global, to_global] = link; + // Skip links where either side is disconnected + if (from_global == disconnected || to_global == disconnected) { + continue; + } + local_links.push_back(BranchIdx{global_to_local.at(from_global), global_to_local.at(to_global)}); + link_mapping.push_back(link_idx); + } + + // If all links are disconnected, set null outputs and continue + if (local_links.empty()) { + for (auto& link_output : supernode_output[topo_idx].link) { + link_output = BranchSolverOutput{}; // Zero/null output + } + continue; + } + + // Prepare node loads for link solver (always uses DoubleComplex = std::complex) + std::vector node_loads; + node_loads.reserve(topo_node.user_nodes.size()); + + if constexpr (is_symmetric_v) { + // Symmetric: direct copy + for (auto const& injection : supernode_output[topo_idx].bus_injection) { + node_loads.push_back(injection); + } + } else { + // Asymmetric: use average of 3 phases + for (auto const& injection : supernode_output[topo_idx].bus_injection) { + node_loads.push_back((injection(0) + injection(1) + injection(2)) / 3.0); + } + } + + // Call link solver with local indices + auto const link_flows = link_solver::compute_loads_link_elements(local_links, node_loads); + + // Convert link solver output to BranchSolverOutput format, placing them at correct indices + for (auto const& [local_idx, flow] : enumerate(link_flows)) { + auto const original_link_idx = link_mapping[local_idx]; + if constexpr (is_symmetric_v) { + // Symmetric: direct assignment + supernode_output[topo_idx].link[original_link_idx] = BranchSolverOutput{ + .s_f = flow, + .s_t = -flow // Power out at to-side is negative of power in at from-side + }; + } else { + // Asymmetric: replicate to 3 phases + supernode_output[topo_idx].link[original_link_idx] = BranchSolverOutput{ + .s_f = ComplexValue{flow, flow, flow}, .s_t = ComplexValue{-flow, -flow, -flow}}; + } + } + } } return supernode_output; diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/topology.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/topology.hpp index 392ad6e306..2fac944d94 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/topology.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/topology.hpp @@ -141,6 +141,7 @@ class Topology { build_sparse_graph(); dfs_search(); couple_branch(); + // Links are not coupled here; they're merged into topological nodes in supernodes.hpp couple_all_appliance(); couple_voltage_regulators(); couple_sensors(); @@ -179,6 +180,9 @@ class Topology { comp_coup_.node.resize(comp_topo_.n_node_total(), unknown_idx2d); comp_coup_.branch.resize(comp_topo_.branch_node_idx.size(), unknown_idx2d); + // Link coupling is not populated here; links are merged into topological nodes + // and accessed via reduced_topology->topo_node_coup.coupling.user_links_to_topo_nodes + comp_coup_.link.clear(); comp_coup_.branch3.resize(comp_topo_.branch3_node_idx.size(), unknown_idx2d_branch3); comp_coup_.shunt.resize(comp_topo_.shunt_node_idx.size(), unknown_idx2d); comp_coup_.load_gen.resize(comp_topo_.load_gen_node_idx.size(), unknown_idx2d); From 185cc17044c4e1dfca11af6f21b8845bd904f6cf Mon Sep 17 00:00:00 2001 From: Jerry Guo Date: Tue, 1 Sep 2026 15:09:53 +0200 Subject: [PATCH 05/15] remove redef Signed-off-by: Jerry Guo --- .../power_grid_model/main_core/output.hpp | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 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 d443588cb0..af9c515905 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 @@ -180,28 +180,28 @@ inline auto output_result(Component const& branch, MathOutput(math_output, math_id)); } -// output link - uses supernode output -template 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 (topo_id.group == disconnected) { - return link.template get_null_output(); - } - // Access link output from supernode_output, not solver_output.branch - 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 (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 link - uses supernode output +// template 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 (topo_id.group == disconnected) { +// return link.template get_null_output(); +// } +// // Access link output from supernode_output, not solver_output.branch +// 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 (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 branch3 template Component, steady_state_solver_output_type SolverOutputType> From 9ad2ec5d8f252e6d08f33d18da910eeda1f7ab21 Mon Sep 17 00:00:00 2001 From: Santiago Figueroa Manrique Date: Wed, 2 Sep 2026 14:25:23 +0200 Subject: [PATCH 06/15] [WIP] partial fix: power flow works Signed-off-by: Santiago Figueroa Manrique --- .../calculation_parameters.hpp | 3 +- .../main_core/container_queries.hpp | 4 +- .../power_grid_model/main_core/output.hpp | 23 ---------- .../main_core/state_queries.hpp | 21 +++------ .../power_grid_model/main_core/topology.hpp | 43 ++++++++++--------- .../power_grid_model/main_core/y_bus.hpp | 10 ++--- .../include/power_grid_model/supernodes.hpp | 13 +++++- .../include/power_grid_model/topology.hpp | 3 -- tests/cpp_unit_tests/test_supernodes.cpp | 6 +-- .../params.json | 6 +-- .../params.json | 6 +-- .../params.json | 6 +-- .../merge-nodes-by-links-1-line/params.json | 6 +-- .../merge-nodes-by-links-2-lines/params.json | 6 +-- 14 files changed, 55 insertions(+), 101 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 94cd702fd7..51f010582f 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 @@ -506,7 +506,7 @@ struct ReducedComponentTopology { struct TopologicalNode { IdxVector user_nodes; - std::vector user_links; + std::vector user_links; // TODO(figueroa1395): temporarily doesn't have user nodes, but internal nodes while exploring constexpr auto is_supernode() const noexcept -> bool { return user_nodes.size() > 1 && !user_links.empty(); } }; @@ -577,7 +577,6 @@ struct ComponentToMathCoupling { struct TopologicalComponentToMathCoupling { std::vector node; std::vector branch; - std::vector link; // NEW path: links have topo-node coupling instead of math coupling std::vector branch3; std::vector shunt; std::vector load_gen; diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/container_queries.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/container_queries.hpp index 188b0af82b..1a9b51ae1c 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/container_queries.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/container_queries.hpp @@ -100,8 +100,8 @@ constexpr auto get_component_citer(ComponentContainer const& components) { namespace detail { template struct topology_base_type; -template Component> struct topology_base_type { - using type = Edge; +template Component> struct topology_base_type { + using type = Branch; }; template Component> struct topology_base_type { using type = Branch3; 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 af9c515905..e008e8b733 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 @@ -180,29 +180,6 @@ inline auto output_result(Component const& branch, MathOutput(math_output, math_id)); } -// // output link - uses supernode output -// template 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 (topo_id.group == disconnected) { -// return link.template get_null_output(); -// } -// // Access link output from supernode_output, not solver_output.branch -// 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 (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 branch3 template Component, steady_state_solver_output_type SolverOutputType> constexpr auto output_result(Component const& branch3, MathOutput> const& math_output, diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/state_queries.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/state_queries.hpp index 0b19da7f30..daa20e48fe 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/state_queries.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/state_queries.hpp @@ -22,7 +22,7 @@ #include namespace power_grid_model::main_core { -template ComponentType, class ComponentContainer> +template ComponentType, class ComponentContainer> requires model_component_state_c constexpr auto get_branch_nodes(MainModelState const& state, Idx topology_sequence_idx) { return state.comp_topo->branch_node_idx[topology_sequence_idx]; @@ -56,19 +56,10 @@ constexpr auto get_math_id(MainModelState const& state, Idx return state.topo_comp_coup->node[topology_sequence_idx]; } -template ComponentType, class ComponentContainer> - requires model_component_state_c && - (!std::same_as) -constexpr auto get_math_id(MainModelState const& state, Idx topology_sequence_idx) { - return state.topo_comp_coup->branch[topology_sequence_idx]; -} - -template ComponentType, class ComponentContainer> +template ComponentType, class ComponentContainer> requires model_component_state_c constexpr auto get_math_id(MainModelState const& state, Idx topology_sequence_idx) { - // Links don't have math IDs; they map to topological nodes instead - // Return the topo-node coupling for this link - return state.reduced_topology->topo_node_coup.coupling.user_links_to_topo_nodes[topology_sequence_idx]; + return state.topo_comp_coup->branch[topology_sequence_idx]; } template ComponentType, class ComponentContainer> @@ -101,10 +92,10 @@ constexpr auto comp_base_sequence_cbegin(MainModelState cons return state.reduced_topology->topo_node_coup.coupling.user_nodes_to_topo_nodes.cbegin(); } -template Component, class ComponentContainer> - requires model_component_state_c && (!std::same_as) +template Component, class ComponentContainer> + requires model_component_state_c constexpr auto comp_base_sequence_cbegin(MainModelState const& state) { - return state.topo_comp_coup->branch.cbegin() + get_component_sequence_offset(state.components); + return state.topo_comp_coup->branch.cbegin() + get_component_sequence_offset(state.components); } template Component, class ComponentContainer> diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topology.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topology.hpp index ef58eceef1..26646b872d 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topology.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/topology.hpp @@ -52,7 +52,7 @@ constexpr void register_topology_components(ComponentContainer const& components } template Component, class ComponentContainer> - requires common::component_container_c + requires common::component_container_c constexpr void register_topology_components(ComponentContainer const& components, ComponentTopology& comp_topo) { apply_registration(components, comp_topo.branch_node_idx, [&components](Edge const& edge) { return BranchIdx{get_component_sequence_idx(components, edge.from_node()), @@ -205,7 +205,7 @@ constexpr void register_topology_components(ComponentContainer const& components template Component, class ComponentContainer> requires common::component_container_c constexpr void register_topology_components(ComponentContainer const& components, ComponentTopology& comp_topo) { - apply_registration(components, comp_topo.branch_node_idx, [&components](Branch const& branch) { + apply_registration(components, comp_topo.branch_node_idx, [&components](Component const& branch) { return BranchIdx{get_component_sequence_idx(components, branch.from_node()), get_component_sequence_idx(components, branch.to_node())}; }); @@ -215,14 +215,14 @@ constexpr void register_topology_components(ComponentContainer const& components template Component, class ComponentContainer> requires common::component_container_c constexpr void register_topology_components(ComponentContainer const& components, ComponentTopology& comp_topo) { - apply_registration(components, comp_topo.link_node_idx, [&components](Link const& link) { + apply_registration(components, comp_topo.link_node_idx, [&components](Component const& link) { return BranchIdx{get_component_sequence_idx(components, link.from_node()), get_component_sequence_idx(components, link.to_node())}; }); } template Component, class ComponentContainer> - requires common::component_container_c + requires common::component_container_c constexpr void register_connections_components(ComponentContainer const& components, ComponentConnections& comp_conn) { apply_registration(components, comp_conn.branch_connected, [](Edge const& edge) { return BranchConnected{status_to_int(edge.from_status()), status_to_int(edge.to_status())}; @@ -253,18 +253,18 @@ constexpr void register_connections_components(ComponentContainer const& compone template Component, class ComponentContainer> requires common::component_container_c constexpr void register_connections_components(ComponentContainer const& components, ComponentConnections& comp_conn) { - apply_registration(components, comp_conn.branch_connected, [](Branch const& branch) { + apply_registration(components, comp_conn.branch_connected, [](Component const& branch) { return BranchConnected{status_to_int(branch.from_status()), status_to_int(branch.to_status())}; }); - apply_registration(components, comp_conn.branch_phase_shift, - [](Branch const& branch) { return branch.phase_shift(); }); + apply_registration(components, comp_conn.branch_phase_shift, + [](Branch const& branch) { return branch.phase_shift(); }); } // new path: Link in link_connected so topology builder can handle super-nodes template Component, class ComponentContainer> requires common::component_container_c constexpr void register_connections_components(ComponentContainer const& components, ComponentConnections& comp_conn) { - apply_registration(components, comp_conn.link_connected, [](Link const& link) { + apply_registration(components, comp_conn.link_connected, [](Component const& link) { return BranchConnected{status_to_int(link.from_status()), status_to_int(link.to_status())}; }); // Note: Links always have zero phase shift, so no phase_shift registration needed @@ -272,24 +272,25 @@ constexpr void register_connections_components(ComponentContainer const& compone } // namespace detail +// entry point -> how to differentiate when a component is a sensor on a node? can i do it here? template - requires common::component_container_c + requires common::component_container_c ComponentTopology construct_topology(typename ModelType::ComponentContainer const& components, bool has_node_injection_sensors = false) { ComponentTopology comp_topo; using TopologyTypesTuple = ModelType::TopologyTypesTuple; main_core::utils::run_functor_with_tuple_return_void( - [&components, &comp_topo, has_node_injection_sensors]() { + [&components, &comp_topo]() { if constexpr (!std::same_as) { detail::register_topology_components(components, comp_topo); - } else if (has_node_injection_sensors) { - // old path: all edges (branches + links) in branch_node_idx - detail::register_topology_components(components, comp_topo); } }); - if (!has_node_injection_sensors) { + if (has_node_injection_sensors) { + // old path: all edges (branches + links) in branch_node_idx + detail::register_topology_components(components, comp_topo); + } else { // new path: branches only in branch_node_idx, links in link_node_idx detail::register_topology_components(components, comp_topo); detail::register_topology_components(components, comp_topo); @@ -304,15 +305,15 @@ ComponentConnections construct_components_connections(typename ModelType::Compon ComponentConnections comp_conn; using TopologyConnectionTypesTuple = ModelType::TopologyConnectionTypesTuple; main_core::utils::run_functor_with_tuple_return_void( - [&components, &comp_conn, has_node_injection_sensors]() { + [&components, &comp_conn]() { if constexpr (!std::same_as) { detail::register_connections_components(components, comp_conn); - } else if (has_node_injection_sensors) { - // old path: all edges (branches + links) in branch_connected - detail::register_connections_components(components, comp_conn); } }); - if (!has_node_injection_sensors) { + if (has_node_injection_sensors) { + // old path: all edges (branches + links) in branch_connected + detail::register_connections_components(components, comp_conn); + } else { // new path: branches only in branch_connected, links in link_connected detail::register_connections_components(components, comp_conn); detail::register_connections_components(components, comp_conn); diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/y_bus.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/y_bus.hpp index 47705b24a4..c784fff379 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/y_bus.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/y_bus.hpp @@ -24,17 +24,17 @@ constexpr Idx isolated_component{-1}; namespace detail { -template ComponentType, math_model_param_c MathModelParamType, typename ComponentContainer> +template ComponentType, math_model_param_c MathModelParamType, typename ComponentContainer> constexpr void add_to_math_model_params(std::vector& math_model_param, MainModelState const& state, Idx const topology_sequence_idx) { - Idx2D const math_idx = get_math_id(state, topology_sequence_idx); + Idx2D const math_idx = get_math_id(state, topology_sequence_idx); if (math_idx.group == isolated_component) { return; } // assign parameters auto& model_params = math_model_param[math_idx.group]; - auto branch_params = state.components.template get_item_by_seq(topology_sequence_idx) + auto branch_params = state.components.template get_item_by_seq(topology_sequence_idx) .template calc_param(); if constexpr (std::derived_from>) { @@ -123,7 +123,7 @@ constexpr void add_to_math_model_params(std::vector const& / } template - requires std::derived_from || std::derived_from || + requires std::derived_from || std::derived_from || std::derived_from || std::derived_from constexpr void add_to_increment(std::vector>& increments, MainModelState const& state, Idx2D const& changed_component_idx) { @@ -193,7 +193,7 @@ inline std::vector> get_math_param(main_model_state_c auto c } // loop all branch for (Idx const i : IdxRange{std::ssize(state.comp_topo->branch_node_idx)}) { - detail::add_to_math_model_params(math_param, state, i); + detail::add_to_math_model_params(math_param, state, i); } // loop all branch3 for (Idx const i : IdxRange{std::ssize(state.comp_topo->branch3_node_idx)}) { diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/supernodes.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/supernodes.hpp index fb1c5597a7..a9680ef7d0 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/supernodes.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/supernodes.hpp @@ -129,7 +129,8 @@ inline TopologicalNodesAndCoupling create_topological_nodes(ComponentTopology co std::vector user_link_topo_node_coup = enumerate(std::views::zip(comp_topo.link_node_idx, comp_conn.link_connected)) | - std::views::transform([&node_mapping, &topo_nodes](auto const& idx_link_and_connectivity) { + std::views::transform([&node_mapping, &topo_nodes, + &user_node_topo_node_coup](auto const& idx_link_and_connectivity) { auto const& [link_idx, link_nodes_and_connectivity] = idx_link_and_connectivity; auto const& [link_nodes, link_connected] = link_nodes_and_connectivity; @@ -137,6 +138,9 @@ inline TopologicalNodesAndCoupling create_topological_nodes(ComponentTopology co auto const from_conn = link_connected[0] == 0 ? disconnected : from; auto const to_conn = link_connected[1] == 0 ? disconnected : to; + auto const from_pos = from_conn == disconnected ? disconnected : user_node_topo_node_coup[from_conn].pos; + auto const to_pos = to_conn == disconnected ? disconnected : user_node_topo_node_coup[to_conn].pos; + assert((from_conn == disconnected || to_conn == disconnected || node_mapping[from] == node_mapping[to]) && "if both sides are connected, they should belong to the same topo node"); @@ -154,9 +158,14 @@ inline TopologicalNodesAndCoupling create_topological_nodes(ComponentTopology co return Idx2D{.group = disconnected, .pos = disconnected}; } + // early out if either is disconnected since the link solver doesn't consume semi-disconnected links + if (from_conn == disconnected || to_conn == disconnected) { + return Idx2D{.group = topo_node, .pos = disconnected}; + } + auto& user_links = topo_nodes[topo_node].user_links; Idx const pos = std::ssize(user_links); - user_links.push_back(BranchIdx{from_conn, to_conn}); // can't emplace_back because BranchIdx is std::array + user_links.push_back(BranchIdx{from_pos, to_pos}); // can't emplace_back because BranchIdx is std::array return Idx2D{.group = topo_node, .pos = pos}; }) | std::ranges::to(); diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/topology.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/topology.hpp index 2fac944d94..31358f85e7 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/topology.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/topology.hpp @@ -180,9 +180,6 @@ class Topology { comp_coup_.node.resize(comp_topo_.n_node_total(), unknown_idx2d); comp_coup_.branch.resize(comp_topo_.branch_node_idx.size(), unknown_idx2d); - // Link coupling is not populated here; links are merged into topological nodes - // and accessed via reduced_topology->topo_node_coup.coupling.user_links_to_topo_nodes - comp_coup_.link.clear(); comp_coup_.branch3.resize(comp_topo_.branch3_node_idx.size(), unknown_idx2d_branch3); comp_coup_.shunt.resize(comp_topo_.shunt_node_idx.size(), unknown_idx2d); comp_coup_.load_gen.resize(comp_topo_.load_gen_node_idx.size(), unknown_idx2d); diff --git a/tests/cpp_unit_tests/test_supernodes.cpp b/tests/cpp_unit_tests/test_supernodes.cpp index 9db6780b7e..1551e7b622 100644 --- a/tests/cpp_unit_tests/test_supernodes.cpp +++ b/tests/cpp_unit_tests/test_supernodes.cpp @@ -126,8 +126,8 @@ TEST_CASE("Test Supernodes") { CHECK(topo_nodes.topo_nodes[1].user_nodes == IdxVector{2, 4}); CHECK(topo_nodes.topo_nodes[2].user_nodes == IdxVector{3, 5}); CHECK(topo_nodes.topo_nodes[0].user_links == std::vector{{0, 1}}); - CHECK(topo_nodes.topo_nodes[1].user_links == std::vector{{2, 4}}); - CHECK(topo_nodes.topo_nodes[2].user_links == std::vector{{3, 5}, {5, 3}, {3, 5}}); + CHECK(topo_nodes.topo_nodes[1].user_links == std::vector{{0, 1}}); + CHECK(topo_nodes.topo_nodes[2].user_links == std::vector{{0, 1}, {1, 0}, {0, 1}}); CHECK(topo_nodes.coupling.user_nodes_to_topo_nodes == std::vector{{.group = 0, .pos = 0}, {.group = 0, .pos = 1}, @@ -165,7 +165,7 @@ TEST_CASE("Test Supernodes") { CHECK(std::ranges::equal( topo_nodes.topo_nodes | std::views::transform([](TopologicalNode const& node) -> auto& { return node.user_links; }), - std::vector>{{{0, disconnected}}, {{disconnected, 1}}, {{2, 4}}, {}, {}})); + std::vector>{{{0, disconnected}}, {{disconnected, 0}}, {{0, 1}}, {}, {}})); CHECK(std::ranges::equal(topo_nodes.coupling.user_nodes_to_topo_nodes, std::vector{{.group = 0, .pos = 0}, diff --git a/tests/data/power_flow/handling-of-links/merge-nodes-by-links-1-line-line-to-super-node-itself-II/params.json b/tests/data/power_flow/handling-of-links/merge-nodes-by-links-1-line-line-to-super-node-itself-II/params.json index 1da92714a0..e59d4a95ab 100644 --- a/tests/data/power_flow/handling-of-links/merge-nodes-by-links-1-line-line-to-super-node-itself-II/params.json +++ b/tests/data/power_flow/handling-of-links/merge-nodes-by-links-1-line-line-to-super-node-itself-II/params.json @@ -1,9 +1,5 @@ { "calculation_method": ["newton_raphson"], "rtol": 3e-7, - "atol": 1e-7, - "xfail": { - "raises": "AssertionError", - "reason": "The handling of links is not fully implemented." - } + "atol": 1e-7 } diff --git a/tests/data/power_flow/handling-of-links/merge-nodes-by-links-1-line-line-to-super-node-itself/params.json b/tests/data/power_flow/handling-of-links/merge-nodes-by-links-1-line-line-to-super-node-itself/params.json index 1da92714a0..e59d4a95ab 100644 --- a/tests/data/power_flow/handling-of-links/merge-nodes-by-links-1-line-line-to-super-node-itself/params.json +++ b/tests/data/power_flow/handling-of-links/merge-nodes-by-links-1-line-line-to-super-node-itself/params.json @@ -1,9 +1,5 @@ { "calculation_method": ["newton_raphson"], "rtol": 3e-7, - "atol": 1e-7, - "xfail": { - "raises": "AssertionError", - "reason": "The handling of links is not fully implemented." - } + "atol": 1e-7 } diff --git a/tests/data/power_flow/handling-of-links/merge-nodes-by-links-1-line-with-line-to-self/params.json b/tests/data/power_flow/handling-of-links/merge-nodes-by-links-1-line-with-line-to-self/params.json index 1da92714a0..e59d4a95ab 100644 --- a/tests/data/power_flow/handling-of-links/merge-nodes-by-links-1-line-with-line-to-self/params.json +++ b/tests/data/power_flow/handling-of-links/merge-nodes-by-links-1-line-with-line-to-self/params.json @@ -1,9 +1,5 @@ { "calculation_method": ["newton_raphson"], "rtol": 3e-7, - "atol": 1e-7, - "xfail": { - "raises": "AssertionError", - "reason": "The handling of links is not fully implemented." - } + "atol": 1e-7 } diff --git a/tests/data/power_flow/handling-of-links/merge-nodes-by-links-1-line/params.json b/tests/data/power_flow/handling-of-links/merge-nodes-by-links-1-line/params.json index 1da92714a0..e59d4a95ab 100644 --- a/tests/data/power_flow/handling-of-links/merge-nodes-by-links-1-line/params.json +++ b/tests/data/power_flow/handling-of-links/merge-nodes-by-links-1-line/params.json @@ -1,9 +1,5 @@ { "calculation_method": ["newton_raphson"], "rtol": 3e-7, - "atol": 1e-7, - "xfail": { - "raises": "AssertionError", - "reason": "The handling of links is not fully implemented." - } + "atol": 1e-7 } diff --git a/tests/data/power_flow/handling-of-links/merge-nodes-by-links-2-lines/params.json b/tests/data/power_flow/handling-of-links/merge-nodes-by-links-2-lines/params.json index 1da92714a0..e59d4a95ab 100644 --- a/tests/data/power_flow/handling-of-links/merge-nodes-by-links-2-lines/params.json +++ b/tests/data/power_flow/handling-of-links/merge-nodes-by-links-2-lines/params.json @@ -1,9 +1,5 @@ { "calculation_method": ["newton_raphson"], "rtol": 3e-7, - "atol": 1e-7, - "xfail": { - "raises": "AssertionError", - "reason": "The handling of links is not fully implemented." - } + "atol": 1e-7 } From 6fdc16905456e4d16b6ed1b7e4c932a4d9aceacb Mon Sep 17 00:00:00 2001 From: Santiago Figueroa Manrique Date: Wed, 2 Sep 2026 15:56:03 +0200 Subject: [PATCH 07/15] SE working Signed-off-by: Santiago Figueroa Manrique --- .../power_grid_model/main_core/output.hpp | 8 ++++---- .../main_core/state_queries.hpp | 10 +++++++++- .../power_grid_model/main_core/y_bus.hpp | 18 ++++++++++++++---- 3 files changed, 27 insertions(+), 9 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 e008e8b733..ba424dd2a1 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 @@ -132,11 +132,11 @@ 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 */, +constexpr auto output_result(Component const& link, MainModelState const& state, MathOutput> const& math_output, Idx2D const& topo_id) { using sym = decode_symmetry_v; - if (topo_id.group == disconnected) { + if (topo_id.group == disconnected || state.topo_comp_coup->node[topo_id.group].group == disconnected) { return link.template get_null_output(); } if (!link.edge_status()) { @@ -146,9 +146,9 @@ constexpr auto output_result(Component const& link, MainModelState Component, class ComponentContainer, short_circuit_solver_output_type SolverOutputType> requires model_component_state_c -inline auto output_result(Component const& link, MainModelState const& /* state */, +inline auto output_result(Component const& link, MainModelState const& state, MathOutput> const& math_output, Idx2D const& topo_id) { - if (topo_id.group == disconnected) { + if (topo_id.group == disconnected || state.topo_comp_coup->node[topo_id.group].group == disconnected) { return link.get_null_sc_output(); } if (!link.edge_status()) { diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/state_queries.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/state_queries.hpp index daa20e48fe..cf069bd5c8 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/state_queries.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/state_queries.hpp @@ -101,7 +101,15 @@ constexpr auto comp_base_sequence_cbegin(MainModelState cons template Component, class ComponentContainer> requires model_component_state_c constexpr auto comp_base_sequence_cbegin(MainModelState const& state) { - return state.reduced_topology->topo_node_coup.coupling.user_links_to_topo_nodes.cbegin(); + 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)) { + // new path: links are not branches + return link_topo_ids.cbegin(); + } else { + // legacy path: links are branches + return state.topo_comp_coup->branch.cbegin() + get_component_sequence_offset(state.components); + } } template Component, class ComponentContainer> diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/y_bus.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/y_bus.hpp index c784fff379..030e1db5d2 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/y_bus.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/y_bus.hpp @@ -10,6 +10,7 @@ #include "../component/branch.hpp" #include "../component/branch3.hpp" #include "../component/component.hpp" +#include "../component/edge.hpp" #include "../math_solver/y_bus.hpp" #include "container_queries.hpp" #include "math_state.hpp" @@ -24,7 +25,8 @@ constexpr Idx isolated_component{-1}; namespace detail { -template ComponentType, math_model_param_c MathModelParamType, typename ComponentContainer> +template + requires std::same_as || std::same_as constexpr void add_to_math_model_params(std::vector& math_model_param, MainModelState const& state, Idx const topology_sequence_idx) { @@ -34,7 +36,7 @@ constexpr void add_to_math_model_params(std::vector& math_mo } // assign parameters auto& model_params = math_model_param[math_idx.group]; - auto branch_params = state.components.template get_item_by_seq(topology_sequence_idx) + auto branch_params = state.components.template get_item_by_seq(topology_sequence_idx) .template calc_param(); if constexpr (std::derived_from>) { @@ -192,8 +194,16 @@ inline std::vector> get_math_param(main_model_state_c auto c math_param[i].source_param.resize(state.math_topology[i]->n_source()); } // loop all branch - for (Idx const i : IdxRange{std::ssize(state.comp_topo->branch_node_idx)}) { - detail::add_to_math_model_params(math_param, state, i); + if (state.comp_topo->link_node_idx.empty()) { + // legacy topology: branch_node_idx contains every Edge, including Link + for (Idx const i : IdxRange{std::ssize(state.comp_topo->branch_node_idx)}) { + detail::add_to_math_model_params(math_param, state, i); + } + } else { + // reduced topology: Links are separate and branch_node_idx contains only Branch + for (Idx const i : IdxRange{std::ssize(state.comp_topo->branch_node_idx)}) { + detail::add_to_math_model_params(math_param, state, i); + } } // loop all branch3 for (Idx const i : IdxRange{std::ssize(state.comp_topo->branch3_node_idx)}) { From 22c17af3a16c3ef03548b643c65a7a4255898521 Mon Sep 17 00:00:00 2001 From: Nitish Bharambe Date: Wed, 2 Sep 2026 16:28:01 +0200 Subject: [PATCH 08/15] address fault to node logic Signed-off-by: Nitish Bharambe --- .../main_core/calculation_input_preparation.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/calculation_input_preparation.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/calculation_input_preparation.hpp index 0fe81e2e7d..22ffd7ac0c 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/calculation_input_preparation.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/calculation_input_preparation.hpp @@ -284,8 +284,10 @@ prepare_short_circuit_input(main_model_state_c auto const& state, ComponentToMat for (Idx const fault_idx : IdxRange{state.components.template size()}) { auto const& fault = state.components.template get_item_by_seq(fault_idx); if (fault.status()) { - auto const node_idx = state.components.template get_seq(fault.get_fault_object()); - auto const topo_bus_idx = state.topo_comp_coup->node[node_idx]; + auto const user_node_idx = state.components.template get_seq(fault.get_fault_object()); + auto const topo_node_idx = + state.reduced_topology->topo_node_coup.coupling.user_nodes_to_topo_nodes[user_node_idx].group; + auto const topo_bus_idx = state.topo_comp_coup->node[topo_node_idx]; if (topo_bus_idx.group >= 0) { // Consider non-isolated objects only topo_fault_indices[topo_bus_idx.group].push_back(fault_idx); From f6ee4f7c62ddd5a6efb5e4f8ac80e89fe724c45e Mon Sep 17 00:00:00 2001 From: Santiago Figueroa Manrique Date: Wed, 2 Sep 2026 16:57:11 +0200 Subject: [PATCH 09/15] fix tap regulator Signed-off-by: Santiago Figueroa Manrique --- .../power_grid_model/main_core/y_bus.hpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/y_bus.hpp b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/y_bus.hpp index 030e1db5d2..23d7121adc 100644 --- a/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/y_bus.hpp +++ b/power_grid_model_c/power_grid_model/include/power_grid_model/main_core/y_bus.hpp @@ -129,8 +129,23 @@ template std::derived_from || std::derived_from constexpr void add_to_increment(std::vector>& increments, MainModelState const& state, Idx2D const& changed_component_idx) { - Idx const topo_sequence_idx = main_core::get_topology_index(state.components, changed_component_idx); - add_to_math_model_params(increments, state, topo_sequence_idx); + if constexpr (std::derived_from) { + if (state.comp_topo->link_node_idx.empty()) { + // legacy branch_node_idx is registered from Edge + Idx const topology_idx = get_component_sequence_idx(state.components, changed_component_idx); + + add_to_math_model_params(increments, state, topology_idx); + } else { + // new branch_node_idx is registered from Branch + Idx const topology_idx = get_topology_index(state.components, changed_component_idx); + + add_to_math_model_params(increments, state, topology_idx); + } + } else { + Idx const topology_idx = get_topology_index(state.components, changed_component_idx); + + add_to_math_model_params(increments, state, topology_idx); + } } // default implementation for other components, does nothing template From 66876c0a1abd990600cff797fb3328debcbee695 Mon Sep 17 00:00:00 2001 From: Jerry Jinfeng Guo Date: Fri, 4 Sep 2026 11:53:49 +0200 Subject: [PATCH 10/15] update pf results and mark sc tests as fail Signed-off-by: Jerry Jinfeng Guo --- .../asym_output_batch.json | 8 ++++---- .../sym_output_batch.json | 8 ++++---- .../sym_output.json | 12 ++++++------ .../dummy-test-line-into-itself/params.json | 6 +++++- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/tests/data/power_flow/dummy-test-line-into-itself/asym_output_batch.json b/tests/data/power_flow/dummy-test-line-into-itself/asym_output_batch.json index 81751215f2..e11741395c 100644 --- a/tests/data/power_flow/dummy-test-line-into-itself/asym_output_batch.json +++ b/tests/data/power_flow/dummy-test-line-into-itself/asym_output_batch.json @@ -8,7 +8,7 @@ "node": [ {"id": 1, "energized": 1, "u_pu": [1.0500026617673375, 1.0500026861214569, 1.0500026124692983], "u": [6062.1931942119581, 6062.1933348205321, 6062.1929095895966], "u_angle": [-1.4832085018044952e-06, -2.0943962754744163, 2.0943933209409646], "p": [449675.58787595929, 335421.00166105793, 560453.31676893623], "q": [-981235.41197776445, -978376.30039988051, -974972.90706424485]}, {"id": 2, "energized": 1, "u_pu": [1.0489305412180272, 1.0488408010290586, 1.047862975371989], "u": [6056.003303334478, 6055.4851881118975, 6049.8397090486005], "u_angle": [-0.0031192166967468683, -2.0971289939257418, 2.0904277197067258], "p": [0.0016991138504116397, 0.0037284466839334995, -0.003119483334405209], "q": [-0.0026769212471873154, -0.0064291658102272617, -0.0016728438498193419]}, - {"id": 3, "energized": 1, "u_pu": [1.0489305377088716, 1.0488407990932342, 1.0478629702944795], "u": [6056.0032830743594, 6055.48517693541, 6049.8396797335854], "u_angle": [-0.0031192079794357627, -2.0971289867084129, 2.0904277299238556], "p": [-440102.09839980368, -330020.10079295578, -549008.40602486604], "q": [-220051.05491385818, -220013.40255902623, -219603.34729454646]} + {"id": 3, "energized": 1, "u_pu": [1.0489305411522316, 1.0488408010053642, 1.04786297529498795], "u": [6056.0033029546424, 6055.485187975122, 6049.8397086040414], "u_angle": [-0.0031192079794357627, -2.0971289867084129, 2.0904277299238556], "p": [-440102.09839980368, -330020.10079295578, -549008.40602486604], "q": [-220051.05491385818, -220013.40255902623, -219603.34729454646]} ], "line": [ {"id": 4, "energized": 1, "p_from": [447366.50582515739, 333111.91732260247, 558144.23634554166], "q_from": [-958144.59097641951, -955285.4754396549, -951882.08489887649], "i_from": [174.43180283068381, 166.88654765669233, 182.02178483504079], "s_from": [1057439.2879742936, 1011698.5168756095, 1103451.1734178273], "p_to": [-442404.25245192216, -332330.57331481267, -551303.81058868743], "q_to": [-197013.57952787445, -196972.45740831006, -196601.52011541839], "i_to": [79.968423817996026, 63.796408946461177, 96.748053573087233], "s_to": [484289.0388042354, 386318.20943002496, 585310.21627962426]}, @@ -23,7 +23,7 @@ {"id": 8, "energized": 1, "p": [2302.1548961487756, 2310.4680800382425, 2295.4022794086763], "q": [-23037.473316395561, -23040.939150568447, -23001.828741266068], "i": [3.8230190216611373, 3.8240523609288242, 3.820940358258869], "s": [23152.21574643557, 23156.492387429349, 23116.076593289967], "pf": [0.099435618662252964, 0.099776254597716843, 0.099298956297582772]} ], "source": [ - {"id": 10, "energized": 1, "p": [449675.58787595917, 335421.00166105776, 560453.31676893623], "q": [-981235.41197776434, -978376.30039988051, -974972.90706424497], "i": [178.04877122315222, 170.61090397350927, 185.50712230088268], "s": [1079366.0491467952, 1034276.2849159138, 1124579.961490781], "pf": [0.41661083210039224, 0.32430503005135358, 0.49836680001480771]} + {"id": 10, "energized": 1, "p": [449675.58787595917, 335421.00166105776, 560453.31676893623], "q": [-981235.41197776434, -978376.30039988051, -974972.90706424497], "i": [178.04877122315222, 170.61090397350927, 185.50712230088268], "s": [1079366.0491467952, 1034276.2849159138, 1124579.961490781], "pf": [0.4166108446159314, 0.3243050345416258, 0.4983668047987631]} ], "sym_load": [ {"id": 11, "energized": 1, "p": [220051.0545876445, 220013.4043685068, 219603.3609028738], "q": [110025.52729382225, 110006.7021842534, 109801.6804514369], "i": [40.624905038748508, 40.621429477109757, 40.583558330579557], "s": [246024.55828924503, 245982.4640645652, 245524.02153312269], "pf": [0.89442719099991586, 0.89442719099991586, 0.89442719099991597]} @@ -36,7 +36,7 @@ "node": [ {"id": 1, "energized": 1, "u_pu": [1.0500026617673375, 1.0500026861214569, 1.0500026124692983], "u": [6062.1931942119581, 6062.1933348205321, 6062.1929095895966], "u_angle": [-1.4832085018044952e-06, -2.0943962754744163, 2.0943933209409646], "p": [449675.58787595929, 335421.00166105793, 560453.31676893623], "q": [-981235.41197776445, -978376.30039988051, -974972.90706424485]}, {"id": 2, "energized": 1, "u_pu": [1.0489305412180272, 1.0488408010290586, 1.047862975371989], "u": [6056.003303334478, 6055.4851881118975, 6049.8397090486005], "u_angle": [-0.0031192166967468683, -2.0971289939257418, 2.0904277197067258], "p": [0.0016991138504116397, 0.0037284466839334995, -0.003119483334405209], "q": [-0.0026769212471873154, -0.0064291658102272617, -0.0016728438498193419]}, - {"id": 3, "energized": 1, "u_pu": [1.0489305377088716, 1.0488407990932342, 1.0478629702944795], "u": [6056.0032830743594, 6055.48517693541, 6049.8396797335854], "u_angle": [-0.0031192079794357627, -2.0971289867084129, 2.0904277299238556], "p": [-440102.09839980368, -330020.10079295578, -549008.40602486604], "q": [-220051.05491385818, -220013.40255902623, -219603.34729454646]} + {"id": 3, "energized": 1, "u_pu": [1.0489305411522316, 1.0488408010053642, 1.04786297529498795], "u": [6056.0033029546424, 6055.485187975122, 6049.8397086040414], "u_angle": [-0.0031192079794357627, -2.0971289867084129, 2.0904277299238556], "p": [-440102.09839980368, -330020.10079295578, -549008.40602486604], "q": [-220051.05491385818, -220013.40255902623, -219603.34729454646]} ], "line": [ {"id": 4, "energized": 1, "p_from": [447366.50582515739, 333111.91732260247, 558144.23634554166], "q_from": [-958144.59097641951, -955285.4754396549, -951882.08489887649], "i_from": [174.43180283068381, 166.88654765669233, 182.02178483504079], "s_from": [1057439.2879742936, 1011698.5168756095, 1103451.1734178273], "p_to": [-442404.25245192216, -332330.57331481267, -551303.81058868743], "q_to": [-197013.57952787445, -196972.45740831006, -196601.52011541839], "i_to": [79.968423817996026, 63.796408946461177, 96.748053573087233], "s_to": [484289.0388042354, 386318.20943002496, 585310.21627962426]}, @@ -51,7 +51,7 @@ {"id": 8, "energized": 0, "p": [0, 0, 0], "q": [0, 0, 0], "i": [0, 0, 0], "s": [0, 0, 0], "pf": [0, 0, 0]} ], "source": [ - {"id": 10, "energized": 1, "p": [449675.58787595917, 335421.00166105776, 560453.31676893623], "q": [-981235.41197776434, -978376.30039988051, -974972.90706424497], "i": [178.04877122315222, 170.61090397350927, 185.50712230088268], "s": [1079366.0491467952, 1034276.2849159138, 1124579.961490781], "pf": [0.41661083210039224, 0.32430503005135358, 0.49836680001480771]} + {"id": 10, "energized": 1, "p": [449675.58787595917, 335421.00166105776, 560453.31676893623], "q": [-981235.41197776434, -978376.30039988051, -974972.90706424497], "i": [178.04877122315222, 170.61090397350927, 185.50712230088268], "s": [1079366.0491467952, 1034276.2849159138, 1124579.961490781], "pf": [0.4166108446159314, 0.3243050345416258, 0.4983668047987631]} ], "sym_load": [ {"id": 11, "energized": 1, "p": [220051.0545876445, 220013.4043685068, 219603.3609028738], "q": [110025.52729382225, 110006.7021842534, 109801.6804514369], "i": [40.624905038748508, 40.621429477109757, 40.583558330579557], "s": [246024.55828924503, 245982.4640645652, 245524.02153312269], "pf": [0.89442719099991586, 0.89442719099991586, 0.89442719099991597]} diff --git a/tests/data/power_flow/dummy-test-line-into-itself/sym_output_batch.json b/tests/data/power_flow/dummy-test-line-into-itself/sym_output_batch.json index 0fb44f337d..03daa2b9c8 100644 --- a/tests/data/power_flow/dummy-test-line-into-itself/sym_output_batch.json +++ b/tests/data/power_flow/dummy-test-line-into-itself/sym_output_batch.json @@ -8,7 +8,7 @@ "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": 3, "energized": 1, "u_pu": 1.0485445626409264, "u": 10485.445626409264, "u_angle": -0.0032736074624537736, "p": -1319334.8476324468, "q": -659667.40050526708} + {"id": 3, "energized": 1, "u_pu": 1.048544566103154, "u": 10485.44566103154, "u_angle": -0.0032736074624537736, "p": -1319334.8476324468, "q": -659667.42426266508} ], "line": [ {"id": 4, "energized": 1, "p_from": 1338726.0924560847, "q_from": -2865446.9838841902, "i_from": 173.90559127246948, "s_from": 3162747.8508527582, "p_to": -1326242.8543147412, "q_to": -590587.19597046508, "i_to": 79.938915357945049, "s_to": 1451796.592042139}, @@ -16,7 +16,7 @@ {"id": 6, "energized": 0, "p_from": 0, "q_from": 0, "i_from": 0, "s_from": 0, "p_to": 0, "q_to": 0, "i_to": 0, "s_to": 0} ], "link": [ - {"id": 9, "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": 9, "energized": 1, "loading": 0, "p_from": 1326242.8673741999, "q_from": 590587.21313346216, "i_from": 79.938915663603737, "s_from": 1451796.5975933056, "p_to": -1326242.8577888545, "q_to": -590587.21313346256, "i_to": 79.938915663603737, "s_to": 1451796.5927362107} ], "shunt": [ {"id": 7, "energized": 1, "p": 6927.2468142502858, "q": -69272.468142502854, "i": 3.8279858566670555, "s": 69617.968880021304, "pf": 0.099503719020998901}, @@ -36,7 +36,7 @@ "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": 3, "energized": 1, "u_pu": 1.0485445626409264, "u": 10485.445626409264, "u_angle": -0.0032736074624537736, "p": -1319334.8476324468, "q": -659667.40050526708} + {"id": 3, "energized": 1, "u_pu": 1.048544566103154, "u": 10485.44566103154, "u_angle": -0.0032736074624537736, "p": -1319334.8476324468, "q": -659667.42426266508} ], "line": [ {"id": 4, "energized": 1, "p_from": 1338726.0924560847, "q_from": -2865446.9838841902, "i_from": 173.90559127246948, "s_from": 3162747.8508527582, "p_to": -1326242.8543147412, "q_to": -590587.19597046508, "i_to": 79.938915357945049, "s_to": 1451796.592042139}, @@ -44,7 +44,7 @@ {"id": 6, "energized": 1, "p_from": 3454.0105336503334, "q_from": -34540.105336503337, "i_from": 1.9113350534043847, "s_from": 34712.376257227246, "p_to": 3454.0105336503334, "q_to": -34540.105336503337, "i_to": 1.9113350534043847, "s_to": 34712.376257227246} ], "link": [ - {"id": 9, "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": 9, "energized": 1, "loading": 0, "p_from": 1326242.8673741999, "q_from": 590587.21313346216, "i_from": 79.938915663603737, "s_from": 1451796.5975933056, "p_to": -1326242.8577888545, "q_to": -590587.21313346256, "i_to": 79.938915663603737, "s_to": 1451796.5927362107} ], "shunt": [ {"id": 7, "energized": 0, "p": 0, "q": 0, "i": 0, "s": 0, "pf": 0}, diff --git a/tests/data/power_flow/handling-of-links/merge-nodes-by-links-1-line-with-line-to-self/sym_output.json b/tests/data/power_flow/handling-of-links/merge-nodes-by-links-1-line-with-line-to-self/sym_output.json index 1f8028cd4a..f718391133 100644 --- a/tests/data/power_flow/handling-of-links/merge-nodes-by-links-1-line-with-line-to-self/sym_output.json +++ b/tests/data/power_flow/handling-of-links/merge-nodes-by-links-1-line-with-line-to-self/sym_output.json @@ -98,13 +98,13 @@ "energized": 1, "loading": 0.0, "p_from": 21084319.029628307, - "q_from": 5195613.111142274, - "i_from": 1195.2496789651816, - "s_from": 21715038.672399823, + "q_from": 5541259.927274941, + "i_from": 1199.94392461469216, + "s_from": 21800322.716069363, "p_to": -21084319.029628307, - "q_to": -5195613.111142274, - "i_to": 1195.2496789651816, - "s_to": 21715038.672399823 + "q_to": -5541259.927274941, + "i_to": 1199.94392461469216, + "s_to": 21800322.716069363 }, { "id": 9, diff --git a/tests/data/short_circuit/dummy-test-line-into-itself/params.json b/tests/data/short_circuit/dummy-test-line-into-itself/params.json index f146b8585b..e1af40a14e 100644 --- a/tests/data/short_circuit/dummy-test-line-into-itself/params.json +++ b/tests/data/short_circuit/dummy-test-line-into-itself/params.json @@ -5,5 +5,9 @@ "default": 1e-9, "i_(.+)?": 1e-5 }, - "short_circuit_voltage_scaling": "maximum" + "short_circuit_voltage_scaling": "maximum", + "xfail": { + "raises": "AssertionError", + "reason": "Bug to be fixed in https://github.com/PowerGridModel/power-grid-model/pull/1566" + } } From 953427d25cfa9e8155ec27adb112dff58531074e Mon Sep 17 00:00:00 2001 From: Jerry Jinfeng Guo Date: Fri, 4 Sep 2026 12:00:00 +0200 Subject: [PATCH 11/15] format a source file in base branch Signed-off-by: Jerry Jinfeng Guo --- .../include/power_grid_model/calculation_parameters.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 51f010582f..3b80f1c137 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 @@ -506,7 +506,8 @@ struct ReducedComponentTopology { struct TopologicalNode { IdxVector user_nodes; - std::vector user_links; // TODO(figueroa1395): temporarily doesn't have user nodes, but internal nodes while exploring + std::vector + user_links; // TODO(figueroa1395): temporarily doesn't have user nodes, but internal nodes while exploring constexpr auto is_supernode() const noexcept -> bool { return user_nodes.size() > 1 && !user_links.empty(); } }; From 74ddc1bcd98b9c7592efb8b82c12ffd88d644f53 Mon Sep 17 00:00:00 2001 From: Jerry Jinfeng Guo Date: Fri, 4 Sep 2026 13:17:45 +0200 Subject: [PATCH 12/15] sc tests are not to be marked as fail; further follow up in #1566 Signed-off-by: Jerry Jinfeng Guo --- .../dummy-test-line-into-itself/params.json | 6 +----- .../sc_output_batch.json | 14 +++++++------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/tests/data/short_circuit/dummy-test-line-into-itself/params.json b/tests/data/short_circuit/dummy-test-line-into-itself/params.json index e1af40a14e..f146b8585b 100644 --- a/tests/data/short_circuit/dummy-test-line-into-itself/params.json +++ b/tests/data/short_circuit/dummy-test-line-into-itself/params.json @@ -5,9 +5,5 @@ "default": 1e-9, "i_(.+)?": 1e-5 }, - "short_circuit_voltage_scaling": "maximum", - "xfail": { - "raises": "AssertionError", - "reason": "Bug to be fixed in https://github.com/PowerGridModel/power-grid-model/pull/1566" - } + "short_circuit_voltage_scaling": "maximum" } diff --git a/tests/data/short_circuit/dummy-test-line-into-itself/sc_output_batch.json b/tests/data/short_circuit/dummy-test-line-into-itself/sc_output_batch.json index eb3918a2db..406b3d48d8 100644 --- a/tests/data/short_circuit/dummy-test-line-into-itself/sc_output_batch.json +++ b/tests/data/short_circuit/dummy-test-line-into-itself/sc_output_batch.json @@ -16,11 +16,11 @@ {"id": 6, "energized": 0, "i_from": [0, 0, 0], "i_from_angle": [0, 0, 0], "i_to": [0, 0, 0], "i_to_angle": [0, 0, 0]} ], "link": [ - {"id": 9, "energized": 1, "i_from": [0, 3.6564798344471052, 3.827107363916523], "i_from_angle": [0, -0.48939955972358584, -2.8761707632835742], "i_to": [0, 3.6564798344471052, 3.827107363916523], "i_to_angle": [0, 2.6521930938662073, 0.2654218903062191]} + {"id": 9, "energized": 1, "i_from": [435.3036828482491, 3.6564810911066432, 3.827110009746561], "i_from_angle": [-1.077618546806934, -0.48939973132640124, -2.8761711431423812], "i_to": [435.3036828482491, 3.65648109110664332, 3.827110009746561], "i_to_angle": [2.063974106782859, 2.6521929222633923, 0.2654215104474122]} ], "shunt": [ {"id": 7, "energized": 1, "i": [0.66507454600033922, 3.6621221898722189, 3.7855668750333362], "i_angle": [1.570793748925259, -0.47527539810683334, -2.8784819041528089]}, - {"id": 8, "energized": 1, "i": [0.68295207957823156, 3.6564810900168685, 3.8271100083522671], "i_angle": [1.5502437165727303, -0.4893997316430107, -2.8761711433811832]} + {"id": 8, "energized": 1, "i": [0.6829520797414301, 3.656481091106643, 3.827110009746561], "i_angle": [1.5502437165727303, -0.4893997316430107, -2.8761711433811832]} ], "source": [ {"id": 10, "energized": 1, "i": [63508529.610858843, 731.32759227924817, 734.26006094063086], "i_angle": [-1.4711276743037347, -1.3381594964606927, -1.8340636301254731]} @@ -33,8 +33,8 @@ ], "fault": [ {"id": 13, "energized": 1, "i_f": [63508331.109258644, 0, 0], "i_f_angle": [-1.4711239498504336, 0, 0]}, - {"id": 14, "energized": 1, "i_f": [436.49378866976025, 0, 0], "i_f_angle": [2.0624362936211509, 0, 0]}, - {"id": 15, "energized": 1, "i_f": [0.68295207957823156, 0, 0], "i_f_angle": [-1.5913489370170628, 0, 0]} + {"id": 14, "energized": 1, "i_f": [435.89860686970485, 0, 0], "i_f_angle": [2.06320415055763809, 0, 0]}, + {"id": 15, "energized": 1, "i_f": [435.898606869704856, 0, 0], "i_f_angle": [2.06320415055763828, 0, 0]} ] }, { @@ -49,7 +49,7 @@ {"id": 6, "energized": 1, "i_from": [0.34147603978911578, 1.8282405450084342, 1.9135550041761336], "i_from_angle": [1.5502437165727303, -0.4893997316430107, -2.8761711433811832], "i_to": [0.34147603978911578, 1.8282405450084342, 1.9135550041761336], "i_to_angle": [1.5502437165727303, -0.4893997316430107, -2.8761711433811832]} ], "link": [ - {"id": 9, "energized": 1, "i_from": [0, 3.6564798344471052, 3.827107363916523], "i_from_angle": [0, -0.48939955972358584, -2.8761707632835742], "i_to": [0, 3.6564798344471052, 3.827107363916523], "i_to_angle": [0, 2.6521930938662073, 0.2654218903062191]} + {"id": 9, "energized": 1, "i_from": [435.3036828482491, 3.6564810911066432, 3.827110009746561], "i_from_angle": [-1.077618546806934, -0.48939973132640124, -2.8761711431423812], "i_to": [435.3036828482491, 3.65648109110664332, 3.827110009746561], "i_to_angle": [2.063974106782859, 2.6521929222633923, 0.2654215104474122]} ], "shunt": [ {"id": 7, "energized": 0, "i": [0, 0, 0], "i_angle": [0, 0, 0]}, @@ -66,8 +66,8 @@ ], "fault": [ {"id": 13, "energized": 1, "i_f": [63508331.109258644, 0, 0], "i_f_angle": [-1.4711239498504336, 0, 0]}, - {"id": 14, "energized": 1, "i_f": [436.49378866976025, 0, 0], "i_f_angle": [2.0624362936211509, 0, 0]}, - {"id": 15, "energized": 1, "i_f": [0.68295207957823156, 0, 0], "i_f_angle": [-1.5913489370170628, 0, 0]} + {"id": 14, "energized": 1, "i_f": [435.89860686970485, 0, 0], "i_f_angle": [2.06320415055763809, 0, 0]}, + {"id": 15, "energized": 1, "i_f": [435.898606869704856, 0, 0], "i_f_angle": [2.06320415055763828, 0, 0]} ] } ] From c942f489b5ad566417e658d66ca4cf8b53a110a7 Mon Sep 17 00:00:00 2001 From: Jerry Jinfeng Guo Date: Fri, 4 Sep 2026 13:23:55 +0200 Subject: [PATCH 13/15] super node test updated Signed-off-by: Jerry Jinfeng Guo --- tests/cpp_unit_tests/test_supernodes.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/cpp_unit_tests/test_supernodes.cpp b/tests/cpp_unit_tests/test_supernodes.cpp index 1551e7b622..b8def2bc6f 100644 --- a/tests/cpp_unit_tests/test_supernodes.cpp +++ b/tests/cpp_unit_tests/test_supernodes.cpp @@ -165,7 +165,7 @@ TEST_CASE("Test Supernodes") { CHECK(std::ranges::equal( topo_nodes.topo_nodes | std::views::transform([](TopologicalNode const& node) -> auto& { return node.user_links; }), - std::vector>{{{0, disconnected}}, {{disconnected, 0}}, {{0, 1}}, {}, {}})); + std::vector>{{}, {}, {{0, 1}}, {}, {}})); CHECK(std::ranges::equal(topo_nodes.coupling.user_nodes_to_topo_nodes, std::vector{{.group = 0, .pos = 0}, @@ -176,10 +176,10 @@ TEST_CASE("Test Supernodes") { {.group = 4, .pos = 0}})); CHECK(std::ranges::equal(topo_nodes.coupling.user_links_to_topo_nodes, - std::vector{{.group = 0, .pos = 0}, + std::vector{{.group = 0, .pos = disconnected}, {.group = 2, .pos = 0}, {.group = disconnected, .pos = disconnected}, - {.group = 1, .pos = 0}})); + {.group = 1, .pos = disconnected}})); } } SUBCASE("construct_reduced_topology") { From 0e496b2dbc3fe94cdbd1105e8d59fa05d1d94db1 Mon Sep 17 00:00:00 2001 From: Jerry Jinfeng Guo Date: Fri, 4 Sep 2026 15:38:13 +0200 Subject: [PATCH 14/15] fix the test fixture Signed-off-by: Jerry Jinfeng Guo --- tests/cpp_unit_tests/main_core/test_main_core_output.cpp | 3 +++ 1 file changed, 3 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 ba01d9a173..95ee31d70c 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 @@ -244,6 +244,9 @@ TEST_CASE("Test main core output") { 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)); + auto topo_comp_coup = std::make_shared(); + topo_comp_coup->node = {{.group = 0, .pos = 0}}; + state.topo_comp_coup = std::move(topo_comp_coup); SUBCASE("Steady state output") { MathOutput>> const math_output{ From 8404bcc14289fae0c5a344b8d253a1d17cae2749 Mon Sep 17 00:00:00 2001 From: Jerry Jinfeng Guo Date: Fri, 4 Sep 2026 15:51:08 +0200 Subject: [PATCH 15/15] error tollerance for u angle dummy pf test Signed-off-by: Jerry Jinfeng Guo --- tests/data/power_flow/dummy-test-line-into-itself/params.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/data/power_flow/dummy-test-line-into-itself/params.json b/tests/data/power_flow/dummy-test-line-into-itself/params.json index d642c7831d..b96bd71fb4 100644 --- a/tests/data/power_flow/dummy-test-line-into-itself/params.json +++ b/tests/data/power_flow/dummy-test-line-into-itself/params.json @@ -3,6 +3,7 @@ "rtol": 1e-9, "atol": { "default": 1e-9, + "u_angle": 1e-4, "p": 0.02, "q": 0.02, "pf": 1e-8,