Merge links on nodes: create super nodes#1398
Conversation
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>
| 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; |
There was a problem hiding this comment.
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
| #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 |
There was a problem hiding this comment.
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"
There was a problem hiding this comment.
I think its a handy function. I see we can already use it in sparse_decode in grouped_idx_vector maybe. Uses similar logic.
There was a problem hiding this comment.
It's constexpr and we shall fully have it when c++23 is fully supported + it's handy. Let's keep it.
| explicit TopologicalNodeMapping(Idx n_topo_nodes_, IdxVector mapping) | ||
| : mapping_{std::move(mapping)}, n_topo_nodes_{n_topo_nodes_} { |
There was a problem hiding this comment.
| 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} { |
| #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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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_); } |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
| auto const from = vertices[0]; | ||
| auto const to = vertices[1]; |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
Clever stuff, nicely done!
| if (link_connected[0] == 0 || link_connected[1] == 0) { | ||
| return Idx2D{.group = disconnected, .pos = disconnected}; | ||
| } |
There was a problem hiding this comment.
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") { |
There was a problem hiding this comment.
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.
| SUBCASE("construct_reduced_topology") { | ||
| // TODO: Add test implementation | ||
| } | ||
| SUBCASE("reduce_topology") { | ||
| // TODO: Add test implementation | ||
| } |
There was a problem hiding this comment.
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}; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
|
no new comments from me @mgovers |
Part of #35
This actually creates the topological nodes from the regular nodes