aGrUM  0.20.3
a C++ library for (probabilistic) graphical models
DAGCycleDetector.cpp
Go to the documentation of this file.
1 /**
2  *
3  * Copyright (c) 2005-2021 by Pierre-Henri WUILLEMIN(@LIP6) & Christophe GONZALES(@AMU)
4  * info_at_agrum_dot_org
5  *
6  * This library is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this library. If not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20 
21 
22 /** @file
23  * @brief A class for detecting directed cycles in DAGs when trying to apply
24  * many changes to the graph
25  *
26  * When trying to assess whether multiple changes applied to a given DAG would
27  * induce cycles, use class DAGCycleDetector instead of trying to apply the
28  * modifications to the DAG itself and check whether and exception is raised:
29  * the class is designed to be fast for such modifications. However, the number
30  * of modifications checked should be higher than at least 3 for this class to
31  * be competitive.
32  * @author Christophe GONZALES(@AMU) and Pierre-Henri WUILLEMIN(@LIP6)
33  */
34 
35 #include <agrum/tools/core/sequence.h>
36 #include <agrum/tools/graphs/algorithms/DAGCycleDetector.h>
37 
38 #ifdef GUM_NO_INLINE
39 # include <agrum/tools/graphs/algorithms/DAGCycleDetector_inl.h>
40 #endif // GUM_NOINLINE
41 
42 namespace gum {
43 
44  /// sets the initial DAG from which changes shall be applied
45  void DAGCycleDetector::setDAG(const DAG& dag) {
46  // sets the dag
47  _dag_ = dag;
48 
49  // get the set of roots and leaves of the dag
50  List< NodeId > roots, leaves;
52 
53  for (const auto node: dag) {
56 
57  if (!nb_ch) leaves.insert(node);
58 
61 
62  if (!nb_pa) roots.insert(node);
63  }
64 
65  // recompute the set of ancestors
68 
69  for (NodeId node: dag) {
71  }
72 
73  while (!roots.empty()) {
74  // get a node and update the ancestors of its children
75  NodeId node = roots.front();
79  roots.popFront();
80 
81  for (const auto ch: node_children) {
83  --nb_parents[ch];
84 
85  if (!nb_parents[ch]) { roots.insert(ch); }
86  }
87  }
88 
89  // recompute the set of descendants
91 
92  for (const auto node: dag) {
94  }
95 
96  while (!leaves.empty()) {
97  // get a node and update the descendants of its parents
98  NodeId node = leaves.front();
101  const NodeSet& node_parents = dag.parents(node);
102  leaves.popFront();
103 
104  for (const auto pa: node_parents) {
106  --nb_children[pa];
107 
108  if (!nb_children[pa]) { leaves.insert(pa); }
109  }
110  }
111  }
112 
113  /// indicates whether a set of modifications would create a cycle
115  // first, we separate deletions and insertions (reversals are cut into
116  // a deletion + an insertion) and we check that no insertion also exists
117  // as a deletion (if so, we remove both operations). In addition, if
118  // we try to add an arc (X,X) we return that it induces a cycle
121 
122  for (const auto& modif: modifs) {
123  Arc arc(modif.tail(), modif.head());
124 
125  switch (modif.type()) {
126  case ChangeType::ARC_DELETION:
127  if (deletions.exists(arc))
128  ++deletions[arc];
129  else
130  deletions.insert(arc, 1);
131 
132  break;
133 
134  case ChangeType::ARC_ADDITION:
135  if (modif.tail() == modif.head()) return true;
136 
137  if (additions.exists(arc))
138  ++additions[arc];
139  else
140  additions.insert(arc, 1);
141 
142  break;
143 
144  case ChangeType::ARC_REVERSAL:
145  if (deletions.exists(arc))
146  ++deletions[arc];
147  else
148  deletions.insert(arc, 1);
149 
150  arc = Arc(modif.head(), modif.tail());
151 
152  if (additions.exists(arc))
153  ++additions[arc];
154  else
155  additions.insert(arc, 1);
156 
157  break;
158 
159  default:
160  GUM_ERROR(OperationNotAllowed, "undefined change type")
161  }
162  }
163 
164  for (auto iter = additions.beginSafe(); // safe iterator needed here
165  iter != additions.endSafe();
166  ++iter) {
167  if (deletions.exists(iter.key())) {
168  Size& nb_del = deletions[iter.key()];
169  Size& nb_add = iter.val();
170 
171  if (nb_del > nb_add) {
173  nb_del -= nb_add;
174  } else if (nb_del < nb_add) {
175  deletions.erase(iter.key());
176  nb_add -= nb_del;
177  } else {
178  deletions.erase(iter.key());
180  }
181  }
182  }
183 
184  // get the set of nodes involved in the modifications
186 
187  for (const auto& modif: modifs) {
190  }
191 
192  // we now retrieve the set of ancestors and descendants of all the
193  // extremities of the arcs involved in the modifications. We also
194  // keep track of all the children and parents of these nodes
196 
197  for (const auto& modif: modifs) {
198  if (!ancestors.exists(modif.tail())) {
201 
205  }
206 
207  if (!ancestors.exists(modif.head())) {
210 
214  }
215  }
216 
217  // we apply all the suppressions:
218  // assume that the modif concerns arc (X,Y). Then, after removing
219  // arc (X,Y), the sets of descendants of the nodes T in
220  // ( X + ancestors (X) ) are updated by subtracting the number of paths
221  // leading to Y and its set of descendants. These numbers are equal to
222  // the numbers found in descendants[X] * the numbers of paths leading
223  // to X, i.e., descendants[T][X].
224  // By symmetry, the set of ancestors of nodes T in ( Y + descendants (Y) )
225  // are
226  // updated by subtracting the number of paths leading to X and its
227  // ancestors.
228  for (auto iter = deletions.cbegin(); iter != deletions.cend(); ++iter) {
229  const Arc& arc = iter.key();
230  const NodeId tail = arc.tail();
231  const NodeProperty< Size >& anc_tail = ancestors[tail];
232  const NodeId head = arc.head();
234 
235  // update the set of descendants
237  set_to_del.insert(head, 1);
239 
240  for (auto iter = anc_tail.cbegin(); iter != anc_tail.cend(); ++iter) {
242  }
243 
244  // update the set of ancestors
246  set_to_del.insert(tail, 1);
248 
249  for (auto iter = desc_head.cbegin(); iter != desc_head.cend(); ++iter) {
251  }
252  }
253 
254  // now we apply all the additions of arcs (including the additions after
255  // arc reversals). After adding arc (X,Y), the set of ancestors T of Y and
256  // its
257  // descendants shall be updated by adding the number of paths leading to
258  // X and its ancestors, i.e., ancestors[X] * the number of paths leading
259  // to Y, i.e., ancestors[T][Y]. Similarly, the set of descendants of X and
260  // its
261  // ancestors should be updated by adding the number of paths leading to Y
262  // and
263  // its descendants. Finally, an arc (X,Y) can be added if and
264  // only if Y does not belong to the ancestor set of X
265  for (auto iter = additions.cbegin(); iter != additions.cend(); ++iter) {
266  const Arc& arc = iter.key();
267  NodeId tail = arc.tail();
268  NodeId head = arc.head();
269 
270  const NodeProperty< Size >& anc_tail = ancestors[tail];
271 
272  if (anc_tail.exists(head)) { return true; }
273 
275 
276  // update the set of ancestors
278  set_to_add.insert(tail, 1);
280 
281  for (auto iter = desc_head.cbegin(); iter != desc_head.cend(); ++iter) {
283  }
284 
285  // update the set of descendants
287  set_to_add.insert(head, 1);
289 
290  for (auto iter = anc_tail.cbegin(); iter != anc_tail.cend(); ++iter) {
292  }
293  }
294 
295  return false;
296  }
297 
298  /// adds a new arc to the current DAG
300  // check that the arc does not already exist
301  if (_dag_.existsArc(tail, head)) return;
302 
303  // check that the arc would not create a cycle
305  GUM_ERROR(InvalidDirectedCycle, "the arc would create a directed into a DAG")
306  }
307 
308  _dag_.addArc(tail, head);
309 
310  // now we apply the addition of the arc as done in method
311  // hasCycleFromModifications
314 
315  // update the set of ancestors
317  set_to_add.insert(tail, 1);
319 
320  for (auto iter = desc_head.cbegin(); iter != desc_head.cend(); ++iter) {
322  }
323 
324  // update the set of descendants
326  set_to_add.insert(head, 1);
328 
329  for (auto iter = anc_tail.cbegin(); iter != anc_tail.cend(); ++iter) {
331  }
332  }
333 
334  /// removes an arc from the current DAG
336  // check that the arc exists
337  if (!_dag_.existsArc(tail, head)) return;
338 
340 
341  // we apply the deletion of the arc as done in method
342  // hasCycleFromModifications
345 
346  // update the set of descendants
348  set_to_del.insert(head, 1);
350 
351  for (auto iter = anc_tail.cbegin(); iter != anc_tail.cend(); ++iter) {
353  }
354 
355  // update the set of ancestors
357  set_to_del.insert(tail, 1);
359 
360  for (auto iter = desc_head.cbegin(); iter != desc_head.cend(); ++iter) {
362  }
363  }
364 
365 } /* namespace gum */
INLINE void emplace(Args &&... args)
Definition: set_tpl.h:643