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