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
;
51
NodeProperty
<
Size
>
nb_parents
,
nb_children
;
52
53
for
(
const
auto
node
:
dag
) {
54
Size
nb_ch
=
dag
.
children
(
node
).
size
();
55
nb_children
.
insert
(
node
,
nb_ch
);
56
57
if
(!
nb_ch
)
leaves
.
insert
(
node
);
58
59
Size
nb_pa
=
dag
.
parents
(
node
).
size
();
60
nb_parents
.
insert
(
node
,
nb_pa
);
61
62
if
(!
nb_pa
)
roots
.
insert
(
node
);
63
}
64
65
// recompute the set of ancestors
66
NodeProperty
<
Size
>
empty_set
;
67
_ancestors_
.
clear
();
68
69
for
(
NodeId
node
:
dag
) {
70
_ancestors_
.
insert
(
node
,
empty_set
);
71
}
72
73
while
(!
roots
.
empty
()) {
74
// get a node and update the ancestors of its children
75
NodeId
node
=
roots
.
front
();
76
NodeProperty
<
Size
>
node_ancestors
=
_ancestors_
[
node
];
77
node_ancestors
.
insert
(
node
, 1);
78
const
NodeSet
&
node_children
=
dag
.
children
(
node
);
79
roots
.
popFront
();
80
81
for
(
const
auto
ch
:
node_children
) {
82
_addWeightedSet_
(
_ancestors_
[
ch
],
node_ancestors
, 1);
83
--
nb_parents
[
ch
];
84
85
if
(!
nb_parents
[
ch
]) {
roots
.
insert
(
ch
); }
86
}
87
}
88
89
// recompute the set of descendants
90
_descendants_
.
clear
();
91
92
for
(
const
auto
node
:
dag
) {
93
_descendants_
.
insert
(
node
,
empty_set
);
94
}
95
96
while
(!
leaves
.
empty
()) {
97
// get a node and update the descendants of its parents
98
NodeId
node
=
leaves
.
front
();
99
NodeProperty
<
Size
>
node_descendants
=
_descendants_
[
node
];
100
node_descendants
.
insert
(
node
, 1);
101
const
NodeSet
&
node_parents
=
dag
.
parents
(
node
);
102
leaves
.
popFront
();
103
104
for
(
const
auto
pa
:
node_parents
) {
105
_addWeightedSet_
(
_descendants_
[
pa
],
node_descendants
, 1);
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
114
bool
DAGCycleDetector
::
hasCycleFromModifications
(
const
std
::
vector
<
Change
>&
modifs
)
const
{
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
119
HashTable
<
Arc
,
Size
>
deletions
(
Size
(
modifs
.
size
()));
120
HashTable
<
Arc
,
Size
>
additions
(
Size
(
modifs
.
size
()));
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
) {
172
additions
.
erase
(
iter
);
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
());
179
additions
.
erase
(
iter
);
180
}
181
}
182
}
183
184
// get the set of nodes involved in the modifications
185
NodeSet
extremities
;
186
187
for
(
const
auto
&
modif
:
modifs
) {
188
extremities
.
insert
(
modif
.
tail
());
189
extremities
.
insert
(
modif
.
head
());
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
195
NodeProperty
<
NodeProperty
<
Size
> >
ancestors
,
descendants
;
196
197
for
(
const
auto
&
modif
:
modifs
) {
198
if
(!
ancestors
.
exists
(
modif
.
tail
())) {
199
NodeProperty
<
Size
>&
anc
=
ancestors
.
insert
(
modif
.
tail
(),
NodeProperty
<
Size
>()).
second
;
200
_restrictWeightedSet_
(
anc
,
_ancestors_
[
modif
.
tail
()],
extremities
);
201
202
NodeProperty
<
Size
>&
desc
203
=
descendants
.
insert
(
modif
.
tail
(),
NodeProperty
<
Size
>()).
second
;
204
_restrictWeightedSet_
(
desc
,
_descendants_
[
modif
.
tail
()],
extremities
);
205
}
206
207
if
(!
ancestors
.
exists
(
modif
.
head
())) {
208
NodeProperty
<
Size
>&
anc
=
ancestors
.
insert
(
modif
.
head
(),
NodeProperty
<
Size
>()).
second
;
209
_restrictWeightedSet_
(
anc
,
_ancestors_
[
modif
.
head
()],
extremities
);
210
211
NodeProperty
<
Size
>&
desc
212
=
descendants
.
insert
(
modif
.
head
(),
NodeProperty
<
Size
>()).
second
;
213
_restrictWeightedSet_
(
desc
,
_descendants_
[
modif
.
head
()],
extremities
);
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
();
233
const
NodeProperty
<
Size
>&
desc_head
=
descendants
[
head
];
234
235
// update the set of descendants
236
NodeProperty
<
Size
>
set_to_del
=
desc_head
;
237
set_to_del
.
insert
(
head
, 1);
238
_delWeightedSet_
(
descendants
[
tail
],
set_to_del
, 1);
239
240
for
(
auto
iter
=
anc_tail
.
cbegin
();
iter
!=
anc_tail
.
cend
(); ++
iter
) {
241
_delWeightedSet_
(
descendants
[
iter
.
key
()],
set_to_del
,
descendants
[
iter
.
key
()][
tail
]);
242
}
243
244
// update the set of ancestors
245
set_to_del
=
anc_tail
;
246
set_to_del
.
insert
(
tail
, 1);
247
_delWeightedSet_
(
ancestors
[
head
],
set_to_del
, 1);
248
249
for
(
auto
iter
=
desc_head
.
cbegin
();
iter
!=
desc_head
.
cend
(); ++
iter
) {
250
_delWeightedSet_
(
ancestors
[
iter
.
key
()],
set_to_del
,
ancestors
[
iter
.
key
()][
head
]);
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
274
const
NodeProperty
<
Size
>&
desc_head
=
descendants
[
head
];
275
276
// update the set of ancestors
277
NodeProperty
<
Size
>
set_to_add
=
anc_tail
;
278
set_to_add
.
insert
(
tail
, 1);
279
_addWeightedSet_
(
ancestors
[
head
],
set_to_add
, 1);
280
281
for
(
auto
iter
=
desc_head
.
cbegin
();
iter
!=
desc_head
.
cend
(); ++
iter
) {
282
_addWeightedSet_
(
ancestors
[
iter
.
key
()],
set_to_add
,
ancestors
[
iter
.
key
()][
head
]);
283
}
284
285
// update the set of descendants
286
set_to_add
=
desc_head
;
287
set_to_add
.
insert
(
head
, 1);
288
_addWeightedSet_
(
descendants
[
tail
],
set_to_add
, 1);
289
290
for
(
auto
iter
=
anc_tail
.
cbegin
();
iter
!=
anc_tail
.
cend
(); ++
iter
) {
291
_addWeightedSet_
(
descendants
[
iter
.
key
()],
set_to_add
,
descendants
[
iter
.
key
()][
tail
]);
292
}
293
}
294
295
return
false
;
296
}
297
298
/// adds a new arc to the current DAG
299
void
DAGCycleDetector
::
addArc
(
NodeId
tail
,
NodeId
head
) {
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
304
if
(
hasCycleFromAddition
(
tail
,
head
)) {
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
312
const
NodeProperty
<
Size
>&
anc_tail
=
_ancestors_
[
tail
];
313
const
NodeProperty
<
Size
>&
desc_head
=
_descendants_
[
head
];
314
315
// update the set of ancestors
316
NodeProperty
<
Size
>
set_to_add
=
anc_tail
;
317
set_to_add
.
insert
(
tail
, 1);
318
_addWeightedSet_
(
_ancestors_
[
head
],
set_to_add
, 1);
319
320
for
(
auto
iter
=
desc_head
.
cbegin
();
iter
!=
desc_head
.
cend
(); ++
iter
) {
321
_addWeightedSet_
(
_ancestors_
[
iter
.
key
()],
set_to_add
,
_ancestors_
[
iter
.
key
()][
head
]);
322
}
323
324
// update the set of descendants
325
set_to_add
=
desc_head
;
326
set_to_add
.
insert
(
head
, 1);
327
_addWeightedSet_
(
_descendants_
[
tail
],
set_to_add
, 1);
328
329
for
(
auto
iter
=
anc_tail
.
cbegin
();
iter
!=
anc_tail
.
cend
(); ++
iter
) {
330
_addWeightedSet_
(
_descendants_
[
iter
.
key
()],
set_to_add
,
_descendants_
[
iter
.
key
()][
tail
]);
331
}
332
}
333
334
/// removes an arc from the current DAG
335
void
DAGCycleDetector
::
eraseArc
(
NodeId
tail
,
NodeId
head
) {
336
// check that the arc exists
337
if
(!
_dag_
.
existsArc
(
tail
,
head
))
return
;
338
339
_dag_
.
eraseArc
(
Arc
(
tail
,
head
));
340
341
// we apply the deletion of the arc as done in method
342
// hasCycleFromModifications
343
const
NodeProperty
<
Size
>&
anc_tail
=
_ancestors_
[
tail
];
344
const
NodeProperty
<
Size
>&
desc_head
=
_descendants_
[
head
];
345
346
// update the set of descendants
347
NodeProperty
<
Size
>
set_to_del
=
desc_head
;
348
set_to_del
.
insert
(
head
, 1);
349
_delWeightedSet_
(
_descendants_
[
tail
],
set_to_del
, 1);
350
351
for
(
auto
iter
=
anc_tail
.
cbegin
();
iter
!=
anc_tail
.
cend
(); ++
iter
) {
352
_delWeightedSet_
(
_descendants_
[
iter
.
key
()],
set_to_del
,
_descendants_
[
iter
.
key
()][
tail
]);
353
}
354
355
// update the set of ancestors
356
set_to_del
=
anc_tail
;
357
set_to_del
.
insert
(
tail
, 1);
358
_delWeightedSet_
(
_ancestors_
[
head
],
set_to_del
, 1);
359
360
for
(
auto
iter
=
desc_head
.
cbegin
();
iter
!=
desc_head
.
cend
(); ++
iter
) {
361
_delWeightedSet_
(
_ancestors_
[
iter
.
key
()],
set_to_del
,
_ancestors_
[
iter
.
key
()][
head
]);
362
}
363
}
364
365
}
/* namespace gum */
gum::Set::emplace
INLINE void emplace(Args &&... args)
Definition:
set_tpl.h:643