aGrUM  0.16.0
DAGCycleDetector.cpp
Go to the documentation of this file.
1 
36 #include <agrum/core/sequence.h>
38 
39 #ifdef GUM_NO_INLINE
41 #endif // GUM_NOINLINE
42 
43 namespace gum {
44 
46  void DAGCycleDetector::setDAG(const DAG& dag) {
47  // sets the dag
48  __dag = dag;
49 
50  // get the set of roots and leaves of the dag
51  List< NodeId > roots, leaves;
52  NodeProperty< Size > nb_parents, nb_children;
53 
54  for (const auto node : dag) {
55  Size nb_ch = dag.children(node).size();
56  nb_children.insert(node, nb_ch);
57 
58  if (!nb_ch) leaves.insert(node);
59 
60  Size nb_pa = dag.parents(node).size();
61  nb_parents.insert(node, nb_pa);
62 
63  if (!nb_pa) roots.insert(node);
64  }
65 
66  // recompute the set of ancestors
67  NodeProperty< Size > empty_set;
68  __ancestors.clear();
69 
70  for (NodeId node : dag) {
71  __ancestors.insert(node, empty_set);
72  }
73 
74  while (!roots.empty()) {
75  // get a node and update the ancestors of its children
76  NodeId node = roots.front();
77  NodeProperty< Size > node_ancestors = __ancestors[node];
78  node_ancestors.insert(node, 1);
79  const NodeSet& node_children = dag.children(node);
80  roots.popFront();
81 
82  for (const auto ch : node_children) {
83  __addWeightedSet(__ancestors[ch], node_ancestors, 1);
84  --nb_parents[ch];
85 
86  if (!nb_parents[ch]) { roots.insert(ch); }
87  }
88  }
89 
90  // recompute the set of descendants
91  __descendants.clear();
92 
93  for (const auto node : dag) {
94  __descendants.insert(node, empty_set);
95  }
96 
97  while (!leaves.empty()) {
98  // get a node and update the descendants of its parents
99  NodeId node = leaves.front();
100  NodeProperty< Size > node_descendants = __descendants[node];
101  node_descendants.insert(node, 1);
102  const NodeSet& node_parents = dag.parents(node);
103  leaves.popFront();
104 
105  for (const auto pa : node_parents) {
106  __addWeightedSet(__descendants[pa], node_descendants, 1);
107  --nb_children[pa];
108 
109  if (!nb_children[pa]) { leaves.insert(pa); }
110  }
111  }
112  }
113 
116  const std::vector< Change >& modifs) const {
117  // first, we separate deletions and insertions (reversals are cut into
118  // a deletion + an insertion) and we check that no insertion also exists
119  // as a deletion (if so, we remove both operations). In addition, if
120  // we try to add an arc (X,X) we return that it induces a cycle
121  HashTable< Arc, Size > deletions(Size(modifs.size()));
122  HashTable< Arc, Size > additions(Size(modifs.size()));
123 
124  for (const auto& modif : modifs) {
125  Arc arc(modif.tail(), modif.head());
126 
127  switch (modif.type()) {
129  if (deletions.exists(arc))
130  ++deletions[arc];
131  else
132  deletions.insert(arc, 1);
133 
134  break;
135 
137  if (modif.tail() == modif.head()) return true;
138 
139  if (additions.exists(arc))
140  ++additions[arc];
141  else
142  additions.insert(arc, 1);
143 
144  break;
145 
147  if (deletions.exists(arc))
148  ++deletions[arc];
149  else
150  deletions.insert(arc, 1);
151 
152  arc = Arc(modif.head(), modif.tail());
153 
154  if (additions.exists(arc))
155  ++additions[arc];
156  else
157  additions.insert(arc, 1);
158 
159  break;
160 
161  default: 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) {
173  additions.erase(iter);
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());
180  additions.erase(iter);
181  }
182  }
183  }
184 
185  // get the set of nodes involved in the modifications
186  NodeSet extremities;
187 
188  for (const auto& modif : modifs) {
189  extremities.insert(modif.tail());
190  extremities.insert(modif.head());
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
196  NodeProperty< NodeProperty< Size > > ancestors, descendants;
197 
198  for (const auto& modif : modifs) {
199  if (!ancestors.exists(modif.tail())) {
200  NodeProperty< Size >& anc =
201  ancestors.insert(modif.tail(), NodeProperty< Size >()).second;
202  __restrictWeightedSet(anc, __ancestors[modif.tail()], extremities);
203 
204  NodeProperty< Size >& desc =
205  descendants.insert(modif.tail(), NodeProperty< Size >()).second;
206  __restrictWeightedSet(desc, __descendants[modif.tail()], extremities);
207  }
208 
209  if (!ancestors.exists(modif.head())) {
210  NodeProperty< Size >& anc =
211  ancestors.insert(modif.head(), NodeProperty< Size >()).second;
212  __restrictWeightedSet(anc, __ancestors[modif.head()], extremities);
213 
214  NodeProperty< Size >& desc =
215  descendants.insert(modif.head(), NodeProperty< Size >()).second;
216  __restrictWeightedSet(desc, __descendants[modif.head()], extremities);
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();
236  const NodeProperty< Size >& desc_head = descendants[head];
237 
238  // update the set of descendants
239  NodeProperty< Size > set_to_del = desc_head;
240  set_to_del.insert(head, 1);
241  __delWeightedSet(descendants[tail], set_to_del, 1);
242 
243  for (auto iter = anc_tail.cbegin(); iter != anc_tail.cend(); ++iter) {
245  descendants[iter.key()], set_to_del, descendants[iter.key()][tail]);
246  }
247 
248  // update the set of ancestors
249  set_to_del = anc_tail;
250  set_to_del.insert(tail, 1);
251  __delWeightedSet(ancestors[head], set_to_del, 1);
252 
253  for (auto iter = desc_head.cbegin(); iter != desc_head.cend(); ++iter) {
255  ancestors[iter.key()], set_to_del, ancestors[iter.key()][head]);
256  }
257  }
258 
259  // now we apply all the additions of arcs (including the additions after
260  // arc reversals). After adding arc (X,Y), the set of ancestors T of Y and
261  // its
262  // descendants shall be updated by adding the number of paths leading to
263  // X and its ancestors, i.e., ancestors[X] * the number of paths leading
264  // to Y, i.e., ancestors[T][Y]. Similarly, the set of descendants of X and
265  // its
266  // ancestors should be updated by adding the number of paths leading to Y
267  // and
268  // its descendants. Finally, an arc (X,Y) can be added if and
269  // only if Y does not belong to the ancestor set of X
270  for (auto iter = additions.cbegin(); iter != additions.cend(); ++iter) {
271  const Arc& arc = iter.key();
272  NodeId tail = arc.tail();
273  NodeId head = arc.head();
274 
275  const NodeProperty< Size >& anc_tail = ancestors[tail];
276 
277  if (anc_tail.exists(head)) { return true; }
278 
279  const NodeProperty< Size >& desc_head = descendants[head];
280 
281  // update the set of ancestors
282  NodeProperty< Size > set_to_add = anc_tail;
283  set_to_add.insert(tail, 1);
284  __addWeightedSet(ancestors[head], set_to_add, 1);
285 
286  for (auto iter = desc_head.cbegin(); iter != desc_head.cend(); ++iter) {
288  ancestors[iter.key()], set_to_add, ancestors[iter.key()][head]);
289  }
290 
291  // update the set of descendants
292  set_to_add = desc_head;
293  set_to_add.insert(head, 1);
294  __addWeightedSet(descendants[tail], set_to_add, 1);
295 
296  for (auto iter = anc_tail.cbegin(); iter != anc_tail.cend(); ++iter) {
298  descendants[iter.key()], set_to_add, descendants[iter.key()][tail]);
299  }
300  }
301 
302  return false;
303  }
304 
307  // check that the arc does not already exist
308  if (__dag.existsArc(tail, head)) return;
309 
310  // check that the arc would not create a cycle
311  if (hasCycleFromAddition(tail, head)) {
313  "the arc would create a directed into a DAG");
314  }
315 
316  __dag.addArc(tail, head);
317 
318  // now we apply the addition of the arc as done in method
319  // hasCycleFromModifications
320  const NodeProperty< Size >& anc_tail = __ancestors[tail];
321  const NodeProperty< Size >& desc_head = __descendants[head];
322 
323  // update the set of ancestors
324  NodeProperty< Size > set_to_add = anc_tail;
325  set_to_add.insert(tail, 1);
326  __addWeightedSet(__ancestors[head], set_to_add, 1);
327 
328  for (auto iter = desc_head.cbegin(); iter != desc_head.cend(); ++iter) {
330  __ancestors[iter.key()], set_to_add, __ancestors[iter.key()][head]);
331  }
332 
333  // update the set of descendants
334  set_to_add = desc_head;
335  set_to_add.insert(head, 1);
336  __addWeightedSet(__descendants[tail], set_to_add, 1);
337 
338  for (auto iter = anc_tail.cbegin(); iter != anc_tail.cend(); ++iter) {
340  __descendants[iter.key()], set_to_add, __descendants[iter.key()][tail]);
341  }
342  }
343 
346  // check that the arc exists
347  if (!__dag.existsArc(tail, head)) return;
348 
349  __dag.eraseArc(Arc(tail, head));
350 
351  // we apply the deletion of the arc as done in method
352  // hasCycleFromModifications
353  const NodeProperty< Size >& anc_tail = __ancestors[tail];
354  const NodeProperty< Size >& desc_head = __descendants[head];
355 
356  // update the set of descendants
357  NodeProperty< Size > set_to_del = desc_head;
358  set_to_del.insert(head, 1);
359  __delWeightedSet(__descendants[tail], set_to_del, 1);
360 
361  for (auto iter = anc_tail.cbegin(); iter != anc_tail.cend(); ++iter) {
363  __descendants[iter.key()], set_to_del, __descendants[iter.key()][tail]);
364  }
365 
366  // update the set of ancestors
367  set_to_del = anc_tail;
368  set_to_del.insert(tail, 1);
369  __delWeightedSet(__ancestors[head], set_to_del, 1);
370 
371  for (auto iter = desc_head.cbegin(); iter != desc_head.cend(); ++iter) {
373  __ancestors[iter.key()], set_to_del, __ancestors[iter.key()][head]);
374  }
375  }
376 
377 } /* namespace gum */
bool empty() const noexcept
Returns a boolean indicating whether the chained list is empty.
Definition: list_tpl.h:1970
DiGraph __dag
the initial dag from which modifications are applied
virtual void addArc(const NodeId tail, const NodeId head)
insert a new arc into the directed graph
Definition: diGraph_inl.h:35
Copyright 2005-2019 Pierre-Henri WUILLEMIN et Christophe GONZALES (LIP6) {prenom.nom}_at_lip6.fr.
const iterator_safe & endSafe() noexcept
Returns the safe iterator pointing to the end of the hashtable.
Size size() const noexcept
Returns the number of elements stored into the hashtable.
void erase(const Key &key)
Removes a given element from the hash table.
void eraseArc(NodeId x, NodeId y)
removes an arc from the current DAG
void __addWeightedSet(NodeProperty< Size > &nodeset, const NodeProperty< Size > &set_to_add, Size multiplier) const
adds a weighted nodeset to another (weights are added)
virtual void eraseArc(const Arc &arc)
removes an arc from the ArcGraphPart
bool exists(const Key &key) const
Checks whether there exists an element with a given key in the hashtable.
void addArc(NodeId x, NodeId y)
adds a new arc to the current DAG
void __delWeightedSet(NodeProperty< Size > &nodeset, const NodeProperty< Size > &set_to_del, Size multiplier) const
removes a weighted nodeset from another (weights are subtracted)
void __restrictWeightedSet(NodeProperty< Size > &result_set, const NodeProperty< Size > &set_to_restrict, const NodeSet &extrmities) const
put into a weighted nodeset the nodes of another weighted set that belong to a set of arc extremities...
Generic doubly linked lists.
Definition: list.h:372
Copyright 2005-2019 Pierre-Henri WUILLEMIN et Christophe GONZALES (LIP6) {prenom.nom}_at_lip6.fr.
Definition: agrum.h:25
void popFront()
Removes the first element of a List, if any.
Definition: list_tpl.h:1964
NodeId head() const
returns the head of the arc
The class for generic Hash Tables.
Definition: hashTable.h:679
Copyright 2005-2019 Pierre-Henri WUILLEMIN et Christophe GONZALES (LIP6) {prenom.nom}_at_lip6.fr.
bool hasCycleFromModifications(const std::vector< Change > &modifs) const
indicates whether a set of modifications would create a cycle
void setDAG(const DAG &dag)
sets the initial DAG from which changes shall be applied
The base class for all directed edgesThis class is used as a basis for manipulating all directed edge...
const const_iterator & cend() const noexcept
Returns the unsafe const_iterator pointing to the end of the hashtable.
Copyright 2005-2019 Pierre-Henri WUILLEMIN et Christophe GONZALES (LIP6) {prenom.nom}_at_lip6.fr.
const_iterator cbegin() const
Returns an unsafe const_iterator pointing to the beginning of the hashtable.
Val & insert(const Val &val)
Inserts a new element at the end of the chained list (alias of pushBack).
Definition: list_tpl.h:1619
Val & front() const
Returns a reference to first element of a list, if any.
Definition: list_tpl.h:1831
bool hasCycleFromAddition(NodeId x, NodeId y) const noexcept
indicates whether an arc addition would create a cycle
iterator_safe beginSafe()
Returns the safe iterator pointing to the beginning of the hashtable.
NodeProperty< NodeProperty< Size > > __descendants
the set of descendants of each node in the dag
NodeProperty< NodeProperty< Size > > __ancestors
the set of ancestors of each node in the dag
std::size_t Size
In aGrUM, hashed values are unsigned long int.
Definition: types.h:48
value_type & insert(const Key &key, const Val &val)
Adds a new element (actually a copy of this element) into the hash table.
Base class for dag.
Definition: DAG.h:102
bool existsArc(const Arc &arc) const
indicates whether a given arc exists
Size NodeId
Type for node ids.
Definition: graphElements.h:98
void insert(const Key &k)
Inserts a new element into the set.
Definition: set_tpl.h:613
NodeId tail() const
returns the tail of the arc
#define GUM_ERROR(type, msg)
Definition: exceptions.h:55