https://mooseframework.inl.gov
Loading...
Searching...
No Matches
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
28template <class T, class Compare>
30
34template <class T, class Compare = std::less<T>>
36{
37public:
38 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
137protected:
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;
175
176 friend class CyclicDependencyException<T, Compare>;
177};
178
179template <class T, class Compare>
180void
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
207template <class T, class Compare>
208void
210{
211 addNode(a);
212 addNode(b);
213
214 _adj[a].push_back(b);
215 _inv_adj[b].push_back(a);
216}
217
218template <class T, class Compare>
219void
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
232template <class T, class Compare>
233void
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
248template <class T, class Compare>
249void
251{
252 _adj.clear();
253 _inv_adj.clear();
254 _insertion_order.clear();
255}
256
257template <class T, class Compare>
258const 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
320template <class T, class Compare>
321const 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
359template <class T, class Compare>
360bool
361DependencyResolver<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
372template <class T, class Compare>
373bool
374DependencyResolver<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
389template <class T, class Compare>
390std::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
409template <class T, class Compare>
410bool
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
427template <class T, class Compare>
428bool
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
461template <class T, class Compare = std::less<T>>
462class CyclicDependencyException : public std::runtime_error
463{
464public:
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
488private:
489 std::vector<T> _cyclic_graph;
490};
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application.
Definition MooseError.h:311
CyclicDependencyException(const CyclicDependencyException &e)
const std::vector< T > & getCyclicDependencies() const
CyclicDependencyException(const std::string &error, const DependencyResolver< T, Compare > &graph)
Class that represents the dependecy as a graph.
bool dependsOn(const T &key, const T &value)
Return true if key depends on value.
void addNode(const T &a)
Add a node 'a' to the graph.
std::map< T, bool, Compare > _rec_stack
recursive stack
std::list< T > getAncestors(const T &key)
Returns a list of all values that a given key depends on.
T _circular_node
Data member for storing nodes that appear circularly in the graph.
bool dfsFromNode(const T &root)
Perform a depth-first-search from the specified root node.
DependencyResolver()=default
std::vector< std::vector< T > > _ordered_items
The sorted vector of sets.
std::map< T, std::list< T >, Compare > _adj
adjacency lists (from leaves to roots)
~DependencyResolver()=default
std::vector< T > _sorted_vector
"sorted" vector of nodes
void deleteDependency(const T &key, const T &value)
Delete a dependency (only the edge) between items in the resolver.
std::size_t _num_visited
number of visited nodes; used to avoid iterating through _visited
bool dependsOn(const std::vector< T > &keys, const T &value)
Return true if any of elements of keys depends on value.
const std::vector< T > & dfs()
Do depth-first search from root nodes to obtain order in which graph nodes should be "executed".
std::map< T, std::list< T >, Compare > _inv_adj
adjacency lists (from roots to leaves)
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.
std::size_t size() const
Returns the number of unique items stored in the dependency resolver.
void clear()
Clear Items from the resolver.
const std::vector< T > & getSortedValues()
This function also returns dependency resolved values but with a simpler single vector interface.
void removeEdgesInvolving(const T &a)
Remove edges drawn from 'a'.
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".
void removeEdge(const T &a, const T &b)
Remove an edge between nodes 'a' and 'b'.
void addEdge(const T &a, const T &b)
Add an edge between nodes 'a' and 'b'.
void deleteDependenciesOfKey(const T &key)
Removes dependencies of the given key.
bool dependsOnFromNode(const T &root, const T &item)
depth first search from a root node, searching for a specific item
const std::vector< std::vector< T > > & getSortedValuesSets()
Returns a vector of sets that represent dependency resolved values.
std::map< T, bool, Compare > _visited
vector of visited nodes