Skip to content

Commit

Permalink
Merge pull request idaholab#20778 from lindsayad/dfs-deps
Browse files Browse the repository at this point in the history
Do depth-first-search with new edge for cycle catching
  • Loading branch information
moosebuild committed Apr 14, 2022
2 parents 6933f73 + a2f13f9 commit 08d5cac
Showing 1 changed file with 75 additions and 5 deletions.
80 changes: 75 additions & 5 deletions framework/include/utils/DependencyResolver.h
Expand Up @@ -13,6 +13,9 @@
#include "Moose.h"
#include "MooseError.h"

#include "libmesh/utility.h"
#include "libmesh/simple_range.h"

// C++ includes
#include <map>
#include <set>
Expand Down Expand Up @@ -132,7 +135,35 @@ class DependencyResolver

bool operator()(const T & a, const T & b);

/**
* Enumeration used to implement the coloring-based depth-first search (DFS) algorithm described
* in https://www.geeksforgeeks.org/detect-cycle-direct-graph-using-colors/. The algorithm
* searches for back-edges. A back-edge is an edge that points from a node to itself or from one
* of a node's descendents (determined through DFS) to itself. The meaning of the colors used in
* this algorithm are:
* WHITE : Node is not processed yet. Initially, all nodes are WHITE.
* GRAY: Node is being processed (DFS for this node has started, but not finished which means that
* all descendants (in DFS tree) of this node are not processed yet (or this node is in the
* function call stack)
* BLACK : Node and all its descendants are processed.
*/
enum Color
{
BLACK,
WHITE,
GRAY
};

private:
/**
* Indicates whether any cyclic dependency is detected when descending a directed graph from this
* node. This is a depth-first search (DFS) algorithm based on
* https://www.geeksforgeeks.org/detect-cycle-direct-graph-using-colors/
* @param node The node in the directed graph from which to begin descent
* @return whether any cyclic dependency was discovered while descending from the node
*/
bool hasCycle(const T & node);

/**
* Helper classes for returning only keys or values in an iterator format
*/
Expand All @@ -156,6 +187,11 @@ class DependencyResolver
// mutual interdependencies
std::vector<T> _ordering_vector;

/// A map used to implement the depth-first search algorithm, based on
/// https://www.geeksforgeeks.org/detect-cycle-direct-graph-using-colors/, for use in detecting
/// cyclic dependencies
std::map<T, Color> _colors;

/// The sorted vector of sets
std::vector<std::vector<T>> _ordered_items;

Expand Down Expand Up @@ -226,25 +262,59 @@ DependencyResolver<T>::insertDependency(const T & key, const T & value)
if (_unique_deps.count(k) > 0)
return;
_unique_deps.insert(k);
auto insert_it = _depends.insert(k);

if (dependsOn(value, key))
for (auto & pr : _colors)
pr.second = WHITE;
_colors.emplace(key, WHITE);
_colors.emplace(value, WHITE);
if (hasCycle(key))
{
decltype(_depends) depends_copy(_depends);

// Insert the breaking dependency here so it will reflect properly in the exception
depends_copy.insert(k);
_depends.erase(insert_it);

throw CyclicDependencyException<T>(
"DependencyResolver: attempt to insert dependency will result in cyclic graph",
depends_copy);
}
_depends.insert(k);
if (std::find(_ordering_vector.begin(), _ordering_vector.end(), key) == _ordering_vector.end())
_ordering_vector.push_back(key);
if (std::find(_ordering_vector.begin(), _ordering_vector.end(), value) == _ordering_vector.end())
_ordering_vector.push_back(value);
}

template <typename T>
bool
DependencyResolver<T>::hasCycle(const T & root)
{
// Catch as reference since we'll modify this value
auto & root_color = libmesh_map_find(_colors, root);
root_color = GRAY;

auto [first, last] = _depends.equal_range(root);
for (auto & val : as_range(first, last))
{
const auto & adj = val.second;
// We'll copy since its a builtin and we don't need to modify this value
const auto color_adj = libmesh_map_find(_colors, adj);

if (color_adj == GRAY)
// We're in the act of processing this node which means that we've now encountered this node
// twice during descent and we have a cycle
return true;

// We are not currently processing (GRAY) nor have we already processed (BLACK) the adjacent
// node, so we continue to descend
if (color_adj == WHITE && hasCycle(adj))
return true;
}

// We did not encounter any cycles while descending from this node
root_color = BLACK;

return false;
}

template <typename T>
void
DependencyResolver<T>::deleteDependency(const T & key, const T & value)
Expand Down

0 comments on commit 08d5cac

Please sign in to comment.