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
;
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
(
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
120
HashTable
<
Arc
,
Size
>
deletions
(
Size
(
modifs
.
size
()));
121
HashTable
<
Arc
,
Size
>
additions
(
Size
(
modifs
.
size
()));
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
) {
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
) {
244
delWeightedSet__
(
descendants
[
iter
.
key
()],
245
set_to_del
,
246
descendants
[
iter
.
key
()][
tail
]);
247
}
248
249
// update the set of ancestors
250
set_to_del
=
anc_tail
;
251
set_to_del
.
insert
(
tail
, 1);
252
delWeightedSet__
(
ancestors
[
head
],
set_to_del
, 1);
253
254
for
(
auto
iter
=
desc_head
.
cbegin
();
iter
!=
desc_head
.
cend
(); ++
iter
) {
255
delWeightedSet__
(
ancestors
[
iter
.
key
()],
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
281
const
NodeProperty
<
Size
>&
desc_head
=
descendants
[
head
];
282
283
// update the set of ancestors
284
NodeProperty
<
Size
>
set_to_add
=
anc_tail
;
285
set_to_add
.
insert
(
tail
, 1);
286
addWeightedSet__
(
ancestors
[
head
],
set_to_add
, 1);
287
288
for
(
auto
iter
=
desc_head
.
cbegin
();
iter
!=
desc_head
.
cend
(); ++
iter
) {
289
addWeightedSet__
(
ancestors
[
iter
.
key
()],
290
set_to_add
,
291
ancestors
[
iter
.
key
()][
head
]);
292
}
293
294
// update the set of descendants
295
set_to_add
=
desc_head
;
296
set_to_add
.
insert
(
head
, 1);
297
addWeightedSet__
(
descendants
[
tail
],
set_to_add
, 1);
298
299
for
(
auto
iter
=
anc_tail
.
cbegin
();
iter
!=
anc_tail
.
cend
(); ++
iter
) {
300
addWeightedSet__
(
descendants
[
iter
.
key
()],
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
310
void
DAGCycleDetector
::
addArc
(
NodeId
tail
,
NodeId
head
) {
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
315
if
(
hasCycleFromAddition
(
tail
,
head
)) {
316
GUM_ERROR
(
InvalidDirectedCycle
,
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
324
const
NodeProperty
<
Size
>&
anc_tail
=
ancestors__
[
tail
];
325
const
NodeProperty
<
Size
>&
desc_head
=
descendants__
[
head
];
326
327
// update the set of ancestors
328
NodeProperty
<
Size
>
set_to_add
=
anc_tail
;
329
set_to_add
.
insert
(
tail
, 1);
330
addWeightedSet__
(
ancestors__
[
head
],
set_to_add
, 1);
331
332
for
(
auto
iter
=
desc_head
.
cbegin
();
iter
!=
desc_head
.
cend
(); ++
iter
) {
333
addWeightedSet__
(
ancestors__
[
iter
.
key
()],
334
set_to_add
,
335
ancestors__
[
iter
.
key
()][
head
]);
336
}
337
338
// update the set of descendants
339
set_to_add
=
desc_head
;
340
set_to_add
.
insert
(
head
, 1);
341
addWeightedSet__
(
descendants__
[
tail
],
set_to_add
, 1);
342
343
for
(
auto
iter
=
anc_tail
.
cbegin
();
iter
!=
anc_tail
.
cend
(); ++
iter
) {
344
addWeightedSet__
(
descendants__
[
iter
.
key
()],
345
set_to_add
,
346
descendants__
[
iter
.
key
()][
tail
]);
347
}
348
}
349
350
/// removes an arc from the current DAG
351
void
DAGCycleDetector
::
eraseArc
(
NodeId
tail
,
NodeId
head
) {
352
// check that the arc exists
353
if
(!
dag__
.
existsArc
(
tail
,
head
))
return
;
354
355
dag__
.
eraseArc
(
Arc
(
tail
,
head
));
356
357
// we apply the deletion of the arc as done in method
358
// hasCycleFromModifications
359
const
NodeProperty
<
Size
>&
anc_tail
=
ancestors__
[
tail
];
360
const
NodeProperty
<
Size
>&
desc_head
=
descendants__
[
head
];
361
362
// update the set of descendants
363
NodeProperty
<
Size
>
set_to_del
=
desc_head
;
364
set_to_del
.
insert
(
head
, 1);
365
delWeightedSet__
(
descendants__
[
tail
],
set_to_del
, 1);
366
367
for
(
auto
iter
=
anc_tail
.
cbegin
();
iter
!=
anc_tail
.
cend
(); ++
iter
) {
368
delWeightedSet__
(
descendants__
[
iter
.
key
()],
369
set_to_del
,
370
descendants__
[
iter
.
key
()][
tail
]);
371
}
372
373
// update the set of ancestors
374
set_to_del
=
anc_tail
;
375
set_to_del
.
insert
(
tail
, 1);
376
delWeightedSet__
(
ancestors__
[
head
],
set_to_del
, 1);
377
378
for
(
auto
iter
=
desc_head
.
cbegin
();
iter
!=
desc_head
.
cend
(); ++
iter
) {
379
delWeightedSet__
(
ancestors__
[
iter
.
key
()],
380
set_to_del
,
381
ancestors__
[
iter
.
key
()][
head
]);
382
}
383
}
384
385
}
/* namespace gum */
gum::Set::emplace
INLINE void emplace(Args &&... args)
Definition:
set_tpl.h:669