https://mooseframework.inl.gov
DependencyResolver.h
Go to the documentation of this file.
1 //* This file is part of the MOOSE framework
2 //* https://mooseframework.inl.gov
3 //*
4 //* All rights reserved, see COPYRIGHT for full restrictions
5 //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 //*
7 //* Licensed under LGPL 2.1, please see LICENSE for details
8 //* https://www.gnu.org/licenses/lgpl-2.1.html
9 
10 #pragma once
11 
12 // MOOSE includes
13 #include "Moose.h"
14 #include "MooseError.h"
15 
16 #include "libmesh/utility.h"
17 #include "libmesh/simple_range.h"
18 
19 // C++ includes
20 #include <map>
21 #include <string>
22 #include <vector>
23 #include <list>
24 #include <algorithm>
25 #include <sstream>
26 #include <exception>
27 
28 template <class T, class Compare>
30 
34 template <class T, class Compare = std::less<T>>
36 {
37 public:
38  DependencyResolver() = default;
39  ~DependencyResolver() = default;
40 
44  void addNode(const T & a);
45 
49  void addEdge(const T & a, const T & b);
50 
54  void removeEdge(const T & a, const T & b);
55 
59  void removeEdgesInvolving(const T & a);
60 
65  void insertDependency(const T & key, const T & value) { addEdge(value, key); }
66 
70  void deleteDependency(const T & key, const T & value) { removeEdge(value, key); }
71 
75  void deleteDependenciesOfKey(const T & key) { removeEdgesInvolving(key); }
76 
80  void addItem(const T & value) { addNode(value); }
81 
85  void clear();
86 
91  const std::vector<T> & dfs();
92 
97  [[nodiscard]] const std::vector<std::vector<T>> & getSortedValuesSets();
98 
106  [[nodiscard]] const std::vector<T> & getSortedValues() { return dfs(); }
107 
119  [[nodiscard]] bool dependsOn(const T & key, const T & value);
120 
124  [[nodiscard]] bool dependsOn(const std::vector<T> & keys, const T & value);
125 
129  [[nodiscard]] std::list<T> getAncestors(const T & key);
130 
135  std::size_t size() const { return _sorted_vector.size(); }
136 
137 protected:
143  [[nodiscard]] bool dependsOnFromNode(const T & root, const T & item);
144 
151  bool dfsFromNode(const T & root);
152 
154  std::map<T, std::list<T>, Compare> _adj;
156  std::map<T, std::list<T>, Compare> _inv_adj;
158  std::map<T, bool, Compare> _visited;
160  std::size_t _num_visited = 0;
162  std::map<T, bool, Compare> _rec_stack;
164  std::vector<T> _sorted_vector;
166  std::vector<std::vector<T>> _ordered_items;
172  std::vector<T> _insertion_order;
174  T _circular_node = T{};
175 
176  friend class CyclicDependencyException<T, Compare>;
177 };
178 
179 template <class T, class Compare>
180 void
182 {
183 #ifndef NDEBUG
184  bool new_adj_insertion = false, new_inv_insertion = false;
185 #endif
186  if (_adj.find(a) == _adj.end())
187  {
188 #ifndef NDEBUG
189  new_adj_insertion = true;
190 #endif
191  _adj[a] = {};
192  _insertion_order.push_back(a);
193  }
194 
195  if (_inv_adj.find(a) == _inv_adj.end())
196  {
197 #ifndef NDEBUG
198  new_inv_insertion = true;
199 #endif
200  _inv_adj[a] = {};
201  }
202  mooseAssert(new_adj_insertion == new_inv_insertion,
203  "We should have symmetric behavior between adjacent and inverse-adjacent "
204  "insertion/non-insertion.");
205 }
206 
207 template <class T, class Compare>
208 void
210 {
211  addNode(a);
212  addNode(b);
213 
214  _adj[a].push_back(b);
215  _inv_adj[b].push_back(a);
216 }
217 
218 template <class T, class Compare>
219 void
221 {
222  auto remove_item = [](auto & list, const auto & item)
223  {
224  auto it = std::find(list.begin(), list.end(), item);
225  mooseAssert(it != list.end(), "We should have this item");
226  list.erase(it);
227  };
228  remove_item(_adj[a], b);
229  remove_item(_inv_adj[b], a);
230 }
231 
232 template <class T, class Compare>
233 void
235 {
236  const auto & inv_adjs = _inv_adj[a];
237  for (const auto & inv_adj : inv_adjs)
238  {
239  auto & adj = _adj[inv_adj];
240  auto it = std::find(adj.begin(), adj.end(), a);
241  mooseAssert(it != adj.end(), "Should have reciprocity");
242  adj.erase(it);
243  }
244 
245  _inv_adj[a].clear();
246 }
247 
248 template <class T, class Compare>
249 void
251 {
252  _adj.clear();
253  _inv_adj.clear();
254  _insertion_order.clear();
255 }
256 
257 template <class T, class Compare>
258 const std::vector<T> &
260 {
261  _sorted_vector.clear();
262  _visited.clear();
263  _num_visited = 0;
264  _rec_stack.clear();
265  _circular_node = T{};
266 
267  for (auto & n : _adj)
268  {
269  _visited[n.first] = false;
270  _rec_stack[n.first] = false;
271  }
272 
273  bool is_cyclic = false;
274 
275  // If there are no adjacencies, then all nodes are both roots and leaves
276  bool roots_found = _adj.empty();
277  for (auto & n : _insertion_order)
278  if (_adj[n].size() == 0)
279  {
280  roots_found = true;
281  is_cyclic = dfsFromNode(n);
282  if (is_cyclic)
283  break;
284  }
285 
286  if (roots_found)
287  {
288  // At this point, if we have any sub-graphs that are cyclic that do not have any
289  // roots _and_ we have found one more or more separate sub-graphs with a root,
290  // we will have never visited the aforementioned cyclic sub-graphs. Therefore,
291  // at this point if we haven't visited something it's a part of a cyclic sub-graph
292  if (!is_cyclic && _num_visited != _insertion_order.size())
293  {
294  for (const auto & [n, visited] : _visited)
295  if (!visited && (is_cyclic = dfsFromNode(n)))
296  break;
297 
298  mooseAssert(is_cyclic, "Should have found a cyclic dependency");
299  }
300  }
301  // Didn't find any roots
302  else
303  {
304  is_cyclic = true;
305  // Create a cycle graph
306  for (auto & n : _insertion_order)
307  if (dfsFromNode(n))
308  break;
309  }
310 
311  if (is_cyclic)
312  throw CyclicDependencyException<T, Compare>("cyclic graph detected", *this);
313 
314  mooseAssert(_sorted_vector.size() == _insertion_order.size(), "Unexpected sorted size");
315  mooseAssert(_num_visited == _insertion_order.size(), "Did not visit all nodes");
316 
317  return _sorted_vector;
318 }
319 
320 template <class T, class Compare>
321 const std::vector<std::vector<T>> &
323 {
324  _ordered_items.clear();
325 
326  const auto & flat_sorted = dfs();
327 
328  std::vector<T> current_group;
329  for (const auto & object : flat_sorted)
330  {
331  if (current_group.empty())
332  {
333  current_group.push_back(object);
334  continue;
335  }
336 
337  const auto & prev_adj_list = _adj[current_group.back()];
338  const bool depends_on_prev =
339  std::find(prev_adj_list.begin(), prev_adj_list.end(), object) != prev_adj_list.end();
340 
341  if (depends_on_prev)
342  {
343  _ordered_items.push_back({object});
344  auto & finalized_group = _ordered_items.back();
345  // Swap the current-group into the back of our ordered items container, and now our newest
346  // object becomes the current group
347  finalized_group.swap(current_group);
348  }
349  else
350  current_group.push_back(object);
351  }
352 
353  if (!current_group.empty())
354  _ordered_items.push_back(std::move(current_group));
355 
356  return _ordered_items;
357 }
358 
359 template <class T, class Compare>
360 bool
361 DependencyResolver<T, Compare>::dependsOn(const T & key, const T & value)
362 {
363  if (_adj.find(value) == _adj.end())
364  return false;
365 
366  for (auto & n : _adj)
367  _visited[n.first] = false;
368 
369  return dependsOnFromNode(key, value);
370 }
371 
372 template <class T, class Compare>
373 bool
374 DependencyResolver<T, Compare>::dependsOn(const std::vector<T> & keys, const T & value)
375 {
376  if (_adj.find(value) == _adj.end())
377  return false;
378 
379  for (auto & n : _adj)
380  _visited[n.first] = false;
381 
382  for (const auto & key : keys)
383  if (dependsOnFromNode(key, value))
384  return true;
385 
386  return false;
387 }
388 
389 template <class T, class Compare>
390 std::list<T>
392 {
393  std::vector<T> ret_vec;
394  // Our sorted vector is our work vector but we also return references to it. So we have to make
395  // sure at the end that we restore the original data we had in it
396  ret_vec.swap(_sorted_vector);
397 
398  for (auto & n : _adj)
399  _visited[n.first] = false;
400 
401  dfsFromNode(key);
402 
403  ret_vec.swap(_sorted_vector);
404  mooseAssert(ret_vec.back() == key, "Our key should be the back of the vector");
405 
406  return {ret_vec.begin(), ret_vec.end()};
407 }
408 
409 template <class T, class Compare>
410 bool
412 {
413  if (root == item)
414  return true;
415 
416  _visited[root] = true;
417 
418  auto & my_dependencies = _inv_adj[root];
419 
420  for (auto & i : my_dependencies)
421  if (!_visited.at(i) && dependsOnFromNode(i, item))
422  return true;
423 
424  return false;
425 }
426 
427 template <class T, class Compare>
428 bool
430 {
431  bool cyclic = false;
432  auto & visited = libmesh_map_find(_visited, root);
433  if (!visited)
434  {
435  ++_num_visited;
436  visited = true;
437  }
438  _rec_stack[root] = true;
439 
440  for (auto & i : _inv_adj[root])
441  {
442  if (!_visited.at(i) && dfsFromNode(i))
443  {
444  cyclic = true;
445  break;
446  }
447  else if (_rec_stack.at(i))
448  {
449  _circular_node = i;
450  _sorted_vector.push_back(i);
451  cyclic = true;
452  break;
453  }
454  }
455 
456  _sorted_vector.push_back(root);
457  _rec_stack[root] = false;
458  return cyclic;
459 }
460 
461 template <class T, class Compare = std::less<T>>
462 class CyclicDependencyException : public std::runtime_error
463 {
464 public:
465  CyclicDependencyException(const std::string & error,
466  const DependencyResolver<T, Compare> & graph) throw()
467  : runtime_error(error)
468  {
469  const auto & sorted = graph._sorted_vector;
470  auto first_occurence = std::find(sorted.begin(), sorted.end(), graph._circular_node);
471  if (first_occurence == sorted.end())
472  mooseError("We must have at least one occurence of the circular node");
473  auto second_occurence = std::find(first_occurence + 1, sorted.end(), graph._circular_node);
474  if (second_occurence == sorted.end())
475  mooseError("We must have two occurences of the circular node");
476  _cyclic_graph = std::vector<T>(first_occurence, second_occurence + 1);
477  }
478 
480  : runtime_error(e), _cyclic_graph(e._cyclic_graph)
481  {
482  }
483 
485 
486  const std::vector<T> & getCyclicDependencies() const { return _cyclic_graph; }
487 
488 private:
489  std::vector<T> _cyclic_graph;
490 };
std::map< T, bool, Compare > _rec_stack
recursive stack
void clear()
Clear Items from the resolver.
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application...
Definition: MooseError.h:302
std::size_t size() const
Returns the number of unique items stored in the dependency resolver.
const std::vector< T > & getSortedValues()
This function also returns dependency resolved values but with a simpler single vector interface...
void deleteDependency(const T &key, const T &value)
Delete a dependency (only the edge) between items in the resolver.
bool dependsOnFromNode(const T &root, const T &item)
depth first search from a root node, searching for a specific item
bool dfsFromNode(const T &root)
Perform a depth-first-search from the specified root node.
bool dependsOn(const T &key, const T &value)
Return true if key depends on value.
std::vector< std::vector< T > > _ordered_items
The sorted vector of sets.
~DependencyResolver()=default
std::list< T > getAncestors(const T &key)
Returns a list of all values that a given key depends on.
void removeEdgesInvolving(const T &a)
Remove edges drawn from &#39;a&#39;.
const std::vector< std::vector< T > > & getSortedValuesSets()
Returns a vector of sets that represent dependency resolved values.
void addEdge(const T &a, const T &b)
Add an edge between nodes &#39;a&#39; and &#39;b&#39;.
std::vector< T > _insertion_order
Container for keeping track of the insertion order.
void addItem(const T &value)
Add an independent item to the set.
T _circular_node
Data member for storing nodes that appear circularly in the graph.
Real value(unsigned n, unsigned alpha, unsigned beta, Real x)
std::map< T, std::list< T >, Compare > _inv_adj
adjacency lists (from roots to leaves)
void deleteDependenciesOfKey(const T &key)
Removes dependencies of the given key.
std::size_t _num_visited
number of visited nodes; used to avoid iterating through _visited
std::map< T, std::list< T >, Compare > _adj
adjacency lists (from leaves to roots)
std::vector< T > _sorted_vector
"sorted" vector of nodes
void addNode(const T &a)
Add a node &#39;a&#39; to the graph.
const std::vector< T > & getCyclicDependencies() const
std::map< T, bool, Compare > _visited
vector of visited nodes
const std::vector< T > & dfs()
Do depth-first search from root nodes to obtain order in which graph nodes should be "executed"...
void removeEdge(const T &a, const T &b)
Remove an edge between nodes &#39;a&#39; and &#39;b&#39;.
Class that represents the dependecy as a graph.
void insertDependency(const T &key, const T &value)
Insert a dependency pair - the first value or the "key" depends on the second value or the "value"...
CyclicDependencyException(const std::string &error, const DependencyResolver< T, Compare > &graph)
DependencyResolver()=default
CyclicDependencyException(const CyclicDependencyException &e)