aGrUM
0.20.2
a C++ library for (probabilistic) graphical models
staticTriangulation.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 base class for graph non-incremental triangulations.
24
*
25
* @author Christophe GONZALES(@AMU) and Pierre-Henri WUILLEMIN(@LIP6)
26
*/
27
28
#
include
<
agrum
/
tools
/
core
/
priorityQueue
.
h
>
29
#
include
<
agrum
/
tools
/
graphs
/
algorithms
/
triangulations
/
eliminationStrategies
/
defaultEliminationSequenceStrategy
.
h
>
30
#
include
<
agrum
/
tools
/
graphs
/
algorithms
/
triangulations
/
junctionTreeStrategies
/
defaultJunctionTreeStrategy
.
h
>
31
#
include
<
agrum
/
tools
/
graphs
/
algorithms
/
triangulations
/
staticTriangulation
.
h
>
32
33
#
ifdef
GUM_NO_INLINE
34
#
include
<
agrum
/
tools
/
graphs
/
algorithms
/
triangulations
/
staticTriangulation_inl
.
h
>
35
#
endif
// GUM_NO_INLINE
36
37
namespace
gum
{
38
39
// default constructor
40
StaticTriangulation
::
StaticTriangulation
(
41
const
UndiGraph
*
theGraph
,
42
const
NodeProperty
<
Size
>*
domsizes
,
43
const
EliminationSequenceStrategy
&
elimSeq
,
44
const
JunctionTreeStrategy
&
JTStrategy
,
45
bool
minimality
) :
46
Triangulation
(
domsizes
),
47
elimination_sequence_strategy_
(
elimSeq
.
newFactory
()),
48
junction_tree_strategy_
(
JTStrategy
.
newFactory
()),
original_graph__
(
theGraph
),
49
minimality_required__
(
minimality
) {
50
// for debugging purposes
51
GUM_CONSTRUCTOR
(
StaticTriangulation
);
52
53
// if the graph is not empty, resize several structures in order to speed-up
54
// their fillings.
55
if
(
theGraph
!=
nullptr
) {
56
elim_order__
.
resize
(
theGraph
->
size
());
57
reverse_elim_order__
.
resize
(
theGraph
->
size
());
58
elim_cliques__
.
resize
(
theGraph
->
size
());
59
node_2_max_prime_clique__
.
resize
(
theGraph
->
size
());
60
added_fill_ins__
.
resize
(
theGraph
->
size
());
61
}
62
63
// register the triangulation to its junction tree strategy
64
junction_tree_strategy_
->
setTriangulation
(
this
);
65
}
66
67
// default constructor
68
StaticTriangulation
::
StaticTriangulation
(
69
const
EliminationSequenceStrategy
&
elimSeq
,
70
const
JunctionTreeStrategy
&
JTStrategy
,
71
bool
minimality
) :
72
elimination_sequence_strategy_
(
elimSeq
.
newFactory
()),
73
junction_tree_strategy_
(
JTStrategy
.
newFactory
()),
74
minimality_required__
(
minimality
) {
75
// for debugging purposes
76
GUM_CONSTRUCTOR
(
StaticTriangulation
);
77
78
// register the triangulation to its junction tree strategy
79
junction_tree_strategy_
->
setTriangulation
(
this
);
80
}
81
82
// copy constructor
83
StaticTriangulation
::
StaticTriangulation
(
const
StaticTriangulation
&
from
) :
84
Triangulation
(
from
),
original_graph__
(
from
.
original_graph__
),
85
triangulated_graph__
(
from
.
triangulated_graph__
),
fill_ins__
(
from
.
fill_ins__
),
86
elim_order__
(
from
.
elim_order__
),
87
reverse_elim_order__
(
from
.
reverse_elim_order__
),
88
elim_cliques__
(
from
.
elim_cliques__
),
elim_tree__
(
from
.
elim_tree__
),
89
max_prime_junction_tree__
(
from
.
max_prime_junction_tree__
),
90
node_2_max_prime_clique__
(
from
.
node_2_max_prime_clique__
),
91
has_triangulation__
(
from
.
has_triangulation__
),
92
has_triangulated_graph__
(
from
.
has_triangulated_graph__
),
93
has_elimination_tree__
(
from
.
has_elimination_tree__
),
94
has_junction_tree__
(
from
.
has_junction_tree__
),
95
has_max_prime_junction_tree__
(
from
.
has_max_prime_junction_tree__
),
96
has_fill_ins__
(
from
.
has_fill_ins__
),
97
minimality_required__
(
from
.
minimality_required__
),
98
added_fill_ins__
(
from
.
added_fill_ins__
),
99
we_want_fill_ins__
(
from
.
we_want_fill_ins__
) {
100
// copy the strategies
101
elimination_sequence_strategy_
102
=
from
.
elimination_sequence_strategy_
->
copyFactory
();
103
junction_tree_strategy_
=
from
.
junction_tree_strategy_
->
copyFactory
(
this
);
104
105
// if from has a junction tree, copy it
106
if
(
from
.
junction_tree__
!=
nullptr
) {
107
junction_tree__
= &(
junction_tree_strategy_
->
junctionTree
());
108
}
109
110
// for debugging purposes
111
GUM_CONS_CPY
(
StaticTriangulation
);
112
}
113
114
// move constructor
115
StaticTriangulation
::
StaticTriangulation
(
StaticTriangulation
&&
from
) :
116
Triangulation
(
std
::
move
(
from
)),
117
elimination_sequence_strategy_
(
from
.
elimination_sequence_strategy_
),
118
junction_tree_strategy_
(
from
.
junction_tree_strategy_
),
119
original_graph__
(
from
.
original_graph__
),
120
triangulated_graph__
(
std
::
move
(
from
.
triangulated_graph__
)),
121
fill_ins__
(
std
::
move
(
from
.
fill_ins__
)),
122
elim_order__
(
std
::
move
(
from
.
elim_order__
)),
123
reverse_elim_order__
(
std
::
move
(
from
.
reverse_elim_order__
)),
124
elim_cliques__
(
std
::
move
(
from
.
elim_cliques__
)),
125
elim_tree__
(
std
::
move
(
from
.
elim_tree__
)),
126
max_prime_junction_tree__
(
std
::
move
(
from
.
max_prime_junction_tree__
)),
127
node_2_max_prime_clique__
(
std
::
move
(
from
.
node_2_max_prime_clique__
)),
128
has_triangulation__
(
from
.
has_triangulation__
),
129
has_triangulated_graph__
(
from
.
has_triangulated_graph__
),
130
has_elimination_tree__
(
from
.
has_elimination_tree__
),
131
has_junction_tree__
(
from
.
has_junction_tree__
),
132
has_max_prime_junction_tree__
(
from
.
has_max_prime_junction_tree__
),
133
has_fill_ins__
(
from
.
has_fill_ins__
),
134
minimality_required__
(
from
.
minimality_required__
),
135
added_fill_ins__
(
std
::
move
(
from
.
added_fill_ins__
)),
136
we_want_fill_ins__
(
from
.
we_want_fill_ins__
) {
137
// move the factories contained in from
138
from
.
elimination_sequence_strategy_
=
new
DefaultEliminationSequenceStrategy
;
139
from
.
junction_tree_strategy_
=
new
DefaultJunctionTreeStrategy
;
140
junction_tree_strategy_
->
moveTriangulation
(
this
);
141
142
// if from has a junction tree, copy it
143
if
(
from
.
junction_tree__
!=
nullptr
) {
144
junction_tree__
= &(
junction_tree_strategy_
->
junctionTree
());
145
}
146
147
// for debugging purposes
148
GUM_CONS_MOV
(
StaticTriangulation
);
149
}
150
151
/// destructor
152
StaticTriangulation
::~
StaticTriangulation
() {
153
// for debugging purposes
154
GUM_DESTRUCTOR
(
StaticTriangulation
);
155
156
delete
elimination_sequence_strategy_
;
157
delete
junction_tree_strategy_
;
158
159
// no need to deallocate original_graph__ nor junction_tree__ because
160
// they are not owned by the static triangulation class
161
}
162
163
/// reinitialize the graph to be triangulated to an empty graph
164
void
StaticTriangulation
::
clear
() {
165
// clear the factories
166
elimination_sequence_strategy_
->
clear
();
167
junction_tree_strategy_
->
clear
();
168
169
// remove the current graphs
170
original_graph__
=
nullptr
;
171
junction_tree__
=
nullptr
;
172
triangulated_graph__
.
clear
();
173
elim_tree__
.
clear
();
174
max_prime_junction_tree__
.
clear
();
175
elim_cliques__
.
clear
();
176
node_2_max_prime_clique__
.
clear
();
177
178
// remove all fill-ins and orderings
179
fill_ins__
.
clear
();
180
added_fill_ins__
.
clear
();
181
elim_order__
.
clear
();
182
reverse_elim_order__
.
clear
();
183
184
// indicates that the junction tree, the max prime tree, etc, are updated
185
has_triangulation__
=
true
;
186
has_triangulated_graph__
=
true
;
187
has_elimination_tree__
=
true
;
188
has_junction_tree__
=
true
;
189
has_max_prime_junction_tree__
=
true
;
190
has_fill_ins__
=
true
;
191
}
192
193
// removes redondant fill-ins and compute proper elimination cliques and order
194
void
StaticTriangulation
::
computeRecursiveThinning__
() {
195
// apply recursive thinning (fmint, see Kjaerulff (90), "triangulation of
196
// graphs - algorithms giving small total state space")
197
NodeId
node1
,
node2
;
198
bool
incomplete
;
199
std
::
vector
<
NodeId
>
adj
;
200
EdgeSet
T_prime
;
201
NodeProperty
<
unsigned
int
>
R
(
triangulated_graph__
.
size
());
202
203
for
(
const
auto
node
:
triangulated_graph__
)
204
R
.
insert
(
node
, 0);
205
206
// the FMINT loop
207
if
(
added_fill_ins__
.
size
() > 0) {
208
for
(
auto
iter
=
added_fill_ins__
.
rbegin
();
iter
!=
added_fill_ins__
.
rend
();
209
++
iter
) {
210
if
(
iter
->
size
()) {
211
// here apply MINT to T_i = added_fill_ins__[i]
212
EdgeSet
&
T
= *
iter
;
213
214
// compute R: by default, R is equal to all the nodes in the graph
215
// when R[...] >= j (see the j in the loop below), this means that the
216
// node belongs to an extremal edge in R
217
for
(
std
::
size_t
k
= 0;
k
<
elim_order__
.
size
(); ++
k
)
218
R
[
elim_order__
[
k
]]
219
= 0;
// WARNING: do not replace R[elim_order__[k]] by
220
221
// R[k] because the node ids may not be
222
// consecutive numbers
223
224
// apply MINT while some edges can possibly be deleted
225
bool
require_mint
=
true
;
226
227
for
(
unsigned
int
j
= 0;
require_mint
; ++
j
) {
228
// find T' (it is equal to the edges (v,w) of T such that
229
// the intersection of adj(v,G) and adj(w,G) is complete and such
230
// that
231
// v and/or w belongs to an extremal node in R
232
for
(
const
auto
&
edge
:
T
) {
233
node1
=
edge
.
first
();
234
node2
=
edge
.
second
();
235
236
// check if at least one extremal node belongs to R
237
if
((
R
[
node1
] <
j
) && (
R
[
node2
] <
j
))
continue
;
238
239
// check if the intersection of adj(v,G) and adj(w,G) is a
240
// complete subgraph
241
if
(
triangulated_graph__
.
neighbours
(
node2
).
size
()
242
<
triangulated_graph__
.
neighbours
(
node1
).
size
()) {
243
NodeId
tmp
=
node1
;
244
node1
=
node2
;
245
node2
=
tmp
;
246
}
247
248
incomplete
=
false
;
249
250
// find the nodes belonging to the intersection of adj(v,G)
251
// and adj(w,G)
252
for
(
const
auto
adjnode
:
triangulated_graph__
.
neighbours
(
node1
))
253
if
(
triangulated_graph__
.
existsEdge
(
node2
,
adjnode
))
254
adj
.
push_back
(
adjnode
);
255
256
// check if the intersection is complete
257
for
(
unsigned
int
k
= 0;
k
<
adj
.
size
() && !
incomplete
; ++
k
) {
258
for
(
unsigned
int
m
=
k
+ 1;
m
<
adj
.
size
(); ++
m
) {
259
if
(!
triangulated_graph__
.
existsEdge
(
adj
[
k
],
adj
[
m
])) {
260
incomplete
=
true
;
261
break
;
262
}
263
}
264
}
265
266
adj
.
clear
();
267
268
if
(!
incomplete
) {
269
T_prime
.
insert
(
edge
);
270
R
[
node1
] =
j
+ 1;
271
R
[
node2
] =
j
+ 1;
272
}
273
}
274
275
// compute the new value of T (i.e. T\T') and update the
276
// triangulated graph
277
for
(
const
auto
&
edge
:
T_prime
) {
278
T
.
erase
(
edge
);
279
triangulated_graph__
.
eraseEdge
(
edge
);
280
281
if
(
has_fill_ins__
)
fill_ins__
.
erase
(
edge
);
282
}
283
284
if
(
T_prime
.
size
() == 0)
require_mint
=
false
;
285
286
T_prime
.
clear
();
287
}
288
}
289
}
290
}
291
292
// here, the recursive thinning has removed all the superfluous edges.
293
// Hence some cliques have been split and, as a result, the elimination
294
// order has changed. We should thus compute a new perfect ordering. To
295
// do so, we use a Maximal Cardinality Search: it has indeed the nice
296
// property that, when the graph is already triangulated, it finds a
297
// compatible order of elimination that does not require any edge addition
298
299
// a structure storing the number of neighbours previously processed
300
PriorityQueue
<
NodeId
,
unsigned
int
,
std
::
greater
<
unsigned
int
> >
301
numbered_neighbours
(
std
::
greater
<
unsigned
int
>(),
302
triangulated_graph__
.
size
());
303
304
for
(
Size
i
= 0;
i
<
elim_order__
.
size
(); ++
i
)
305
numbered_neighbours
.
insert
(
elim_order__
[
i
], 0);
306
307
// perform the maximum cardinality search
308
if
(
elim_order__
.
size
() > 0) {
309
for
(
Size
i
=
Size
(
elim_order__
.
size
());
i
>= 1; --
i
) {
310
NodeId
ni
=
NodeId
(
i
- 1);
311
NodeId
node
=
numbered_neighbours
.
pop
();
312
elim_order__
[
ni
] =
node
;
313
reverse_elim_order__
[
node
] =
ni
;
314
315
for
(
const
auto
neighbour
:
triangulated_graph__
.
neighbours
(
node
)) {
316
try
{
317
numbered_neighbours
.
setPriority
(
318
neighbour
,
319
1 +
numbered_neighbours
.
priority
(
neighbour
));
320
}
catch
(
NotFound
&) {}
321
}
322
}
323
}
324
325
// here the elimination order is ok. We now need to update the
326
// elim_cliques__
327
for
(
Size
i
= 0;
i
<
elim_order__
.
size
(); ++
i
) {
328
NodeSet
&
cliques
=
elim_cliques__
.
insert
(
elim_order__
[
i
],
NodeSet
()).
second
;
329
cliques
<<
elim_order__
[
i
];
330
331
for
(
const
auto
neighbour
:
triangulated_graph__
.
neighbours
(
elim_order__
[
i
]))
332
if
(
reverse_elim_order__
[
neighbour
] >
i
)
cliques
<<
neighbour
;
333
}
334
}
335
336
/// returns an elimination tree from a triangulated graph
337
void
StaticTriangulation
::
computeEliminationTree__
() {
338
// if there already exists an elimination tree, no need to compute it again
339
if
(
has_elimination_tree__
)
return
;
340
341
// if the graph is not triangulated, triangulate it
342
if
(!
has_triangulation__
)
triangulate__
();
343
344
// create the nodes of the elimination tree
345
elim_tree__
.
clear
();
346
347
Size
size
=
Size
(
elim_order__
.
size
());
348
for
(
NodeId
i
=
NodeId
(0);
i
<
size
; ++
i
)
349
elim_tree__
.
addNode
(
i
,
elim_cliques__
[
elim_order__
[
i
]]);
350
351
// create the edges of the elimination tree: join a node to the one in
352
// its clique that is eliminated first
353
for
(
NodeId
i
=
NodeId
(0);
i
<
size
; ++
i
) {
354
NodeId
clique_i_creator
=
elim_order__
[
i
];
355
const
NodeSet
&
list_of_nodes
=
elim_cliques__
[
clique_i_creator
];
356
Idx
child
=
original_graph__
->
bound
() + 1;
357
358
for
(
const
auto
node
:
list_of_nodes
) {
359
Idx
it_elim_step
=
reverse_elim_order__
[
node
];
360
361
if
((
node
!=
clique_i_creator
) && (
child
>
it_elim_step
))
362
child
=
it_elim_step
;
363
}
364
365
if
(
child
<=
original_graph__
->
bound
()) {
366
// WARNING: here, we assume that the nodes of the elimination tree are
367
// indexed from 0 to n-1
368
elim_tree__
.
addEdge
(
i
,
child
);
369
}
370
}
371
372
has_elimination_tree__
=
true
;
373
}
374
375
/// used for computing the junction tree of the maximal prime subgraphs
376
void
StaticTriangulation
::
computeMaxPrimeMergings__
(
377
const
NodeId
node
,
378
const
NodeId
from
,
379
std
::
vector
<
Arc
>&
merged_cliques
,
380
NodeSet
&
mark
)
const
{
381
mark
<<
node
;
382
383
for
(
const
auto
other_node
:
junction_tree__
->
neighbours
(
node
))
384
if
(
other_node
!=
from
) {
385
const
NodeSet
&
separator
=
junction_tree__
->
separator
(
node
,
other_node
);
386
// check that the separator between node and other_node is complete
387
bool
complete
=
true
;
388
389
for
(
auto
iter_sep1
=
separator
.
begin
();
390
iter_sep1
!=
separator
.
end
() &&
complete
;
391
++
iter_sep1
) {
392
auto
iter_sep2
=
iter_sep1
;
393
394
for
(++
iter_sep2
;
iter_sep2
!=
separator
.
end
(); ++
iter_sep2
) {
395
if
(!
original_graph__
->
existsEdge
(*
iter_sep1
, *
iter_sep2
)) {
396
complete
=
false
;
397
break
;
398
}
399
}
400
}
401
402
// here complete indicates whether the separator is complete or not
403
if
(!
complete
)
merged_cliques
.
push_back
(
Arc
(
other_node
,
node
));
404
405
computeMaxPrimeMergings__
(
other_node
,
node
,
merged_cliques
,
mark
);
406
}
407
}
408
409
/// computes the junction tree of the maximal prime subgraphs
410
void
StaticTriangulation
::
computeMaxPrimeJunctionTree__
() {
411
// if there already exists a max prime junction tree, no need
412
// to recompute it
413
if
(
has_max_prime_junction_tree__
)
return
;
414
415
// if there is no junction tree, create it
416
if
(!
has_junction_tree__
)
junctionTree
();
417
418
// the maximal prime subgraph join tree is created by aggregation of some
419
// cliques. More precisely, when the separator between 2 cliques is not
420
// complete in the original graph, then the two cliques must be merged.
421
// Create a hashtable indicating which clique has been absorbed by some
422
// other
423
// clique.
424
NodeProperty
<
NodeId
>
T_mpd_cliques
(
junction_tree__
->
size
());
425
426
for
(
const
auto
clik
:
junction_tree__
->
nodes
())
427
T_mpd_cliques
.
insert
(
clik
,
clik
);
428
429
// parse all the separators of the junction tree and test those that are not
430
// complete in the orginal graph
431
std
::
vector
<
Arc
>
merged_cliques
;
432
433
NodeSet
mark
;
434
435
for
(
const
auto
clik
:
junction_tree__
->
nodes
())
436
if
(!
mark
.
contains
(
clik
))
437
computeMaxPrimeMergings__
(
clik
,
clik
,
merged_cliques
,
mark
);
438
439
// compute the transitive closure of merged_cliques. This one will contain
440
// pairs (X,Y) indicating that clique X must be merged with clique Y.
441
// Actually clique X will be inserted into clique Y.
442
for
(
unsigned
int
i
= 0;
i
<
merged_cliques
.
size
(); ++
i
) {
443
T_mpd_cliques
[
merged_cliques
[
i
].
tail
()]
444
=
T_mpd_cliques
[
merged_cliques
[
i
].
head
()];
445
}
446
447
// now we can create the max prime junction tree. First, create the cliques
448
for
(
const
auto
&
elt
:
T_mpd_cliques
)
449
if
(
elt
.
first
==
elt
.
second
)
450
max_prime_junction_tree__
.
addNode
(
elt
.
second
,
451
junction_tree__
->
clique
(
elt
.
second
));
452
453
// add to the cliques previously created the nodes of the cliques that were
454
// merged into them
455
for
(
const
auto
&
elt
:
T_mpd_cliques
)
456
if
(
elt
.
first
!=
elt
.
second
)
457
for
(
const
auto
node
:
junction_tree__
->
clique
(
elt
.
first
)) {
458
try
{
459
max_prime_junction_tree__
.
addToClique
(
elt
.
second
,
node
);
460
}
catch
(
DuplicateElement
&) {}
461
}
462
463
// add the edges to the graph
464
for
(
const
auto
&
edge
:
junction_tree__
->
edges
()) {
465
NodeId
node1
=
T_mpd_cliques
[
edge
.
first
()];
466
NodeId
node2
=
T_mpd_cliques
[
edge
.
second
()];
467
468
if
(
node1
!=
node2
) {
469
try
{
470
max_prime_junction_tree__
.
addEdge
(
node1
,
node2
);
471
}
catch
(
DuplicateElement
&) {}
472
}
473
}
474
475
// compute for each node which clique of the max prime junction tree was
476
// created by the elimination of the node
477
const
NodeProperty
<
NodeId
>&
node_2_junction_clique
478
=
junction_tree_strategy_
->
createdCliques
();
479
480
for
(
const
auto
&
elt
:
node_2_junction_clique
)
481
node_2_max_prime_clique__
.
insert
(
elt
.
first
,
T_mpd_cliques
[
elt
.
second
]);
482
483
has_max_prime_junction_tree__
=
true
;
484
}
485
486
/// returns the triangulated graph
487
const
UndiGraph
&
StaticTriangulation
::
triangulatedGraph
() {
488
if
(!
has_triangulation__
)
triangulate__
();
489
490
// if we did not construct the triangulated graph during the triangulation,
491
// that is, we just constructed the junction tree, we shall construct it now
492
if
(!
has_triangulated_graph__
) {
493
// just in case, be sure that the junction tree has been constructed
494
if
(!
has_junction_tree__
)
junctionTree
();
495
496
// copy the original graph
497
triangulated_graph__
= *
original_graph__
;
498
499
for
(
const
auto
clik_id
: *
junction_tree__
) {
500
// for each clique, add the edges necessary to make it complete
501
const
NodeSet
&
clique
=
junction_tree__
->
clique
(
clik_id
);
502
std
::
vector
<
NodeId
>
clique_nodes
(
clique
.
size
());
503
unsigned
int
i
= 0;
504
505
for
(
const
auto
node
:
clique
) {
506
clique_nodes
[
i
] =
node
;
507
i
+= 1;
508
}
509
510
for
(
i
= 0;
i
<
clique_nodes
.
size
(); ++
i
) {
511
for
(
unsigned
int
j
=
i
+ 1;
j
<
clique_nodes
.
size
(); ++
j
) {
512
try
{
513
triangulated_graph__
.
addEdge
(
clique_nodes
[
i
],
clique_nodes
[
j
]);
514
}
catch
(
DuplicateElement
&) {}
515
}
516
}
517
}
518
519
has_triangulated_graph__
=
true
;
520
}
521
522
return
triangulated_graph__
;
523
}
524
525
// initialize the triangulation algorithm for a new graph
526
void
StaticTriangulation
::
setGraph
(
const
UndiGraph
*
graph
,
527
const
NodeProperty
<
Size
>*
domsizes
) {
528
// remove the old graph
529
clear
();
530
531
if
(
graph
!=
nullptr
) {
532
// prepare the size of the data for the new graph
533
elim_order__
.
resize
(
graph
->
size
());
534
reverse_elim_order__
.
resize
(
graph
->
size
());
535
elim_cliques__
.
resize
(
graph
->
size
());
536
added_fill_ins__
.
resize
(
graph
->
size
());
537
node_2_max_prime_clique__
.
resize
(
graph
->
size
());
538
}
539
540
// keep track of the variables passed in argument
541
original_graph__
=
graph
;
542
domain_sizes_
=
domsizes
;
543
544
// indicate that no triangulation was performed for this graph
545
has_triangulation__
=
false
;
546
has_triangulated_graph__
=
false
;
547
has_elimination_tree__
=
false
;
548
has_junction_tree__
=
false
;
549
has_max_prime_junction_tree__
=
false
;
550
has_fill_ins__
=
false
;
551
}
552
553
/// the function that performs the triangulation
554
void
StaticTriangulation
::
triangulate__
() {
555
// if the graph is already triangulated, no need to triangulate it again
556
if
(
has_triangulation__
)
return
;
557
558
// copy the graph to be triangulated, so that we can modify it
559
UndiGraph
tmp_graph
= *
original_graph__
;
560
561
initTriangulation_
(
tmp_graph
);
562
elimination_sequence_strategy_
->
askFillIns
(
we_want_fill_ins__
);
563
564
// if we are to do recursive thinning, we will have to add fill-ins to the
565
// triangulated graph each time we eliminate a node. Hence, we shall
566
// initialize the triangulated graph by a copy of the original graph
567
if
(
minimality_required__
) {
568
triangulated_graph__
= *
original_graph__
;
569
has_triangulated_graph__
=
true
;
570
}
571
572
// perform the triangulation
573
NodeId
removable_node
= 0;
574
575
for
(
unsigned
int
nb_elim
= 0;
tmp_graph
.
size
() != 0; ++
nb_elim
) {
576
removable_node
=
elimination_sequence_strategy_
->
nextNodeToEliminate
();
577
578
// when minimality is not required, i.e., we won't apply recursive
579
// thinning, the cliques sets can be computed
580
if
(!
minimality_required__
) {
581
NodeSet
&
cliques
=
elim_cliques__
.
insert
(
removable_node
,
NodeSet
()).
second
;
582
cliques
.
resize
(
tmp_graph
.
neighbours
(
removable_node
).
size
() / 2);
583
cliques
<<
removable_node
;
584
for
(
const
auto
clik
:
tmp_graph
.
neighbours
(
removable_node
))
585
cliques
<<
clik
;
586
}
else
{
587
// here recursive thinning will be applied, hence we need store the
588
// fill-ins added by the current node removal
589
EdgeSet
&
current_fill
=
added_fill_ins__
[
nb_elim
];
590
NodeId
node1
,
node2
;
591
592
const
NodeSet
&
nei
=
tmp_graph
.
neighbours
(
removable_node
);
593
594
for
(
auto
iter_edges1
=
nei
.
begin
();
iter_edges1
!=
nei
.
end
();
595
++
iter_edges1
) {
596
node1
= *
iter_edges1
;
597
auto
iter_edges2
=
iter_edges1
;
598
599
for
(++
iter_edges2
;
iter_edges2
!=
nei
.
end
(); ++
iter_edges2
) {
600
node2
= *
iter_edges2
;
601
Edge
edge
(
node1
,
node2
);
602
603
if
(!
tmp_graph
.
existsEdge
(
edge
)) {
604
current_fill
.
insert
(
edge
);
605
triangulated_graph__
.
addEdge
(
node1
,
node2
);
606
}
607
}
608
}
609
}
610
611
// if we want fill-ins but the elimination sequence strategy does not
612
// compute them, we shall compute them here
613
if
(
we_want_fill_ins__
614
&& !
elimination_sequence_strategy_
->
providesFillIns
()) {
615
NodeId
node1
,
node2
;
616
617
// add edges between removable_node's neighbours in order to create
618
// a clique
619
const
NodeSet
&
nei
=
tmp_graph
.
neighbours
(
removable_node
);
620
621
for
(
auto
iter_edges1
=
nei
.
begin
();
iter_edges1
!=
nei
.
end
();
622
++
iter_edges1
) {
623
node1
= *
iter_edges1
;
624
auto
iter_edges2
=
iter_edges1
;
625
626
for
(++
iter_edges2
;
iter_edges2
!=
nei
.
end
(); ++
iter_edges2
) {
627
node2
= *
iter_edges2
;
628
Edge
edge
(
node1
,
node2
);
629
630
if
(!
tmp_graph
.
existsEdge
(
edge
)) {
fill_ins__
.
insert
(
edge
); }
631
}
632
}
633
634
// delete removable_node and its adjacent edges
635
tmp_graph
.
eraseNode
(
removable_node
);
636
}
637
638
// inform the elimination sequence that we are actually removing
639
// removable_node (this enables, for instance, to update the weights of
640
// the nodes in the graph)
641
elimination_sequence_strategy_
->
eliminationUpdate
(
removable_node
);
642
643
if
(!
elimination_sequence_strategy_
->
providesGraphUpdate
()) {
644
NodeId
node1
,
node2
;
645
646
const
NodeSet
&
nei
=
tmp_graph
.
neighbours
(
removable_node
);
647
648
for
(
auto
iter_edges1
=
nei
.
begin
();
iter_edges1
!=
nei
.
end
();
649
++
iter_edges1
) {
650
node1
= *
iter_edges1
;
651
auto
iter_edges2
=
iter_edges1
;
652
653
for
(++
iter_edges2
;
iter_edges2
!=
nei
.
end
(); ++
iter_edges2
) {
654
node2
= *
iter_edges2
;
655
Edge
edge
(
node1
,
node2
);
656
657
if
(!
tmp_graph
.
existsEdge
(
edge
)) {
tmp_graph
.
addEdge
(
node1
,
node2
); }
658
}
659
}
660
661
tmp_graph
.
eraseNode
(
removable_node
);
662
}
663
664
// update the elimination order
665
elim_order__
[
nb_elim
] =
removable_node
;
666
reverse_elim_order__
.
insert
(
removable_node
,
nb_elim
);
667
}
668
669
// indicate whether we actually computed fill-ins
670
has_fill_ins__
=
we_want_fill_ins__
;
671
672
// if minimality is required, remove the redondant edges
673
if
(
minimality_required__
)
computeRecursiveThinning__
();
674
675
has_triangulation__
=
true
;
676
}
677
678
/// returns the fill-ins added by the triangulation algorithm
679
const
EdgeSet
&
StaticTriangulation
::
fillIns
() {
680
// if we did not compute the triangulation yet, do it and commpute
681
// the fill-ins at the same time
682
if
(!
has_triangulation__
) {
683
bool
want_fill_ins
=
we_want_fill_ins__
;
684
we_want_fill_ins__
=
true
;
685
triangulate__
();
686
we_want_fill_ins__
=
want_fill_ins
;
687
has_fill_ins__
=
true
;
688
}
689
690
// here, we already computed a triangulation and we already computed
691
// the fill-ins, so return them
692
if
(
has_fill_ins__
) {
693
if
(
elimination_sequence_strategy_
->
providesFillIns
())
694
return
elimination_sequence_strategy_
->
fillIns
();
695
else
696
return
fill_ins__
;
697
}
else
{
698
// ok, here, we shall compute the fill-ins as they were not precomputed
699
if
(!
original_graph__
)
return
fill_ins__
;
700
701
// just in case, be sure that the junction tree has been constructed
702
if
(!
has_junction_tree__
)
junctionTree
();
703
704
for
(
const
auto
clik_id
:
junction_tree__
->
nodes
()) {
705
// for each clique, add the edges necessary to make it complete
706
const
NodeSet
&
clique
=
junction_tree__
->
clique
(
clik_id
);
707
std
::
vector
<
NodeId
>
clique_nodes
(
clique
.
size
());
708
unsigned
int
i
= 0;
709
710
for
(
const
auto
node
:
clique
) {
711
clique_nodes
[
i
] =
node
;
712
i
+= 1;
713
}
714
715
for
(
i
= 0;
i
<
clique_nodes
.
size
(); ++
i
) {
716
for
(
unsigned
int
j
=
i
+ 1;
j
<
clique_nodes
.
size
(); ++
j
) {
717
Edge
edge
(
clique_nodes
[
i
],
clique_nodes
[
j
]);
718
719
if
(!
original_graph__
->
existsEdge
(
edge
)) {
720
try
{
721
fill_ins__
.
insert
(
edge
);
722
}
catch
(
DuplicateElement
&) {}
723
}
724
}
725
}
726
}
727
728
return
fill_ins__
;
729
}
730
}
731
732
/// the function called to initialize the triangulation process
733
void
StaticTriangulation
::
initTriangulation_
(
UndiGraph
&
graph
) {
734
elimination_sequence_strategy_
->
setGraph
(&
graph
,
domain_sizes_
);
735
}
736
737
}
/* namespace gum */
gum::Set::emplace
INLINE void emplace(Args &&... args)
Definition:
set_tpl.h:669