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
1175 lines
44 KiB
C++
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;
|
|
}
|
|
}
|
|
}
|