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
41  const UndiGraph* theGraph,
42  const NodeProperty< Size >* domsizes,
45  bool minimality) :
50  // for debugging purposes
52 
53  // if the graph is not empty, resize several structures in order to speed-up
54  // their fillings.
55  if (theGraph != nullptr) {
61  }
62 
63  // register the triangulation to its junction tree strategy
65  }
66 
67  // default constructor
71  bool minimality) :
75  // for debugging purposes
77 
78  // register the triangulation to its junction tree strategy
80  }
81 
82  // copy constructor
100  // copy the strategies
104 
105  // if from has a junction tree, copy it
106  if (from.junction_tree__ != nullptr) {
108  }
109 
110  // for debugging purposes
112  }
113 
114  // move constructor
137  // move the factories contained in from
141 
142  // if from has a junction tree, copy it
143  if (from.junction_tree__ != nullptr) {
145  }
146 
147  // for debugging purposes
149  }
150 
151  /// destructor
153  // for debugging purposes
155 
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
165  // clear the factories
168 
169  // remove the current graphs
170  original_graph__ = nullptr;
171  junction_tree__ = nullptr;
173  elim_tree__.clear();
177 
178  // remove all fill-ins and orderings
179  fill_ins__.clear();
183 
184  // indicates that the junction tree, the max prime tree, etc, are updated
185  has_triangulation__ = true;
187  has_elimination_tree__ = true;
188  has_junction_tree__ = true;
190  has_fill_ins__ = true;
191  }
192 
193  // removes redondant fill-ins and compute proper elimination cliques and order
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;
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) {
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
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))
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) {
260  incomplete = true;
261  break;
262  }
263  }
264  }
265 
266  adj.clear();
267 
268  if (!incomplete) {
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);
280 
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 >(),
303 
304  for (Size i = 0; i < elim_order__.size(); ++i)
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);
312  elim_order__[ni] = node;
314 
315  for (const auto neighbour: triangulated_graph__.neighbours(node)) {
316  try {
318  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) {
329  cliques << elim_order__[i];
330 
333  }
334  }
335 
336  /// returns an elimination tree from a triangulated graph
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
343 
344  // create the nodes of the elimination tree
345  elim_tree__.clear();
346 
348  for (NodeId i = NodeId(0); i < size; ++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) {
356  Idx child = original_graph__->bound() + 1;
357 
358  for (const auto node: list_of_nodes) {
360 
361  if ((node != clique_i_creator) && (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
369  }
370  }
371 
372  has_elimination_tree__ = true;
373  }
374 
375  /// used for computing the junction tree of the maximal prime subgraphs
377  const NodeId node,
378  const NodeId from,
380  NodeSet& mark) const {
381  mark << node;
382 
383  for (const auto other_node: junction_tree__->neighbours(node))
384  if (other_node != from) {
386  // check that the separator between node and other_node is complete
387  bool complete = true;
388 
389  for (auto iter_sep1 = separator.begin();
391  ++iter_sep1) {
392  auto iter_sep2 = iter_sep1;
393 
394  for (++iter_sep2; iter_sep2 != separator.end(); ++iter_sep2) {
396  complete = false;
397  break;
398  }
399  }
400  }
401 
402  // here complete indicates whether the separator is complete or not
404 
406  }
407  }
408 
409  /// computes the junction tree of the maximal prime subgraphs
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
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.
425 
426  for (const auto clik: junction_tree__->nodes())
428 
429  // parse all the separators of the junction tree and test those that are not
430  // complete in the orginal graph
432 
433  NodeSet mark;
434 
435  for (const auto clik: junction_tree__->nodes())
436  if (!mark.contains(clik))
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) {
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)
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 {
460  } catch (DuplicateElement&) {}
461  }
462 
463  // add the edges to the graph
464  for (const auto& edge: junction_tree__->edges()) {
467 
468  if (node1 != node2) {
469  try {
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
479 
480  for (const auto& elt: node_2_junction_clique)
482 
484  }
485 
486  /// returns the triangulated graph
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
493  // just in case, be sure that the junction tree has been constructed
495 
496  // copy the original graph
498 
499  for (const auto clik_id: *junction_tree__) {
500  // for each clique, add the edges necessary to make it complete
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 {
514  } catch (DuplicateElement&) {}
515  }
516  }
517  }
518 
520  }
521 
522  return triangulated_graph__;
523  }
524 
525  // initialize the triangulation algorithm for a new 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
538  }
539 
540  // keep track of the variables passed in argument
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;
550  has_fill_ins__ = false;
551  }
552 
553  /// the function that performs the triangulation
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
560 
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__) {
570  }
571 
572  // perform the triangulation
574 
575  for (unsigned int nb_elim = 0; tmp_graph.size() != 0; ++nb_elim) {
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__) {
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
590  NodeId node1, node2;
591 
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)) {
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
615  NodeId node1, node2;
616 
617  // add edges between removable_node's neighbours in order to create
618  // a clique
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 
631  }
632  }
633 
634  // delete removable_node and its adjacent edges
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)
642 
644  NodeId node1, node2;
645 
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 
658  }
659  }
660 
662  }
663 
664  // update the elimination order
667  }
668 
669  // indicate whether we actually computed fill-ins
671 
672  // if minimality is required, remove the redondant edges
674 
675  has_triangulation__ = true;
676  }
677 
678  /// returns the fill-ins added by the triangulation algorithm
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__) {
684  we_want_fill_ins__ = true;
685  triangulate__();
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__) {
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
703 
704  for (const auto clik_id: junction_tree__->nodes()) {
705  // for each clique, add the edges necessary to make it complete
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) {
718 
720  try {
722  } catch (DuplicateElement&) {}
723  }
724  }
725  }
726  }
727 
728  return fill_ins__;
729  }
730  }
731 
732  /// the function called to initialize the triangulation process
735  }
736 
737 } /* namespace gum */
INLINE void emplace(Args &&... args)
Definition: set_tpl.h:669