aGrUM  0.20.2
a C++ library for (probabilistic) graphical models
DAGCycleDetector.cpp
Go to the documentation of this file.
1 /**
2  *
3  * Copyright 2005-2020 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  const std::vector< Change >& modifs) const {
116  // first, we separate deletions and insertions (reversals are cut into
117  // a deletion + an insertion) and we check that no insertion also exists
118  // as a deletion (if so, we remove both operations). In addition, if
119  // we try to add an arc (X,X) we return that it induces a cycle
122 
123  for (const auto& modif: modifs) {
124  Arc arc(modif.tail(), modif.head());
125 
126  switch (modif.type()) {
127  case ChangeType::ARC_DELETION:
128  if (deletions.exists(arc))
129  ++deletions[arc];
130  else
131  deletions.insert(arc, 1);
132 
133  break;
134 
135  case ChangeType::ARC_ADDITION:
136  if (modif.tail() == modif.head()) return true;
137 
138  if (additions.exists(arc))
139  ++additions[arc];
140  else
141  additions.insert(arc, 1);
142 
143  break;
144 
145  case ChangeType::ARC_REVERSAL:
146  if (deletions.exists(arc))
147  ++deletions[arc];
148  else
149  deletions.insert(arc, 1);
150 
151  arc = Arc(modif.head(), modif.tail());
152 
153  if (additions.exists(arc))
154  ++additions[arc];
155  else
156  additions.insert(arc, 1);
157 
158  break;
159 
160  default:
161  GUM_ERROR(OperationNotAllowed, "undefined change type");
162  }
163  }
164 
165  for (auto iter = additions.beginSafe(); // safe iterator needed here
166  iter != additions.endSafe();
167  ++iter) {
168  if (deletions.exists(iter.key())) {
169  Size& nb_del = deletions[iter.key()];
170  Size& nb_add = iter.val();
171 
172  if (nb_del > nb_add) {
174  nb_del -= nb_add;
175  } else if (nb_del < nb_add) {
176  deletions.erase(iter.key());
177  nb_add -= nb_del;
178  } else {
179  deletions.erase(iter.key());
181  }
182  }
183  }
184 
185  // get the set of nodes involved in the modifications
187 
188  for (const auto& modif: modifs) {
191  }
192 
193  // we now retrieve the set of ancestors and descendants of all the
194  // extremities of the arcs involved in the modifications. We also
195  // keep track of all the children and parents of these nodes
197 
198  for (const auto& modif: modifs) {
199  if (!ancestors.exists(modif.tail())) {
200  NodeProperty< Size >& anc
203 
207  }
208 
209  if (!ancestors.exists(modif.head())) {
210  NodeProperty< Size >& anc
213 
217  }
218  }
219 
220  // we apply all the suppressions:
221  // assume that the modif concerns arc (X,Y). Then, after removing
222  // arc (X,Y), the sets of descendants of the nodes T in
223  // ( X + ancestors (X) ) are updated by subtracting the number of paths
224  // leading to Y and its set of descendants. These numbers are equal to
225  // the numbers found in descendants[X] * the numbers of paths leading
226  // to X, i.e., descendants[T][X].
227  // By symmetry, the set of ancestors of nodes T in ( Y + descendants (Y) )
228  // are
229  // updated by subtracting the number of paths leading to X and its
230  // ancestors.
231  for (auto iter = deletions.cbegin(); iter != deletions.cend(); ++iter) {
232  const Arc& arc = iter.key();
233  const NodeId tail = arc.tail();
234  const NodeProperty< Size >& anc_tail = ancestors[tail];
235  const NodeId head = arc.head();
237 
238  // update the set of descendants
240  set_to_del.insert(head, 1);
242 
243  for (auto iter = anc_tail.cbegin(); iter != anc_tail.cend(); ++iter) {
245  set_to_del,
246  descendants[iter.key()][tail]);
247  }
248 
249  // update the set of ancestors
251  set_to_del.insert(tail, 1);
253 
254  for (auto iter = desc_head.cbegin(); iter != desc_head.cend(); ++iter) {
256  set_to_del,
257  ancestors[iter.key()][head]);
258  }
259  }
260 
261  // now we apply all the additions of arcs (including the additions after
262  // arc reversals). After adding arc (X,Y), the set of ancestors T of Y and
263  // its
264  // descendants shall be updated by adding the number of paths leading to
265  // X and its ancestors, i.e., ancestors[X] * the number of paths leading
266  // to Y, i.e., ancestors[T][Y]. Similarly, the set of descendants of X and
267  // its
268  // ancestors should be updated by adding the number of paths leading to Y
269  // and
270  // its descendants. Finally, an arc (X,Y) can be added if and
271  // only if Y does not belong to the ancestor set of X
272  for (auto iter = additions.cbegin(); iter != additions.cend(); ++iter) {
273  const Arc& arc = iter.key();
274  NodeId tail = arc.tail();
275  NodeId head = arc.head();
276 
277  const NodeProperty< Size >& anc_tail = ancestors[tail];
278 
279  if (anc_tail.exists(head)) { return true; }
280 
282 
283  // update the set of ancestors
285  set_to_add.insert(tail, 1);
287 
288  for (auto iter = desc_head.cbegin(); iter != desc_head.cend(); ++iter) {
290  set_to_add,
291  ancestors[iter.key()][head]);
292  }
293 
294  // update the set of descendants
296  set_to_add.insert(head, 1);
298 
299  for (auto iter = anc_tail.cbegin(); iter != anc_tail.cend(); ++iter) {
301  set_to_add,
302  descendants[iter.key()][tail]);
303  }
304  }
305 
306  return false;
307  }
308 
309  /// adds a new arc to the current DAG
311  // check that the arc does not already exist
312  if (dag__.existsArc(tail, head)) return;
313 
314  // check that the arc would not create a cycle
317  "the arc would create a directed into a DAG");
318  }
319 
320  dag__.addArc(tail, head);
321 
322  // now we apply the addition of the arc as done in method
323  // hasCycleFromModifications
326 
327  // update the set of ancestors
329  set_to_add.insert(tail, 1);
331 
332  for (auto iter = desc_head.cbegin(); iter != desc_head.cend(); ++iter) {
334  set_to_add,
335  ancestors__[iter.key()][head]);
336  }
337 
338  // update the set of descendants
340  set_to_add.insert(head, 1);
342 
343  for (auto iter = anc_tail.cbegin(); iter != anc_tail.cend(); ++iter) {
345  set_to_add,
346  descendants__[iter.key()][tail]);
347  }
348  }
349 
350  /// removes an arc from the current DAG
352  // check that the arc exists
353  if (!dag__.existsArc(tail, head)) return;
354 
356 
357  // we apply the deletion of the arc as done in method
358  // hasCycleFromModifications
361 
362  // update the set of descendants
364  set_to_del.insert(head, 1);
366 
367  for (auto iter = anc_tail.cbegin(); iter != anc_tail.cend(); ++iter) {
369  set_to_del,
370  descendants__[iter.key()][tail]);
371  }
372 
373  // update the set of ancestors
375  set_to_del.insert(tail, 1);
377 
378  for (auto iter = desc_head.cbegin(); iter != desc_head.cend(); ++iter) {
380  set_to_del,
381  ancestors__[iter.key()][head]);
382  }
383  }
384 
385 } /* namespace gum */
INLINE void emplace(Args &&... args)
Definition: set_tpl.h:669