LCOV - code coverage report
Current view: top level - include/utils - DependencyResolver.h (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 2bf808 Lines: 138 145 95.2 %
Date: 2025-07-17 01:28:37 Functions: 113 175 64.6 %
Legend: Lines: hit not hit

          Line data    Source code
       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>
      29             : class CyclicDependencyException;
      30             : 
      31             : /**
      32             :  * Class that represents the dependecy as a graph
      33             :  */
      34             : template <class T, class Compare = std::less<T>>
      35             : class DependencyResolver
      36             : {
      37             : public:
      38      350157 :   DependencyResolver() = default;
      39      344252 :   ~DependencyResolver() = default;
      40             : 
      41             :   /**
      42             :    * Add a node 'a' to the graph
      43             :    */
      44             :   void addNode(const T & a);
      45             : 
      46             :   /**
      47             :    * Add an edge between nodes 'a' and 'b'
      48             :    */
      49             :   void addEdge(const T & a, const T & b);
      50             : 
      51             :   /**
      52             :    * Remove an edge between nodes 'a' and 'b'
      53             :    */
      54             :   void removeEdge(const T & a, const T & b);
      55             : 
      56             :   /**
      57             :    * Remove edges drawn from 'a'
      58             :    */
      59             :   void removeEdgesInvolving(const T & a);
      60             : 
      61             :   /**
      62             :    * Insert a dependency pair - the first value or the "key" depends on the second value or the
      63             :    * "value"
      64             :    */
      65    10909383 :   void insertDependency(const T & key, const T & value) { addEdge(value, key); }
      66             : 
      67             :   /**
      68             :    * Delete a dependency (only the edge) between items in the resolver
      69             :    */
      70           2 :   void deleteDependency(const T & key, const T & value) { removeEdge(value, key); }
      71             : 
      72             :   /**
      73             :    * Removes dependencies of the given key
      74             :    */
      75           1 :   void deleteDependenciesOfKey(const T & key) { removeEdgesInvolving(key); }
      76             : 
      77             :   /**
      78             :    * Add an independent item to the set
      79             :    */
      80     8948328 :   void addItem(const T & value) { addNode(value); }
      81             : 
      82             :   /**
      83             :    * Clear Items from the resolver
      84             :    */
      85             :   void clear();
      86             : 
      87             :   /**
      88             :    * Do depth-first search from root nodes to obtain order in which graph nodes should be
      89             :    * "executed".
      90             :    */
      91             :   const std::vector<T> & dfs();
      92             : 
      93             :   /**
      94             :    * Returns a vector of sets that represent dependency resolved values.  Items in the same
      95             :    * subvector have no dependence upon one and other.
      96             :    */
      97             :   [[nodiscard]] const std::vector<std::vector<T>> & getSortedValuesSets();
      98             : 
      99             :   /**
     100             :    * This function also returns dependency resolved values but with a simpler single vector
     101             :    * interface.
     102             :    * Some information may be lost as values at the same level that don't depend on one and other
     103             :    * can't
     104             :    * be represented in a single vector.  This isn't a problem in practice though.
     105             :    */
     106       69272 :   [[nodiscard]] const std::vector<T> & getSortedValues() { return dfs(); }
     107             : 
     108             :   /**
     109             :    * Return true if key depends on value.
     110             :    * That is, return true, if a chain of calls of the form
     111             :    * insertDependency(key, v0)
     112             :    * insertDependency(v0, v1)
     113             :    * insertDependency(v1, v2)
     114             :    * ...
     115             :    * insertDependency(vN, value)
     116             :    * has been performed.
     117             :    * dependsOn(x, x) always returns true
     118             :    */
     119             :   [[nodiscard]] bool dependsOn(const T & key, const T & value);
     120             : 
     121             :   /**
     122             :    * Return true if any of elements of keys depends on value
     123             :    */
     124             :   [[nodiscard]] bool dependsOn(const std::vector<T> & keys, const T & value);
     125             : 
     126             :   /**
     127             :    * Returns a list of all values that a given key depends on
     128             :    */
     129             :   [[nodiscard]] std::list<T> getAncestors(const T & key);
     130             : 
     131             :   /**
     132             :    * Returns the number of unique items stored in the dependency resolver. lindsayad comment: does
     133             :    * it really return the number of *unique* items?
     134             :    */
     135       21837 :   std::size_t size() const { return _sorted_vector.size(); }
     136             : 
     137             : protected:
     138             :   /**
     139             :    * depth first search from a root node, searching for a specific item
     140             :    * @param root The node we start from
     141             :    * @param item The node we are searching for
     142             :    */
     143             :   [[nodiscard]] bool dependsOnFromNode(const T & root, const T & item);
     144             : 
     145             :   /**
     146             :    * Perform a depth-first-search from the specified \p root node. Populates _visited and
     147             :    * _sorted_vector data members
     148             :    * @return whether a cyclic graph was detected while performing the depth-first-search from the \p
     149             :    * root node
     150             :    */
     151             :   bool dfsFromNode(const T & root);
     152             : 
     153             :   /// adjacency lists (from leaves to roots)
     154             :   std::map<T, std::list<T>, Compare> _adj;
     155             :   /// adjacency lists (from roots to leaves)
     156             :   std::map<T, std::list<T>, Compare> _inv_adj;
     157             :   /// vector of visited nodes
     158             :   std::map<T, bool, Compare> _visited;
     159             :   /// number of visited nodes; used to avoid iterating through _visited
     160             :   std::size_t _num_visited = 0;
     161             :   /// recursive stack
     162             :   std::map<T, bool, Compare> _rec_stack;
     163             :   /// "sorted" vector of nodes
     164             :   std::vector<T> _sorted_vector;
     165             :   /// The sorted vector of sets
     166             :   std::vector<std::vector<T>> _ordered_items;
     167             :   /// Container for keeping track of the insertion order. We will use this to determine iteration
     168             :   /// order because it is essential that iteration order be sync'd across multiple
     169             :   /// processes. Iterating over maps with pointer keys, for example, can be out of sync on multiple
     170             :   /// processes. If dependency resolver memory usage shows up in profiling, we can consider making
     171             :   /// this a container of reference wrappers
     172             :   std::vector<T> _insertion_order;
     173             :   /// Data member for storing nodes that appear circularly in the graph
     174             :   T _circular_node = T{};
     175             : 
     176             :   friend class CyclicDependencyException<T, Compare>;
     177             : };
     178             : 
     179             : template <class T, class Compare>
     180             : void
     181    31732404 : DependencyResolver<T, Compare>::addNode(const T & a)
     182             : {
     183             : #ifndef NDEBUG
     184             :   bool new_adj_insertion = false, new_inv_insertion = false;
     185             : #endif
     186    31732404 :   if (_adj.find(a) == _adj.end())
     187             :   {
     188             : #ifndef NDEBUG
     189             :     new_adj_insertion = true;
     190             : #endif
     191     9664629 :     _adj[a] = {};
     192     9664629 :     _insertion_order.push_back(a);
     193             :   }
     194             : 
     195    31732404 :   if (_inv_adj.find(a) == _inv_adj.end())
     196             :   {
     197             : #ifndef NDEBUG
     198             :     new_inv_insertion = true;
     199             : #endif
     200     9664629 :     _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    31732404 : }
     206             : 
     207             : template <class T, class Compare>
     208             : void
     209    10994757 : DependencyResolver<T, Compare>::addEdge(const T & a, const T & b)
     210             : {
     211    10994757 :   addNode(a);
     212    10994757 :   addNode(b);
     213             : 
     214    10994757 :   _adj[a].push_back(b);
     215    10994757 :   _inv_adj[b].push_back(a);
     216    10994757 : }
     217             : 
     218             : template <class T, class Compare>
     219             : void
     220           4 : DependencyResolver<T, Compare>::removeEdge(const T & a, const T & b)
     221             : {
     222          16 :   auto remove_item = [](auto & list, const auto & item)
     223             :   {
     224           8 :     auto it = std::find(list.begin(), list.end(), item);
     225             :     mooseAssert(it != list.end(), "We should have this item");
     226           8 :     list.erase(it);
     227             :   };
     228           4 :   remove_item(_adj[a], b);
     229           4 :   remove_item(_inv_adj[b], a);
     230           4 : }
     231             : 
     232             : template <class T, class Compare>
     233             : void
     234           1 : DependencyResolver<T, Compare>::removeEdgesInvolving(const T & a)
     235             : {
     236           1 :   const auto & inv_adjs = _inv_adj[a];
     237           2 :   for (const auto & inv_adj : inv_adjs)
     238             :   {
     239           1 :     auto & adj = _adj[inv_adj];
     240           1 :     auto it = std::find(adj.begin(), adj.end(), a);
     241             :     mooseAssert(it != adj.end(), "Should have reciprocity");
     242           1 :     adj.erase(it);
     243             :   }
     244             : 
     245           1 :   _inv_adj[a].clear();
     246           1 : }
     247             : 
     248             : template <class T, class Compare>
     249             : void
     250           0 : DependencyResolver<T, Compare>::clear()
     251             : {
     252           0 :   _adj.clear();
     253           0 :   _inv_adj.clear();
     254           0 :   _insertion_order.clear();
     255           0 : }
     256             : 
     257             : template <class T, class Compare>
     258             : const std::vector<T> &
     259      349328 : DependencyResolver<T, Compare>::dfs()
     260             : {
     261      349328 :   _sorted_vector.clear();
     262      349328 :   _visited.clear();
     263      349328 :   _num_visited = 0;
     264      349328 :   _rec_stack.clear();
     265      349328 :   _circular_node = T{};
     266             : 
     267     9900958 :   for (auto & n : _adj)
     268             :   {
     269     9551630 :     _visited[n.first] = false;
     270     9551630 :     _rec_stack[n.first] = false;
     271             :   }
     272             : 
     273      349328 :   bool is_cyclic = false;
     274             : 
     275             :   // If there are no adjacencies, then all nodes are both roots and leaves
     276      349328 :   bool roots_found = _adj.empty();
     277     9900829 :   for (auto & n : _insertion_order)
     278     9551506 :     if (_adj[n].size() == 0)
     279             :     {
     280     1324177 :       roots_found = true;
     281     1324177 :       is_cyclic = dfsFromNode(n);
     282     1324177 :       if (is_cyclic)
     283           5 :         break;
     284             :     }
     285             : 
     286      349328 :   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      349323 :     if (!is_cyclic && _num_visited != _insertion_order.size())
     293             :     {
     294           2 :       for (const auto & [n, visited] : _visited)
     295           2 :         if (!visited && (is_cyclic = dfsFromNode(n)))
     296           1 :           break;
     297             : 
     298             :       mooseAssert(is_cyclic, "Should have found a cyclic dependency");
     299             :     }
     300             :   }
     301             :   // Didn't find any roots
     302             :   else
     303             :   {
     304           5 :     is_cyclic = true;
     305             :     // Create a cycle graph
     306           5 :     for (auto & n : _insertion_order)
     307           5 :       if (dfsFromNode(n))
     308           5 :         break;
     309             :   }
     310             : 
     311      349328 :   if (is_cyclic)
     312          11 :     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      349317 :   return _sorted_vector;
     318             : }
     319             : 
     320             : template <class T, class Compare>
     321             : const std::vector<std::vector<T>> &
     322       45807 : DependencyResolver<T, Compare>::getSortedValuesSets()
     323             : {
     324       45807 :   _ordered_items.clear();
     325             : 
     326       45807 :   const auto & flat_sorted = dfs();
     327             : 
     328       45803 :   std::vector<T> current_group;
     329      147490 :   for (const auto & object : flat_sorted)
     330             :   {
     331      101687 :     if (current_group.empty())
     332             :     {
     333       45803 :       current_group.push_back(object);
     334       45803 :       continue;
     335             :     }
     336             : 
     337       55884 :     const auto & prev_adj_list = _adj[current_group.back()];
     338             :     const bool depends_on_prev =
     339       55884 :         std::find(prev_adj_list.begin(), prev_adj_list.end(), object) != prev_adj_list.end();
     340             : 
     341       55884 :     if (depends_on_prev)
     342             :     {
     343       74208 :       _ordered_items.push_back({object});
     344       48359 :       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       48359 :       finalized_group.swap(current_group);
     348             :     }
     349             :     else
     350        7525 :       current_group.push_back(object);
     351             :   }
     352             : 
     353       45803 :   if (!current_group.empty())
     354       45803 :     _ordered_items.push_back(std::move(current_group));
     355             : 
     356       45803 :   return _ordered_items;
     357       71652 : }
     358             : 
     359             : template <class T, class Compare>
     360             : bool
     361          91 : DependencyResolver<T, Compare>::dependsOn(const T & key, const T & value)
     362             : {
     363          91 :   if (_adj.find(value) == _adj.end())
     364           1 :     return false;
     365             : 
     366         560 :   for (auto & n : _adj)
     367         470 :     _visited[n.first] = false;
     368             : 
     369          90 :   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>
     391       21838 : DependencyResolver<T, Compare>::getAncestors(const T & key)
     392             : {
     393       21838 :   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       21838 :   ret_vec.swap(_sorted_vector);
     397             : 
     398       67931 :   for (auto & n : _adj)
     399       46093 :     _visited[n.first] = false;
     400             : 
     401       21838 :   dfsFromNode(key);
     402             : 
     403       21838 :   ret_vec.swap(_sorted_vector);
     404             :   mooseAssert(ret_vec.back() == key, "Our key should be the back of the vector");
     405             : 
     406       43676 :   return {ret_vec.begin(), ret_vec.end()};
     407       21838 : }
     408             : 
     409             : template <class T, class Compare>
     410             : bool
     411         252 : DependencyResolver<T, Compare>::dependsOnFromNode(const T & root, const T & item)
     412             : {
     413         252 :   if (root == item)
     414          65 :     return true;
     415             : 
     416         187 :   _visited[root] = true;
     417             : 
     418         187 :   auto & my_dependencies = _inv_adj[root];
     419             : 
     420         285 :   for (auto & i : my_dependencies)
     421         168 :     if (!_visited.at(i) && dependsOnFromNode(i, item))
     422          70 :       return true;
     423             : 
     424         117 :   return false;
     425             : }
     426             : 
     427             : template <class T, class Compare>
     428             : bool
     429     9597658 : DependencyResolver<T, Compare>::dfsFromNode(const T & root)
     430             : {
     431     9597658 :   bool cyclic = false;
     432     9597658 :   auto & visited = libmesh_map_find(_visited, root);
     433     9597658 :   if (!visited)
     434             :   {
     435     9597658 :     ++_num_visited;
     436     9597658 :     visited = true;
     437             :   }
     438     9597658 :   _rec_stack[root] = true;
     439             : 
     440    20477163 :   for (auto & i : _inv_adj[root])
     441             :   {
     442    10879553 :     if (!_visited.at(i) && dfsFromNode(i))
     443             :     {
     444          37 :       cyclic = true;
     445          37 :       break;
     446             :     }
     447    10879516 :     else if (_rec_stack.at(i))
     448             :     {
     449          11 :       _circular_node = i;
     450          11 :       _sorted_vector.push_back(i);
     451          11 :       cyclic = true;
     452          11 :       break;
     453             :     }
     454             :   }
     455             : 
     456     9597658 :   _sorted_vector.push_back(root);
     457     9597658 :   _rec_stack[root] = false;
     458     9597658 :   return cyclic;
     459             : }
     460             : 
     461             : template <class T, class Compare = std::less<T>>
     462             : class CyclicDependencyException : public std::runtime_error
     463             : {
     464             : public:
     465          11 :   CyclicDependencyException(const std::string & error,
     466             :                             const DependencyResolver<T, Compare> & graph) throw()
     467          11 :     : runtime_error(error)
     468             :   {
     469          11 :     const auto & sorted = graph._sorted_vector;
     470          11 :     auto first_occurence = std::find(sorted.begin(), sorted.end(), graph._circular_node);
     471          11 :     if (first_occurence == sorted.end())
     472           0 :       mooseError("We must have at least one occurence of the circular node");
     473          11 :     auto second_occurence = std::find(first_occurence + 1, sorted.end(), graph._circular_node);
     474          11 :     if (second_occurence == sorted.end())
     475           0 :       mooseError("We must have two occurences of the circular node");
     476          11 :     _cyclic_graph = std::vector<T>(first_occurence, second_occurence + 1);
     477          11 :   }
     478             : 
     479             :   CyclicDependencyException(const CyclicDependencyException & e) throw()
     480             :     : runtime_error(e), _cyclic_graph(e._cyclic_graph)
     481             :   {
     482             :   }
     483             : 
     484           3 :   ~CyclicDependencyException() throw() {}
     485             : 
     486           9 :   const std::vector<T> & getCyclicDependencies() const { return _cyclic_graph; }
     487             : 
     488             : private:
     489             :   std::vector<T> _cyclic_graph;
     490             : };

Generated by: LCOV version 1.14