KPN/include/kpn/tmp/topo_sort.hpp
Duncan Tourolle 2bca2a7554
All checks were successful
🧪 Test / test (push) Successful in 6m4s
Add static network
2026-05-08 20:00:15 +02:00

224 lines
9.4 KiB
C++

#pragma once
#include "fanout_groups.hpp"
#include <cstddef>
#include <tuple>
#include <type_traits>
// Compile-time topological sort + cycle detection over a TypeList<SimpleEdge<...>>.
//
// Nodes are identified by type (not by index), since two different node types
// are always distinct vertices. The sort produces a TypeList of node types in
// topological order (sources first).
//
// cycle detection: if DFS revisits a node currently on the stack → static_assert.
namespace kpn::tmp {
// ── Collect all unique node types from an expanded edge list ──────────────────
template<typename EdgeList, typename Seen = TypeList<>>
struct all_node_types;
template<typename Seen>
struct all_node_types<TypeList<>, Seen> { using type = Seen; };
template<typename Head, typename... Tail, typename Seen>
struct all_node_types<TypeList<Head, Tail...>, Seen> {
using S1 = std::conditional_t<
already_processed<typename Head::src_node_t, Seen>::value,
Seen, append_t<Seen, typename Head::src_node_t>>;
using S2 = std::conditional_t<
already_processed<typename Head::dst_node_t, S1>::value,
S1, append_t<S1, typename Head::dst_node_t>>;
using type = typename all_node_types<TypeList<Tail...>, S2>::type;
};
// ── Collect successors (dst node types) of a given src node type ──────────────
template<typename NodeType, typename EdgeList>
struct successors;
template<typename NodeType>
struct successors<NodeType, TypeList<>> { using type = TypeList<>; };
template<typename NodeType, typename Head, typename... Tail>
struct successors<NodeType, TypeList<Head, Tail...>> {
using rest = typename successors<NodeType, TypeList<Tail...>>::type;
using type = std::conditional_t<
std::is_same_v<typename Head::src_node_t, NodeType>,
std::conditional_t<
already_processed<typename Head::dst_node_t, rest>::value,
rest,
append_t<rest, typename Head::dst_node_t>>,
rest>;
};
// ── DFS state ─────────────────────────────────────────────────────────────────
// Color: 0=white(unseen), 1=grey(on stack), 2=black(done)
// We track grey nodes as a TypeList to detect back-edges.
template<typename Node, typename GreyList>
struct is_grey : already_processed<Node, GreyList> {};
// DFS result: has_cycle flag + topo order (black nodes appended post-visit)
template<bool Cycle, typename TopoList>
struct DfsResult { static constexpr bool has_cycle = Cycle; using topo = TopoList; };
template<typename Node, typename EdgeList,
typename GreyList, typename BlackList, typename TopoList>
struct dfs_node;
// Iterate successors
template<typename SuccList, typename EdgeList,
typename GreyList, typename BlackList, typename TopoList, bool CycleSoFar>
struct dfs_successors;
template<typename EdgeList, typename GreyList, typename BlackList,
typename TopoList, bool CycleSoFar>
struct dfs_successors<TypeList<>, EdgeList, GreyList, BlackList, TopoList, CycleSoFar> {
static constexpr bool has_cycle = CycleSoFar;
using black = BlackList;
using topo = TopoList;
};
template<typename Head, typename... Tail, typename EdgeList,
typename GreyList, typename BlackList, typename TopoList, bool CycleSoFar>
struct dfs_successors<TypeList<Head, Tail...>, EdgeList,
GreyList, BlackList, TopoList, CycleSoFar> {
// Visit Head
using visit = dfs_node<Head, EdgeList, GreyList, BlackList, TopoList>;
static constexpr bool cycle1 = CycleSoFar || visit::has_cycle;
// Continue with remaining successors using updated black/topo
using rest = dfs_successors<TypeList<Tail...>, EdgeList,
GreyList, typename visit::black,
typename visit::topo, cycle1>;
static constexpr bool has_cycle = rest::has_cycle;
using black = typename rest::black;
using topo = typename rest::topo;
};
template<typename Node, typename EdgeList,
typename GreyList, typename BlackList, typename TopoList>
struct dfs_node {
// Already black — skip
static constexpr bool already_done = already_processed<Node, BlackList>::value;
// On stack — cycle
static constexpr bool on_stack = is_grey<Node, GreyList>::value;
// Visit successors (only if not already done / on stack)
using succs = typename successors<Node, EdgeList>::type;
using new_grey = append_t<GreyList, Node>;
using visit_succs = dfs_successors<succs, EdgeList, new_grey, BlackList, TopoList,
on_stack>;
static constexpr bool has_cycle = already_done ? false :
on_stack ? true :
visit_succs::has_cycle;
// Append node to topo after all successors (post-order = reverse topo)
using topo_after = std::conditional_t<
already_done || on_stack,
TopoList,
append_t<typename visit_succs::topo, Node>>;
using black = std::conditional_t<
already_done || on_stack,
BlackList,
append_t<typename visit_succs::black, Node>>;
using topo = topo_after;
};
// ── Top-level DFS over all nodes ──────────────────────────────────────────────
template<typename NodeList, typename EdgeList,
typename BlackList, typename TopoList, bool CycleSoFar>
struct dfs_all;
template<typename EdgeList, typename BlackList, typename TopoList, bool CycleSoFar>
struct dfs_all<TypeList<>, EdgeList, BlackList, TopoList, CycleSoFar> {
static constexpr bool has_cycle = CycleSoFar;
using topo = TopoList;
};
template<typename Head, typename... Tail, typename EdgeList,
typename BlackList, typename TopoList, bool CycleSoFar>
struct dfs_all<TypeList<Head, Tail...>, EdgeList, BlackList, TopoList, CycleSoFar> {
using visit = dfs_node<Head, EdgeList, TypeList<>, BlackList, TopoList>;
static constexpr bool cycle1 = CycleSoFar || visit::has_cycle;
using rest = dfs_all<TypeList<Tail...>, EdgeList,
typename visit::black, typename visit::topo, cycle1>;
static constexpr bool has_cycle = rest::has_cycle;
using topo = typename rest::topo;
};
// ── Public interface ───────────────────────────────────────────────────────────
// topo_sort<EdgeList>:
// ::topo — TypeList of node types, sources first (start order)
// ::has_cycle — true if a cycle was detected
template<typename EdgeList>
struct topo_sort {
using Nodes = typename all_node_types<EdgeList>::type;
using result = dfs_all<Nodes, EdgeList, TypeList<>, TypeList<>, false>;
// DFS post-order gives reverse topo; reverse the list for sources-first order.
// Reversing a TypeList:
template<typename List, typename Acc = TypeList<>>
struct reverse_list;
template<typename Acc>
struct reverse_list<TypeList<>, Acc> { using type = Acc; };
template<typename H, typename... T, typename Acc>
struct reverse_list<TypeList<H, T...>, Acc>
: reverse_list<TypeList<T...>, append_t<Acc, H>> {}; // wrong direction intentionally
// Post-order appends children before parent, so the list is already
// reverse-topo (sinks first). Reverse it to get sources first.
template<typename List, typename Acc = TypeList<>>
struct rev;
template<typename Acc>
struct rev<TypeList<>, Acc> { using type = Acc; };
template<typename H, typename... T, typename Acc>
struct rev<TypeList<H, T...>, Acc> : rev<TypeList<T...>, TypeList<H, Acc>> {
// prepend H to Acc: TypeList<H, Acc...>
};
// Simpler: just reverse by prepending
template<typename L, typename A = TypeList<>>
struct rev2 { using type = A; };
template<typename H, typename... T, typename... As>
struct rev2<TypeList<H, T...>, TypeList<As...>>
: rev2<TypeList<T...>, TypeList<H, As...>> {};
static constexpr bool has_cycle = result::has_cycle;
using topo = typename rev2<typename result::topo>::type;
};
// ── Duplicate-tag detection ───────────────────────────────────────────────────
//
// Two user nodes share a (Func, UniqueTag) pair iff they have the same type.
// has_duplicate_tags_v<Edges...> is true if any two user node types are identical.
template<typename T, typename List>
struct type_in_list : std::false_type {};
template<typename T, typename H, typename... Tail>
struct type_in_list<T, TypeList<H, Tail...>>
: std::conditional_t<std::is_same_v<T, H>,
std::true_type,
type_in_list<T, TypeList<Tail...>>> {};
template<typename NodeList>
struct has_duplicate_node_types : std::false_type {};
template<typename H, typename... Tail>
struct has_duplicate_node_types<TypeList<H, Tail...>>
: std::conditional_t<type_in_list<H, TypeList<Tail...>>::value,
std::true_type,
has_duplicate_node_types<TypeList<Tail...>>> {};
template<typename... Edges>
inline constexpr bool has_duplicate_tags_v =
has_duplicate_node_types<
typename all_node_types<TypeList<Edges...>>::type>::value;
} // namespace kpn::tmp