Files
TheRock/rocm-libraries/shared/rocroller/lib/include/rocRoller/Graph/Hypergraph_impl.hpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

1175 lines
44 KiB
C++

// Copyright Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#pragma once
#include <algorithm>
#include <compare>
#include <deque>
#include <map>
#include <ranges>
#include <unordered_set>
#include <variant>
#include <vector>
#include <rocRoller/Graph/Hypergraph.hpp>
#include <rocRoller/Utilities/Comparison.hpp>
#include <rocRoller/Utilities/Error.hpp>
#include <rocRoller/Utilities/Generator.hpp>
#include <rocRoller/Utilities/Settings.hpp>
namespace rocRoller
{
namespace Graph
{
inline std::string toString(ElementType e)
{
switch(e)
{
case ElementType::Node:
return "Node";
case ElementType::Edge:
return "Edge";
case ElementType::Count:
break;
}
throw std::runtime_error("Invalid ElementType");
}
inline std::ostream& operator<<(std::ostream& stream, ElementType const& e)
{
return stream << toString(e);
}
inline std::string toString(Direction d)
{
switch(d)
{
case Direction::Upstream:
return "Upstream";
case Direction::Downstream:
return "Downstream";
case Direction::Count:
break;
}
throw std::runtime_error("Invalid Direction");
}
constexpr inline Direction opposite(Direction d)
{
return d == Direction::Downstream ? Direction::Upstream : Direction::Downstream;
}
inline std::string toString(GraphModification g)
{
switch(g)
{
case GraphModification::DeleteElement:
return "DeleteElement";
case GraphModification::AddElement:
return "AddElement";
case GraphModification::SetElement:
return "SetElement";
case GraphModification::Count:
return "Count";
}
Throw<FatalError>("Invalid GraphModification ", static_cast<int>(g));
}
template <typename Node, typename Edge, bool Hyper>
std::string Hypergraph<Node, Edge, Hyper>::ElementName(
typename Hypergraph<Node, Edge, Hyper>::Element const& el)
{
return std::visit(rocRoller::overloaded{[](Node const&) { return "Node"; },
[](Edge const&) { return "Edge"; }},
el);
}
inline std::string truncate(std::string const& s)
{
auto const maxLen = Settings::Get(Settings::GraphNodeLabelMaxLength);
if(maxLen == 0 || s.size() <= maxLen)
{
return s;
}
constexpr std::size_t ellipsisLen = 3;
if(maxLen <= ellipsisLen)
{
return s.substr(0, maxLen);
}
return s.substr(0, maxLen - ellipsisLen) + "...";
}
template <typename Node, typename Edge, bool Hyper>
constexpr inline bool
Hypergraph<Node, Edge, Hyper>::Location::operator==(Location const& rhs) const
{
// clang-format off
return LexicographicCompare(
tag, rhs.tag,
incoming, rhs.incoming,
outgoing, rhs.outgoing) == 0;
// clang-format on
}
template <typename Node, typename Edge, bool Hyper>
bool Hypergraph<Node, Edge, Hyper>::exists(int tag) const
{
return m_elements.contains(tag);
}
template <typename Node, typename Edge, bool Hyper>
ElementType Hypergraph<Node, Edge, Hyper>::getElementType(int tag) const
{
return getElementType(getElement(tag));
}
template <typename Node, typename Edge, bool Hyper>
ElementType Hypergraph<Node, Edge, Hyper>::getElementType(Element const& e) const
{
if(holds_alternative<Node>(e))
return ElementType::Node;
return ElementType::Edge;
}
inline ElementType getConnectingType(ElementType t)
{
return t == ElementType::Node ? ElementType::Edge : ElementType::Node;
}
template <typename Node, typename Edge, bool Hyper>
void Hypergraph<Node, Edge, Hyper>::clearCache(GraphModification)
{
}
template <typename Node, typename Edge, bool Hyper>
template <typename T>
int Hypergraph<Node, Edge, Hyper>::addElement(T&& element)
{
auto tag = nextAvailableTag();
m_elements.emplace(tag, std::forward<T>(element));
AssertFatal(isModificationAllowed(tag), "addElement is disallowed on this graph");
clearCache(GraphModification::AddElement);
return tag;
}
template <typename Node, typename Edge, bool Hyper>
template <typename T, typename... Ts>
auto Hypergraph<Node, Edge, Hyper>::addElements(T&& element, Ts&&... rest)
{
auto myValue = addElement(std::forward<T>(element));
return std::tuple{myValue, addElement(std::forward<Ts>(rest))...};
}
template <typename Node, typename Edge, bool Hyper>
template <typename T>
void Hypergraph<Node, Edge, Hyper>::setElement(int tag, T&& element)
{
if(m_elements.contains(tag))
{
AssertFatal(getElementType(element) == getElementType(tag),
"setElement() should not change an element between node and edge",
ShowValue(getElementType(element)),
ShowValue(getElementType(tag)),
ShowValue(tag));
}
m_elements[tag] = std::forward<T>(element);
clearCache(GraphModification::SetElement);
}
template <typename Node, typename Edge, bool Hyper>
template <typename T>
int Hypergraph<Node, Edge, Hyper>::addElement(T&& element,
std::initializer_list<int> inputs,
std::initializer_list<int> outputs)
{
return addElement<T, std::initializer_list<int>, std::initializer_list<int>>(
std::forward<T>(element), inputs, outputs);
}
template <typename Node, typename Edge, bool Hyper>
template <typename T, CForwardRangeOf<int> T_Inputs, CForwardRangeOf<int> T_Outputs>
int Hypergraph<Node, Edge, Hyper>::addElement(T&& element,
T_Inputs const& inputs,
T_Outputs const& outputs)
{
auto tag = nextAvailableTag();
addElement(tag, std::forward<T>(element), inputs, outputs);
return tag;
}
template <typename Node, typename Edge, bool Hyper>
template <typename T, CForwardRangeOf<int> T_Inputs, CForwardRangeOf<int> T_Outputs>
void Hypergraph<Node, Edge, Hyper>::addElement(int tag,
T&& element,
T_Inputs const& inputs,
T_Outputs const& outputs)
{
AssertFatal(m_elements.find(tag) == m_elements.end());
auto elementType = getElementType(element);
auto connectingType = getConnectingType(elementType);
for(auto input : inputs)
{
AssertFatal(getElementType(input) == connectingType);
}
for(auto output : outputs)
{
AssertFatal(getElementType(output) == connectingType);
}
clearCache(GraphModification::AddElement);
m_elements.emplace(tag, std::forward<T>(element));
AssertFatal(isModificationAllowed(tag), "addElement is disallowed on this graph");
m_incidence.addIncidentConnections(tag, inputs, outputs);
if constexpr(!Hyper)
{
// Enforce "calm" graph restriction of Edges requiring exactly one incoming Node and
// one outgoing Node
std::string errorMsg = "Graph is not a Hypergraph and Edge requires exactly one "
"incoming Node and one outgoing Node";
if(elementType == ElementType::Edge)
{
AssertFatal(m_incidence.getSrcCount(tag) == 1
&& m_incidence.getDstCount(tag) == 1,
errorMsg,
ShowValue(tag));
}
else
{
for(auto input : inputs)
{
AssertFatal(m_incidence.getDstCount(input) == 1,
errorMsg,
ShowValue(tag),
ShowValue(input));
}
for(auto output : outputs)
{
AssertFatal(m_incidence.getSrcCount(output) == 1,
errorMsg,
ShowValue(tag),
ShowValue(output));
}
}
}
else
{
// Check if we accidentally added a dangling edge
if(elementType == ElementType::Edge)
{
AssertFatal(m_incidence.getSrcCount(tag) >= 1
&& m_incidence.getDstCount(tag) >= 1,
"Hypergraph has dangling edge",
ShowValue(tag));
}
}
}
template <typename Node, typename Edge, bool Hyper>
void Hypergraph<Node, Edge, Hyper>::deleteElement(int tag)
{
AssertFatal(isModificationAllowed(tag), "deleteElement is disallowed on this graph");
clearCache(GraphModification::DeleteElement);
m_incidence.deleteTag(tag);
m_elements.erase(tag);
}
// delete edge between the inputs and outputs with exact match
// deletes the first match found (duplicates not deleted)
template <typename Node, typename Edge, bool Hyper>
template <CForwardRangeOf<int> T_Inputs,
CForwardRangeOf<int> T_Outputs,
std::predicate<Edge const&> T_Predicate>
void Hypergraph<Node, Edge, Hyper>::deleteElement(T_Inputs const& inputs,
T_Outputs const& outputs,
T_Predicate edgePredicate)
{
AssertFatal(!inputs.empty() && !outputs.empty());
clearCache(GraphModification::DeleteElement);
for(auto input : inputs)
{
AssertFatal(getElementType(input) == ElementType::Node, "Requires node handles");
}
for(auto output : outputs)
{
AssertFatal(getElementType(output) == ElementType::Node, "Requires node handles");
}
auto match = false;
for(auto e : getNeighbours<Graph::Direction::Downstream>(inputs[0]))
{
auto elem = getElement(e);
if(!edgePredicate(std::get<Edge>(elem)))
continue;
match = true;
auto srcs = getNeighbours<Graph::Direction::Upstream>(e);
if(srcs.size() != inputs.size())
{
match = false;
continue;
}
for(auto src : inputs)
{
if(std::find(srcs.begin(), srcs.end(), src) == srcs.end())
{
match = false;
break;
}
}
if(match)
{
auto dsts = getNeighbours<Graph::Direction::Downstream>(e);
if(dsts.size() != outputs.size())
{
match = false;
continue;
}
for(auto dst : outputs)
{
if(std::find(dsts.begin(), dsts.end(), dst) == dsts.end())
{
match = false;
break;
}
}
}
if(match)
{
deleteElement(e);
return;
}
}
AssertFatal(match, "edge to delete : match not found");
}
template <typename Node, typename Edge, bool Hyper>
template <typename T, CForwardRangeOf<int> T_Inputs, CForwardRangeOf<int> T_Outputs>
requires(std::constructible_from<Edge, T>) void Hypergraph<Node, Edge, Hyper>::
deleteElement(T_Inputs const& inputs, T_Outputs const& outputs)
{
return deleteElement(
inputs, outputs, [](Edge const& edge) { return std::holds_alternative<T>(edge); });
}
template <typename Node, typename Edge, bool Hyper>
size_t Hypergraph<Node, Edge, Hyper>::getElementCount() const
{
return m_elements.size();
}
template <typename Node, typename Edge, bool Hyper>
auto Hypergraph<Node, Edge, Hyper>::getElement(int tag) const -> Element const&
{
AssertFatal(m_elements.contains(tag), "Element not found", ShowValue(tag));
return m_elements.at(tag);
}
template <typename Node, typename Edge, bool Hyper>
template <typename T>
T Hypergraph<Node, Edge, Hyper>::getNode(int tag) const
{
static_assert(std::constructible_from<Node, T>);
auto const& node = std::get<Node>(getElement(tag));
if constexpr(std::same_as<Node, T>)
{
return node;
}
else
{
return std::get<T>(node);
}
}
template <typename Node, typename Edge, bool Hyper>
template <typename T>
T Hypergraph<Node, Edge, Hyper>::getEdge(int tag) const
{
static_assert(std::constructible_from<Edge, T>);
auto const& edge = std::get<Edge>(getElement(tag));
if constexpr(std::same_as<Edge, T>)
{
return edge;
}
else
{
return std::get<T>(edge);
}
}
template <typename Node, typename Edge, bool Hyper>
auto Hypergraph<Node, Edge, Hyper>::getLocation(int tag) const -> Location
{
return {.tag = tag,
.incoming = m_incidence.getSrcs(tag),
.outgoing = m_incidence.getDsts(tag),
.element = getElement(tag)};
}
template <typename Node, typename Edge, bool Hyper>
Generator<int> Hypergraph<Node, Edge, Hyper>::roots() const
{
for(auto const& pair : m_elements)
{
auto tag = pair.first;
if(m_incidence.getSrcCount(tag) == 0)
co_yield tag;
}
}
template <typename Node, typename Edge, bool Hyper>
Generator<int> Hypergraph<Node, Edge, Hyper>::leaves() const
{
for(auto const& pair : m_elements)
{
auto tag = pair.first;
if(m_incidence.getDstCount(tag) == 0)
co_yield tag;
}
}
template <typename Node, typename Edge, bool Hyper>
Generator<int> Hypergraph<Node, Edge, Hyper>::childNodes(int parent) const
{
if(getElementType(parent) == ElementType::Node)
{
std::set<int> visited;
for(auto edgeTag : getNeighbours<Direction::Downstream>(parent))
{
for(auto neighbour : getNeighbours<Direction::Downstream>(edgeTag))
{
if(!visited.contains(neighbour))
{
visited.insert(neighbour);
co_yield neighbour;
}
}
}
}
else
{
for(auto child : getNeighbours<Direction::Downstream>(parent))
co_yield child;
}
}
template <typename Node, typename Edge, bool Hyper>
Generator<int> Hypergraph<Node, Edge, Hyper>::parentNodes(int child) const
{
if(getElementType(child) == ElementType::Node)
{
std::set<int> visited;
for(auto edgeTag : getNeighbours<Direction::Upstream>(child))
{
for(auto neighbour : getNeighbours<Direction::Upstream>(edgeTag))
{
if(!visited.contains(neighbour))
{
visited.insert(neighbour);
co_yield neighbour;
}
}
}
}
else
{
for(auto parent : getNeighbours<Direction::Upstream>(child))
co_yield parent;
}
}
template <typename Node, typename Edge, bool Hyper>
template <CForwardRangeOf<int> Range>
Generator<int> Hypergraph<Node, Edge, Hyper>::depthFirstVisit(Range const& starts,
Direction dir) const
{
std::unordered_set<int> visitedNodes;
if(dir == Direction::Downstream)
{
for(auto tag : starts)
co_yield depthFirstVisit<Direction::Downstream>(tag, visitedNodes);
}
else
{
for(auto tag : starts)
co_yield depthFirstVisit<Direction::Upstream>(tag, visitedNodes);
}
}
template <typename Node, typename Edge, bool Hyper>
template <CForwardRangeOf<int> Range, std::predicate<int> Predicate>
Generator<int> Hypergraph<Node, Edge, Hyper>::depthFirstVisit(Range const& starts,
Predicate edgePredicate,
Direction dir) const
{
std::unordered_set<int> visitedNodes;
if(dir == Direction::Downstream)
{
for(auto tag : starts)
co_yield depthFirstVisit<Direction::Downstream>(
tag, edgePredicate, visitedNodes);
}
else
{
for(auto tag : starts)
co_yield depthFirstVisit<Direction::Upstream>(tag, edgePredicate, visitedNodes);
}
}
template <typename Node, typename Edge, bool Hyper>
template <std::predicate<int> Predicate>
Generator<int> Hypergraph<Node, Edge, Hyper>::depthFirstVisit(int start,
Predicate edgePredicate,
Direction dir) const
{
std::unordered_set<int> visitedNodes;
if(dir == Direction::Downstream)
{
co_yield depthFirstVisit<Direction::Downstream>(start, edgePredicate, visitedNodes);
}
else
{
co_yield depthFirstVisit<Direction::Upstream>(start, edgePredicate, visitedNodes);
}
}
template <typename Node, typename Edge, bool Hyper>
Generator<int> Hypergraph<Node, Edge, Hyper>::depthFirstVisit(int start,
Direction dir) const
{
std::initializer_list<int> starts{start};
co_yield depthFirstVisit(starts, dir);
}
template <typename Node, typename Edge, bool Hyper>
template <std::predicate<int> Predicate>
Generator<int> Hypergraph<Node, Edge, Hyper>::findNodes(int start,
Predicate nodeSelector,
Direction dir) const
{
co_yield filter(nodeSelector, depthFirstVisit(start, dir));
}
template <typename Node, typename Edge, bool Hyper>
template <CForwardRangeOf<int> Range, std::predicate<int> Predicate>
Generator<int> Hypergraph<Node, Edge, Hyper>::findNodes(Range const& starts,
Predicate nodeSelector,
Direction dir) const
{
co_yield filter(nodeSelector, depthFirstVisit(starts, dir));
}
template <typename Node, typename Edge, bool Hyper>
Generator<int> Hypergraph<Node, Edge, Hyper>::allElements() const
{
for(auto const& pair : m_elements)
co_yield pair.first;
}
template <typename Node, typename Edge, bool Hyper>
template <std::predicate<int> Predicate>
Generator<int> Hypergraph<Node, Edge, Hyper>::findElements(Predicate nodeSelector) const
{
co_yield filter(nodeSelector, allElements());
}
template <typename Node, typename Edge, bool Hyper>
Generator<int> Hypergraph<Node, Edge, Hyper>::breadthFirstVisit(int start,
Direction dir) const
{
std::unordered_set<int> visitedNodes;
visitedNodes.insert(start);
co_yield start;
// This is a pair<srcTag, dstTag> for Downstream, or pair<dstTag, srcTag> for Upstream
std::deque<std::pair<int, int>> toExplore;
std::set<std::pair<int, int>> noted;
for(auto connected : dir == Direction::Downstream ? m_incidence.getDsts(start)
: m_incidence.getSrcs(start))
{
std::pair<int, int> candidate = {start, connected};
toExplore.push_back(candidate);
noted.insert(candidate);
}
while(!toExplore.empty())
{
auto i = toExplore.front();
auto node = i.second;
toExplore.pop_front();
if(visitedNodes.contains(node))
continue;
visitedNodes.insert(node);
co_yield node;
for(auto connected : dir == Direction::Downstream ? m_incidence.getDsts(node)
: m_incidence.getSrcs(node))
{
std::pair<int, int> candidate = {node, connected};
if(!noted.contains(candidate))
{
toExplore.push_back(candidate);
noted.insert(candidate);
}
}
}
co_return;
}
template <typename Node, typename Edge, bool Hyper>
template <Direction Dir>
Generator<int> Hypergraph<Node, Edge, Hyper>::depthFirstVisit(
int start, std::unordered_set<int>& visitedNodes) const
{
if(visitedNodes.contains(start))
co_return;
visitedNodes.insert(start);
co_yield start;
for(auto element : getNeighbours<Dir>(start))
{
co_yield depthFirstVisit<Dir>(element, visitedNodes);
}
}
template <typename Node, typename Edge, bool Hyper>
template <Direction Dir, std::predicate<int> Predicate>
Generator<int> Hypergraph<Node, Edge, Hyper>::depthFirstVisit(
int start, Predicate edgePredicate, std::unordered_set<int>& visitedElements) const
{
if(visitedElements.contains(start))
co_return;
visitedElements.insert(start);
co_yield start;
for(auto tag : getNeighbours<Dir>(start))
{
visitedElements.insert(tag);
if(edgePredicate(tag))
{
for(auto child : getNeighbours<Dir>(tag))
{
co_yield depthFirstVisit<Dir>(child, edgePredicate, visitedElements);
}
}
}
}
template <typename Node, typename Edge, bool Hyper>
template <Direction Dir, CForwardRangeOf<int> RangeStart, CForwardRangeOf<int> RangeEnd>
Generator<int> Hypergraph<Node, Edge, Hyper>::path(RangeStart const& starts,
RangeEnd const& ends) const
{
auto truePred = [](int) { return true; };
co_yield path<Dir>(starts, ends, truePred);
}
template <typename Node, typename Edge, bool Hyper>
template <Direction Dir,
CForwardRangeOf<int> RangeStart,
CForwardRangeOf<int> RangeEnd,
std::predicate<int> Predicate>
Generator<int> Hypergraph<Node, Edge, Hyper>::path(RangeStart const& starts,
RangeEnd const& ends,
Predicate edgeSelector) const
{
std::map<int, bool> visitedElements;
co_yield path<Dir>(starts, ends, edgeSelector, visitedElements);
}
template <typename Node, typename Edge, bool Hyper>
template <Direction Dir,
CForwardRangeOf<int> RangeStart,
CForwardRangeOf<int> RangeEnd,
std::predicate<int> Predicate>
Generator<int>
Hypergraph<Node, Edge, Hyper>::path(RangeStart const& starts,
RangeEnd const& ends,
Predicate edgeSelector,
std::map<int, bool>& visitedElements) const
{
constexpr Direction reverseDir = opposite(Dir);
for(auto end : ends)
{
if(visitedElements.contains(end))
{
continue;
}
if(std::count(starts.begin(), starts.end(), end) > 0)
{
visitedElements[end] = true;
co_yield end;
continue;
}
visitedElements[end] = false;
std::vector<int> results;
for(auto nextElement : getNeighbours<reverseDir>(end))
{
if(getElementType(nextElement) == ElementType::Edge
&& !edgeSelector(nextElement))
{
continue;
}
std::vector<int> branchResults
= path<Dir>(
starts, std::vector<int>{nextElement}, edgeSelector, visitedElements)
.template to<std::vector>();
results.insert(results.end(), branchResults.begin(), branchResults.end());
bool satisfied = (getElementType(end) != ElementType::Edge
|| edgeSatisfied<reverseDir>(end, visitedElements));
visitedElements[end] = visitedElements[end] || visitedElements[nextElement];
visitedElements[end] = visitedElements[end] && satisfied;
}
if(visitedElements.at(end))
{
for(auto const result : results)
{
co_yield result;
}
co_yield end;
}
}
}
template <typename Node, typename Edge, bool Hyper>
template <Direction Dir>
bool Hypergraph<Node, Edge, Hyper>::edgeSatisfied(
int const edge, std::map<int, bool> const& visitedElements) const
{
for(auto element : getNeighbours<Dir>(edge))
{
auto iter = visitedElements.find(element);
if(iter == visitedElements.end() || !iter->second)
return false;
}
return true;
}
template <typename Node, typename Edge, bool Hyper>
template <Direction Dir>
std::vector<int> Hypergraph<Node, Edge, Hyper>::getNeighbours(int const tag) const
{
AssertFatal(m_elements.contains(tag),
"Graph tag not registered, element not in graph",
ShowValue(tag));
if constexpr(Dir == Direction::Downstream)
{
return m_incidence.getDsts(tag);
}
else
{
return m_incidence.getSrcs(tag);
}
}
template <typename Node, typename Edge, bool Hyper>
std::vector<int> Hypergraph<Node, Edge, Hyper>::getNeighbours(int const tag,
Direction Dir) const
{
if(Dir == Direction::Downstream)
{
return getNeighbours<Direction::Downstream>(tag);
}
else
{
return getNeighbours<Direction::Upstream>(tag);
}
}
template <typename Node, typename Edge, bool Hyper>
inline Generator<int> Hypergraph<Node, Edge, Hyper>::topologicalSort() const
{
auto start = roots().template to<std::vector>();
auto end = leaves().template to<std::vector>();
co_yield path<Graph::Direction::Downstream>(start, end);
}
template <typename Node, typename Edge, bool Hyper>
inline Generator<int> Hypergraph<Node, Edge, Hyper>::reverseTopologicalSort() const
{
auto start = roots().template to<std::vector>();
auto end = leaves().template to<std::vector>();
co_yield path<Graph::Direction::Upstream>(end, start);
}
template <typename Node, typename Edge, bool Hyper>
std::string Hypergraph<Node, Edge, Hyper>::toDOT(std::string const& prefix,
bool standalone) const
{
std::ostringstream msg;
if(standalone)
msg << "digraph {" << std::endl;
for(auto const& pair : m_elements)
{
msg << '"' << prefix << pair.first << '"' << "[label=\"";
if(getElementType(pair.second) == ElementType::Node)
{
auto x = std::get<Node>(pair.second);
msg << truncate(toString(x)) << "(" << pair.first << ")\"";
}
else
{
auto x = std::get<Edge>(pair.second);
msg << truncate(toString(x)) << "(" << pair.first << ")\",shape=box";
}
msg << "];" << std::endl;
}
msg << m_incidence.toDOTSection(prefix);
// Enforce left-to-right ordering for elements connected to an edge.
for(auto const& pair : m_elements)
{
if(getElementType(pair.second) != ElementType::Edge)
continue;
auto const& loc = getLocation(pair.first);
if(loc.incoming.size() > 1)
{
msg << "{\nrank=same\n";
bool first = true;
for(auto idx : loc.incoming)
{
if(!first)
msg << "->";
msg << '"' << prefix << idx << '"';
first = false;
}
msg << "[style=invis]\nrankdir=LR\n}\n";
}
if(loc.outgoing.size() > 1)
{
msg << "{\nrank=same\n";
bool first = true;
for(auto idx : loc.outgoing)
{
if(!first)
msg << "->";
msg << '"' << prefix << idx << '"';
first = false;
}
msg << "[style=invis]\nrankdir=LR\n}\n";
}
}
if(standalone)
msg << "}" << std::endl;
return msg.str();
}
template <typename Node, typename Edge, bool Hyper>
template <std::predicate<Edge const&> Predicate>
std::string Hypergraph<Node, Edge, Hyper>::toDOT(Predicate edgePredicate) const
{
std::ostringstream msg;
std::string const prefix = "";
msg << "digraph {" << std::endl;
for(auto const& pair : m_elements)
{
if(getElementType(pair.second) == ElementType::Node)
{
auto x = std::get<Node>(pair.second);
msg << '"' << prefix << pair.first << '"' << "[label=\"";
msg << truncate(toString(x)) << "(" << pair.first << ")\"";
msg << "];" << std::endl;
}
else
{
auto x = std::get<Edge>(pair.second);
if(edgePredicate(x))
{
msg << '"' << prefix << pair.first << '"' << "[label=\"";
msg << truncate(toString(x)) << "(" << pair.first << ")\",shape=box";
msg << "];" << std::endl;
}
}
}
for(auto const& pair : m_elements)
{
if(getElementType(pair.second) == ElementType::Edge)
{
auto x = std::get<Edge>(pair.second);
if(edgePredicate(x))
{
for(auto y : getNeighbours<Direction::Upstream>(pair.first))
{
msg << '"' << prefix << y << "\" -> \"" << prefix << pair.first << '"'
<< std::endl;
}
for(auto y : getNeighbours<Direction::Downstream>(pair.first))
{
msg << '"' << prefix << pair.first << "\" -> \"" << prefix << y << '"'
<< std::endl;
}
}
}
}
msg << "}" << std::endl;
return msg.str();
}
template <typename Node, typename Edge, bool Hyper>
template <typename T>
requires(std::constructible_from<Node, T> || std::constructible_from<Edge, T>)
Generator<int> Hypergraph<Node, Edge, Hyper>::getElements()
const
{
for(auto const& elem : m_elements)
{
if constexpr(std::same_as<T, Node> || std::same_as<T, Edge>)
{
if(std::holds_alternative<T>(elem.second))
co_yield elem.first;
}
else if constexpr(std::constructible_from<Node, T>)
{
if(std::holds_alternative<Node>(elem.second))
{
auto const& node = std::get<Node>(elem.second);
if(std::holds_alternative<T>(node))
co_yield elem.first;
}
}
else if constexpr(std::constructible_from<Edge, T>)
{
if(std::holds_alternative<Edge>(elem.second))
{
auto const& edge = std::get<Edge>(elem.second);
if(std::holds_alternative<T>(edge))
co_yield elem.first;
}
}
}
}
template <typename Node, typename Edge, bool Hyper>
template <typename T>
requires(std::constructible_from<Node, T>)
Generator<int> Hypergraph<Node, Edge, Hyper>::getNodes()
const
{
co_yield getElements<T>();
}
template <typename Node, typename Edge, bool Hyper>
template <typename T>
requires(std::constructible_from<Edge, T>)
Generator<int> Hypergraph<Node, Edge, Hyper>::getEdges()
const
{
co_yield getElements<T>();
}
template <typename Node, typename Edge, bool Hyper>
template <typename T, Direction Dir>
requires(std::constructible_from<Edge, T>)
Generator<int> Hypergraph<Node, Edge, Hyper>::getConnectedNodeIndices(int const dst)
const
{
if constexpr(std::same_as<Edge, T>)
{
auto truePredicate = [](auto const&) { return true; };
co_yield getConnectedNodeIndices<Dir>(dst, truePredicate);
}
else
{
auto edgePredicate
= [](Edge const& edge) { return std::holds_alternative<T>(edge); };
co_yield getConnectedNodeIndices<Dir>(dst, edgePredicate);
}
}
template <typename Node, typename Edge, bool Hyper>
template <Direction Dir, std::predicate<Edge const&> Predicate>
Generator<int>
Hypergraph<Node, Edge, Hyper>::getConnectedNodeIndices(int const dst,
Predicate edgePredicate) const
{
AssertFatal(getElementType(dst) == ElementType::Node, "Require a node handle");
for(auto elem : getNeighbours<Dir>(dst))
{
if(edgePredicate(std::get<Edge>(getElement(elem))))
{
for(auto tag : getNeighbours<Dir>(elem))
co_yield tag;
}
}
}
template <typename Node, typename Edge, bool Hyper>
template <typename T>
requires(std::constructible_from<Edge, T>)
Generator<int> Hypergraph<Node, Edge, Hyper>::getInputNodeIndices(int const dst)
const
{
co_yield getConnectedNodeIndices<T, Direction::Upstream>(dst);
}
template <typename Node, typename Edge, bool Hyper>
template <std::predicate<Edge const&> Predicate>
Generator<int>
Hypergraph<Node, Edge, Hyper>::getInputNodeIndices(int const dst,
Predicate edgePredicate) const
{
co_yield getConnectedNodeIndices<Direction::Upstream>(dst, edgePredicate);
}
template <typename Node, typename Edge, bool Hyper>
template <typename T>
requires(std::constructible_from<Edge, T>)
Generator<int> Hypergraph<Node, Edge, Hyper>::getOutputNodeIndices(int const src)
const
{
co_yield getConnectedNodeIndices<T, Direction::Downstream>(src);
}
template <typename Node, typename Edge, bool Hyper>
template <std::predicate<Edge const&> Predicate>
Generator<int>
Hypergraph<Node, Edge, Hyper>::getOutputNodeIndices(int const src,
Predicate edgePredicate) const
{
co_yield getConnectedNodeIndices<Direction::Downstream>(src, edgePredicate);
}
template <typename Node, typename Edge, bool Hyper>
template <typename T>
requires(std::constructible_from<Edge, T>)
std::set<int> Hypergraph<Node, Edge, Hyper>::followEdges(
std::set<int> const& candidates)
const
{
// Nodes to be analyzed
std::set<int> currentNodes = candidates;
// Full set of connected nodes to be returned
std::set<int> connectedNodes = candidates;
auto numCandidates = connectedNodes.size();
do
{
// Nodes which are found by this sweep
std::set<int> foundNodes;
numCandidates = connectedNodes.size();
for(auto tag : currentNodes)
{
auto outTags = getOutputNodeIndices<T>(tag);
foundNodes.insert(outTags.begin(), outTags.end());
}
connectedNodes.insert(foundNodes.begin(), foundNodes.end());
currentNodes = std::move(foundNodes);
} while(numCandidates != connectedNodes.size());
return connectedNodes;
}
template <typename Node, typename Edge, bool Hyper>
inline std::ostream& operator<<(std::ostream& stream,
Hypergraph<Node, Edge, Hyper> const& graph)
{
return stream << graph.toDOT();
}
template <Graph::Direction Dir, typename Node, typename Edge, bool Hyper>
Generator<int> reachableNodes(Graph::Hypergraph<Node, Edge, Hyper> const& graph,
int start,
auto nodePredicate,
auto edgePredicate,
auto destNodePredicate)
{
for(auto nextNode : graph.template getConnectedNodeIndices<Dir>(start, edgePredicate))
{
auto const& node = graph.getNode(nextNode);
if(destNodePredicate(node))
co_yield nextNode;
if(nodePredicate(node))
co_yield reachableNodes<Dir>(
graph, nextNode, nodePredicate, edgePredicate, destNodePredicate);
}
}
template <typename Node, typename Edge, bool Hyper>
std::optional<int> Hypergraph<Node, Edge, Hyper>::findEdge(int tail, int head) const
{
static_assert(!Hyper, "findEdge not supported for hypergraphs.");
AssertFatal(m_elements.contains(tail) && m_elements.contains(head),
"Graph tags not registered, elements not in graph",
ShowValue(tail),
ShowValue(head));
auto dsts = m_incidence.getDsts(tail);
for(auto src : m_incidence.getSrcs(head))
{
auto rv = std::find(dsts.begin(), dsts.end(), src);
if(rv != dsts.end())
return *rv;
}
return std::nullopt;
}
template <typename Node, typename Edge, bool Hyper>
int Hypergraph<Node, Edge, Hyper>::nextAvailableTag() const
{
if(m_elements.empty())
return 1;
return m_elements.rbegin()->first + 1;
}
}
}