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
41  const NodeProperty< Size >* domsizes,
44  bool minimality) :
49  // for debugging purposes
51 
52  // if the graph is not empty, resize several structures in order to speed-up
53  // their fillings.
54  if (theGraph != nullptr) {
60  }
61 
62  // register the triangulation to its junction tree strategy
64  }
65 
66  // default constructor
69  bool minimality) :
72  // for debugging purposes
74 
75  // register the triangulation to its junction tree strategy
77  }
78 
79  // copy constructor
94  // copy the strategies
97 
98  // if from has a junction tree, copy it
99  if (from._junction_tree_ != nullptr) {
101  }
102 
103  // for debugging purposes
105  }
106 
107  // move constructor
127  // move the factories contained in from
131 
132  // if from has a junction tree, copy it
133  if (from._junction_tree_ != nullptr) {
135  }
136 
137  // for debugging purposes
139  }
140 
141  /// destructor
143  // for debugging purposes
145 
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
155  // clear the factories
158 
159  // remove the current graphs
160  _original_graph_ = nullptr;
161  _junction_tree_ = nullptr;
163  _elim_tree_.clear();
167 
168  // remove all fill-ins and orderings
169  _fill_ins_.clear();
173 
174  // indicates that the junction tree, the max prime tree, etc, are updated
175  _has_triangulation_ = true;
177  _has_elimination_tree_ = true;
178  _has_junction_tree_ = true;
180  _has_fill_ins_ = true;
181  }
182 
183  // removes redondant fill-ins and compute proper elimination cliques and order
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;
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
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))
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) {
247  incomplete = true;
248  break;
249  }
250  }
251  }
252 
253  adj.clear();
254 
255  if (!incomplete) {
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);
267 
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 >(),
290 
291  for (Size i = 0; i < _elim_order_.size(); ++i)
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);
299  _elim_order_[ni] = node;
301 
302  for (const auto neighbour: _triangulated_graph_.neighbours(node)) {
303  try {
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) {
314  cliques << _elim_order_[i];
315 
318  }
319  }
320 
321  /// returns an elimination tree from a triangulated graph
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
328 
329  // create the nodes of the elimination tree
330  _elim_tree_.clear();
331 
333  for (NodeId i = NodeId(0); i < size; ++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) {
341  Idx child = _original_graph_->bound() + 1;
342 
343  for (const auto node: list_of_nodes) {
345 
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
353  }
354  }
355 
356  _has_elimination_tree_ = true;
357  }
358 
359  /// used for computing the junction tree of the maximal prime subgraphs
361  const NodeId from,
363  NodeSet& mark) const {
364  mark << node;
365 
366  for (const auto other_node: _junction_tree_->neighbours(node))
367  if (other_node != from) {
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) {
378  complete = false;
379  break;
380  }
381  }
382  }
383 
384  // here complete indicates whether the separator is complete or not
386 
388  }
389  }
390 
391  /// computes the junction tree of the maximal prime subgraphs
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
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.
407 
408  for (const auto clik: _junction_tree_->nodes())
410 
411  // parse all the separators of the junction tree and test those that are not
412  // complete in the orginal graph
414 
415  NodeSet mark;
416 
417  for (const auto clik: _junction_tree_->nodes())
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) {
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)
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 {
439  } catch (DuplicateElement&) {}
440  }
441 
442  // add the edges to the graph
443  for (const auto& edge: _junction_tree_->edges()) {
446 
447  if (node1 != node2) {
448  try {
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
458 
459  for (const auto& elt: node_2_junction_clique)
461 
463  }
464 
465  /// returns the triangulated graph
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
472  // just in case, be sure that the junction tree has been constructed
474 
475  // copy the original graph
477 
478  for (const auto clik_id: *_junction_tree_) {
479  // for each clique, add the edges necessary to make it complete
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 {
493  } catch (DuplicateElement&) {}
494  }
495  }
496  }
497 
499  }
500 
501  return _triangulated_graph_;
502  }
503 
504  // initialize the triangulation algorithm for a new graph
506  // remove the old graph
507  clear();
508 
509  if (graph != nullptr) {
510  // prepare the size of the data for the new graph
516  }
517 
518  // keep track of the variables passed in argument
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;
528  _has_fill_ins_ = false;
529  }
530 
531  /// the function that performs the triangulation
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
538 
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_) {
548  }
549 
550  // perform the triangulation
552 
553  for (unsigned int nb_elim = 0; tmp_graph.size() != 0; ++nb_elim) {
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_) {
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
568  NodeId node1, node2;
569 
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)) {
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
591  NodeId node1, node2;
592 
593  // add edges between removable_node's neighbours in order to create
594  // a clique
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 
606  }
607  }
608 
609  // delete removable_node and its adjacent edges
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)
617 
619  NodeId node1, node2;
620 
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 
632  }
633  }
634 
636  }
637 
638  // update the elimination order
641  }
642 
643  // indicate whether we actually computed fill-ins
645 
646  // if minimality is required, remove the redondant edges
648 
649  _has_triangulation_ = true;
650  }
651 
652  /// returns the fill-ins added by the triangulation algorithm
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_) {
658  _we_want_fill_ins_ = true;
659  _triangulate_();
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_) {
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
677 
678  for (const auto clik_id: _junction_tree_->nodes()) {
679  // for each clique, add the edges necessary to make it complete
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) {
692 
694  try {
696  } catch (DuplicateElement&) {}
697  }
698  }
699  }
700  }
701 
702  return _fill_ins_;
703  }
704  }
705 
706  /// the function called to initialize the triangulation process
709  }
710 
711 } /* namespace gum */
INLINE void emplace(Args &&... args)
Definition: set_tpl.h:643