Skip to content

Merge links on nodes: create super nodes#1398

Open
mgovers wants to merge 8 commits into
mainfrom
feature/super-nodes
Open

Merge links on nodes: create super nodes#1398
mgovers wants to merge 8 commits into
mainfrom
feature/super-nodes

Conversation

@mgovers
Copy link
Copy Markdown
Member

@mgovers mgovers commented May 13, 2026

Part of #35

This actually creates the topological nodes from the regular nodes

mgovers added 8 commits April 29, 2026 14:23
Signed-off-by: Martijn Govers <Martijn.Govers@Alliander.com>
Signed-off-by: Martijn Govers <Martijn.Govers@Alliander.com>
Signed-off-by: Martijn Govers <Martijn.Govers@Alliander.com>
Signed-off-by: Martijn Govers <Martijn.Govers@Alliander.com>
Signed-off-by: Martijn Govers <Martijn.Govers@Alliander.com>
Signed-off-by: Martijn Govers <Martijn.Govers@Alliander.com>
Signed-off-by: Martijn Govers <Martijn.Govers@Alliander.com>
Signed-off-by: Martijn Govers <Martijn.Govers@Alliander.com>
@mgovers mgovers self-assigned this May 13, 2026
@mgovers mgovers added the feature New feature or request label May 13, 2026
Comment on lines +433 to +448
struct ReducedComponentTopology {
Idx n_node{}; // num of topological nodes (with reduced links), plus internal nodes for 3-way branches
std::vector<BranchIdx> branch_node_idx;
std::vector<Branch3Idx> branch3_node_idx;
IdxVector shunt_node_idx;
IdxVector source_node_idx;
IdxVector load_gen_node_idx;
std::span<LoadGenType const> load_gen_type;
IdxVector voltage_sensor_node_idx;
std::span<Idx const> power_sensor_object_idx; // the index is relative to branch, source, shunt or load_gen
std::span<MeasuredTerminalType const> power_sensor_terminal_type;
std::span<Idx const> current_sensor_object_idx; // the index is relative to branch
std::span<MeasuredTerminalType const> current_sensor_terminal_type;
std::span<ComponentType const> regulator_type;
std::span<Idx const> regulated_object_idx; // the index is relative to branch or branch3
std::span<ComponentType const> regulated_object_type;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the span ones essentially do not change when we go from user nodes to topological nodes, so we can do with a pure view. The branch2, branch3 and appliance ones may change so they need a pass-by-copy.

We can also opt for a maybe_owning_view, which can either take by reference, or by value, depending on the situation. I'm investigating that on the side

Comment on lines +32 to +43
#if __cpp_lib_ranges_pairwise >= 202110L
using std::views::pairwise;
#else
constexpr auto pairwise(std::ranges::viewable_range auto&& range_to_pair) {
auto const begin = std::ranges::begin(range_to_pair);
auto const end = std::ranges::end(range_to_pair);
if (begin == end) {
return std::views::zip(std::ranges::subrange(begin, begin), std::ranges::subrange(begin, begin));
}
return std::views::zip(std::ranges::subrange(begin, end - 1), std::ranges::subrange(begin + 1, end));
}
#endif
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the end, I did not use it in this PR but initially i thought i needed it. I've had the same thing in the past already (multiple times) but removed it every single time, so I was like "ok, might as well keep it this time"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its a handy function. I see we can already use it in sparse_decode in grouped_idx_vector maybe. Uses similar logic.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's constexpr and we shall fully have it when c++23 is fully supported + it's handy. Let's keep it.

Comment on lines +32 to +33
explicit TopologicalNodeMapping(Idx n_topo_nodes_, IdxVector mapping)
: mapping_{std::move(mapping)}, n_topo_nodes_{n_topo_nodes_} {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
explicit TopologicalNodeMapping(Idx n_topo_nodes_, IdxVector mapping)
: mapping_{std::move(mapping)}, n_topo_nodes_{n_topo_nodes_} {
explicit TopologicalNodeMapping(Idx n_topo_nodes, IdxVector mapping)
: mapping_{std::move(mapping)}, n_topo_nodes_{n_topo_nodes} {

Comment on lines +32 to +43
#if __cpp_lib_ranges_pairwise >= 202110L
using std::views::pairwise;
#else
constexpr auto pairwise(std::ranges::viewable_range auto&& range_to_pair) {
auto const begin = std::ranges::begin(range_to_pair);
auto const end = std::ranges::end(range_to_pair);
if (begin == end) {
return std::views::zip(std::ranges::subrange(begin, begin), std::ranges::subrange(begin, begin));
}
return std::views::zip(std::ranges::subrange(begin, end - 1), std::ranges::subrange(begin + 1, end));
}
#endif
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's constexpr and we shall fully have it when c++23 is fully supported + it's handy. Let's keep it.

};

struct ReducedComponentTopology {
Idx n_node{}; // num of topological nodes (with reduced links), plus internal nodes for 3-way branches
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "(with reduced links)" part makes this confusing. It should be all topological nodes, which includes user nodes not connected by links and all supernodes, right?

struct ComponentConnections {
std::vector<BranchConnected> branch_connected;
std::vector<Branch3Connected> branch3_connected;
std::vector<BranchConnected> link_connected;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this struct and for ComponentTopology, let's add a short comment on why we have split links from branches - for future reference.

}

auto mapping() const& -> IdxVector const& { return mapping_; }
auto mapping() && -> IdxVector { return std::move(mapping_); }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'll find out later, but why not return a span? Why explicitly define the move away option?

namespace power_grid_model::supernodes {
class TopologicalNodeMapping {
public:
TopologicalNodeMapping() = default;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the default constructor when below you have an explicit one and there is no way to later "populate" the class? I'd argue having a default one makes it easier for us to introduce a bug by mistake. Thoughts?

Comment on lines +99 to +100
auto const from = vertices[0];
auto const to = vertices[1];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a sanity check like

assert(from < n_nodes && to < n_nodes);

for when we split links from branches in main_core/topology.hpp?

// not the user nodes. This effectively removes all link-related stuff from the math topology.
inline ReducedComponentTopology construct_reduced_topology(ComponentTopology const& comp_topo,
TopologicalNodeMapping const& topo_node_mapping) {
auto remap_nodes = [&topo_node_mapping]<typename IdxType>(std::vector<IdxType> const& user_nodes) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clever stuff, nicely done!

Comment on lines +150 to +152
if (link_connected[0] == 0 || link_connected[1] == 0) {
return Idx2D{.group = disconnected, .pos = disconnected};
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking about this edge case:
If we have a link that's disconnected on one side, but it's not part of any other super node, and its connected side is then connected to a node to an active line, what will happen to that link? It clearly isn't part of a supernode so it will be excluded from the link solver algorithm, but it will also be excluded from the regular topology. What happens then? Is this expected? We still have to have output for it on the connected side.

REQUIRE(topo_node_mapping.n_user_nodes() == comp_topo.n_node);
CHECK(topo_node_mapping.mapping() == IdxVector{0, 0, 1});
}
SUBCASE("Disconnected link => not remapped") {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mentioned edge case in my previous comment is similar to this, however, can you add it here as well too? Just to make sure the mapping handles it well.

Comment on lines +155 to +160
SUBCASE("construct_reduced_topology") {
// TODO: Add test implementation
}
SUBCASE("reduce_topology") {
// TODO: Add test implementation
}
Copy link
Copy Markdown
Member

@figueroa1395 figueroa1395 May 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reminder to add these :)

std::views::transform([&topo_nodes](auto const& idx_and_topo) {
auto const& [user_node, topo_node] = idx_and_topo;
topo_nodes[topo_node].user_nodes.push_back(user_node);
return Idx2D{.group = topo_node, .pos = user_node};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can already start with separate alias for same type.

I already might be getting confused with the terminology of user / topo / supernodes (And then maybe we need to rename or define the terms in comments.)

Idx n_node{};
std::vector<BranchIdx> branch_node_idx;
std::vector<Branch3Idx> branch3_node_idx;
std::vector<BranchIdx> link_node_idx;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is intended for future refactor:

cfr. https://github.com/PowerGridModel/power-grid-model/pull/1398/changes#r3272590347

Lets define what index means.
PGM maintainers can understand that these links are ordered on the basis of their order in container. But maybe not new contributors.

I say this because now there might be overlap in content between branch and link.

@nitbharambe
Copy link
Copy Markdown
Member

no new comments from me @mgovers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants