aGrUM  0.14.2
incrementalTriangulation.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * Copyright (C) 2005 by Christophe GONZALES and Pierre-Henri WUILLEMIN *
3  * {prenom.nom}_at_lip6.fr *
4  * *
5  * This program is free software; you can redistribute it and/or modify *
6  * it under the terms of the GNU General Public License as published by *
7  * the Free Software Foundation; either version 2 of the License, or *
8  * (at your option) any later version. *
9  * *
10  * This program is distributed in the hope that it will be useful, *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13  * GNU General Public License for more details. *
14  * *
15  * You should have received a copy of the GNU General Public License *
16  * along with this program; if not, write to the *
17  * Free Software Foundation, Inc., *
18  * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19  ***************************************************************************/
20 
27 #include <limits>
28 #include <utility>
29 
30 #include <agrum/agrum.h>
31 #include <agrum/core/math/math.h>
32 
33 #include <agrum/core/list.h>
35 #include <agrum/graphs/undiGraph.h>
36 
37 #ifdef GUM_NO_INLINE
39 #endif // GUM_NO_INLINE
40 
41 #ifndef DOXYGEN_SHOULD_SKIP_THIS
42 
43 namespace gum {
44 
47  const UnconstrainedTriangulation& triang_algo,
48  const UndiGraph* theGraph,
49  const NodeProperty< Size >* domsizes) :
50  Triangulation(domsizes),
51  __triangulation(triang_algo.newFactory()) {
52  // for debugging purposes
53  GUM_CONSTRUCTOR(IncrementalTriangulation);
54 
55  // reset the triangulation algorithm => it starts with an empty graph
56  __triangulation->clear();
57 
58  // copy the graph passed in argument and update the structures
59  // containing the informations useful for the triangulation
60 
61  for (const auto node : *theGraph)
62  addNode(node, (*domsizes)[node]);
63 
64  // insert all the edges of the graph into the structure. This will
65  // implicitly update the "require_update" field
66  for (const auto& edge : theGraph->edges())
67  addEdge(edge.first(), edge.second());
68  }
69 
72  const UnconstrainedTriangulation& triang_algo) :
73  __triangulation(triang_algo.newFactory()) {
74  // for debugging purposes
75  GUM_CONSTRUCTOR(IncrementalTriangulation);
76 
77  // reset the triangulation algorithm => it starts with an empty graph
78  __triangulation->clear();
79  }
80 
83  const IncrementalTriangulation& from) :
84  Triangulation(from),
85  __graph(from.__graph), __junction_tree(from.__junction_tree),
86  __T_mpd(from.__T_mpd), __mps_of_node(from.__mps_of_node),
87  __cliques_of_mps(from.__cliques_of_mps),
88  __mps_of_clique(from.__mps_of_clique), __mps_affected(from.__mps_affected),
89  __triangulation(from.__triangulation->newFactory()),
90  __require_update(from.__require_update),
91  __require_elimination_order(from.__require_elimination_order),
92  __elimination_order(from.__elimination_order),
93  __reverse_elimination_order(from.__reverse_elimination_order),
94  __require_created_JT_cliques(from.__require_created_JT_cliques),
95  __created_JT_cliques(from.__created_JT_cliques) {
96  // for debugging purposes
97  GUM_CONS_CPY(IncrementalTriangulation);
98 
99  __domain_sizes = from.__domain_sizes;
100  }
101 
104  // for debugging purposes
105  GUM_DESTRUCTOR(IncrementalTriangulation);
106 
107  // remove things that were allocated within the current class
108  delete __triangulation;
109  }
110 
112  IncrementalTriangulation* IncrementalTriangulation::newFactory() const {
113  return new IncrementalTriangulation(*__triangulation);
114  }
115 
117  IncrementalTriangulation* IncrementalTriangulation::copyFactory() const {
118  return new IncrementalTriangulation(*this);
119  }
120 
122  IncrementalTriangulation& IncrementalTriangulation::
123  operator=(const IncrementalTriangulation& from) {
124  // avoid self assignment
125  if (this != &from) {
126  // for debugging purposes
127  GUM_OP_CPY(IncrementalTriangulation);
128 
129  // copy all the structures stored in "from"
130  __graph = from.__graph;
131  __domain_sizes = from.__domain_sizes;
132  __junction_tree = from.__junction_tree;
133  __T_mpd = from.__T_mpd;
134  __mps_of_node = from.__mps_of_node;
135  __cliques_of_mps = from.__cliques_of_mps;
136  __mps_of_clique = from.__mps_of_clique;
137  __mps_affected = from.__mps_affected;
138  __require_update = from.__require_update;
139  __require_elimination_order = from.__require_elimination_order;
140  __elimination_order = from.__elimination_order;
141  __reverse_elimination_order = from.__reverse_elimination_order;
142  __require_created_JT_cliques = from.__require_created_JT_cliques;
143  __created_JT_cliques = from.__created_JT_cliques;
144 
145  // just in case we changed the triangulation algorithm, we remove it
146  // and create it again
147  delete __triangulation;
148  __triangulation = from.__triangulation->newFactory();
149  }
150 
151  return *this;
152  }
153 
155  void IncrementalTriangulation::addNode(const NodeId node, Size modal) {
156  // check if the node already exists
157  if (__graph.existsNode(node)) return;
158 
159  // add the new node to the graph
160  __graph.addNodeWithId(node);
161  __domain_sizes.insert(node, modal);
162 
163  // add a new clique to T_mpd and the junction tree
164  NodeSet clique_nodes(2);
165  clique_nodes.insert(node);
166 
167  NodeId MPS = __T_mpd.addNode(clique_nodes);
168  NodeId new_clique = __junction_tree.addNode(clique_nodes);
169 
170  // indicate in which MPS node belongs
171  List< NodeId >& list_of_mps =
172  __mps_of_node.insert(node, List< NodeId >()).second;
173  list_of_mps.insert(MPS);
174 
175  // indicate in which MPS the clique added to the junction tree belongs
176  std::vector< NodeId >& cliquesMPS =
177  __cliques_of_mps.insert(MPS, std::vector< NodeId >()).second;
178 
179  cliquesMPS.push_back(new_clique);
180  __mps_of_clique.insert(new_clique, MPS);
181 
182  // indicate that the new MPS should not be affected by a triangulation
183  __mps_affected.insert(MPS, false);
184 
185  // insert the node into the elimination order sequence
186  __elimination_order.push_back(node);
187 
188  if (!__reverse_elimination_order.exists(node))
189  __reverse_elimination_order.insert(node, Size(__elimination_order.size()));
190 
191  if (!__created_JT_cliques.exists(node))
192  __created_JT_cliques.insert(node, new_clique);
193  }
194 
196 
198  const NodeId Mz,
199  const Edge& edge) {
200  // mark the MPS so that it will be retriangulated
201  __mps_affected[My] = true;
202 
203  // mark all the neighbour MPS that contain edge
204  for (const auto nei : __T_mpd.neighbours(My))
205  if (nei != Mz) {
206  const NodeSet& Syk = __T_mpd.separator(Edge(nei, My));
207 
208  if (Syk.contains(edge.first()) && Syk.contains(edge.second()))
209  __markAffectedMPSsByRemoveLink(nei, My, edge);
210  }
211  }
212 
214 
215  void IncrementalTriangulation::eraseEdge(const Edge& edge) {
216  // check that the edge exist
217  if (!__graph.existsEdge(edge)) return;
218 
219  // find a MPS containing the edge (X,Y)
220  const NodeId X = edge.first();
221  const NodeId Y = edge.second();
222 
223  const List< NodeId >& mps1 = __mps_of_node[X];
224  const List< NodeId >& mps2 = __mps_of_node[Y];
225 
226  NodeId Mx = mps1[0];
227 
228  if (mps1.size() <= mps2.size()) {
229  for (const auto node : mps1)
230  if (__T_mpd.clique(node).contains(Y)) {
231  Mx = node;
232  break;
233  }
234  } else {
235  for (const auto node : mps2)
236  if (__T_mpd.clique(node).contains(X)) {
237  Mx = node;
238  break;
239  }
240  }
241 
242  // mark the MPS that need be updated
243  __markAffectedMPSsByRemoveLink(Mx, Mx, edge);
244 
245  __require_update = true;
246 
247  __require_elimination_order = true;
248 
249  __require_created_JT_cliques = true;
250 
251  // remove the edge (X,Y) from the graph
252  __graph.eraseEdge(edge);
253  }
254 
257 
259  // check if the node exists
260  if (!__graph.existsNode(X)) return;
261 
262  // remove all the edges adjacent to the node
263  {
264  const NodeSet& neighbours = __graph.neighbours(X);
265 
266  for (auto neighbour_edge =
267  neighbours.beginSafe(); // safe iterator needed here
268  neighbour_edge != neighbours.endSafe();
269  ++neighbour_edge) {
270  eraseEdge(Edge(*neighbour_edge, X));
271  }
272  }
273 
274  auto& MPS_of_X = __mps_of_node[X];
275 
276  // remove X from the MPS containing X
277  for (const auto node : MPS_of_X) {
278  __T_mpd.eraseFromClique(node, X);
279 
280  // if the intersection between *iter and one of its neighbour is empty,
281  // remove the edge linking them
282  auto& neighbours = __T_mpd.neighbours(node);
283 
284  for (auto it_neighbour = neighbours.beginSafe();
285  it_neighbour != neighbours.endSafe();
286  ++it_neighbour) { // safe iterator needed here
287  Edge neigh(*it_neighbour, node);
288 
289  if (__T_mpd.separator(neigh).size() == 0) __T_mpd.eraseEdge(neigh);
290  }
291  }
292 
293  // remove X from the cliques containing X
294  for (const auto clique : MPS_of_X) {
295  const std::vector< NodeId >& cliques_of_X = __cliques_of_mps[clique];
296 
297  for (unsigned int i = 0; i < cliques_of_X.size(); ++i) {
298  __junction_tree.eraseFromClique(cliques_of_X[i], X);
299 
300  // if the intersection between clique and one of its neighbour is empty,
301  // remove the edge linking them only if, in addition, there is no
302  // edge in __graph between a node of clique and a node in the neighbour
303  auto& neighbours = __junction_tree.neighbours(cliques_of_X[i]);
304 
305  for (auto it_neighbour = neighbours.beginSafe();
306  it_neighbour != neighbours.endSafe();
307  ++it_neighbour) { // safe iterator needed here
308  Edge neigh(*it_neighbour, cliques_of_X[i]);
309 
310  if (__junction_tree.separator(neigh).size() == 0) {
311  // try to see if there is an edge between the nodes of one extremity
312  // of *neighbour and those of the other extremity
313  bool hasCommonEdge = false;
314 
315  for (const auto node1 : __junction_tree.clique(neigh.first()))
316  for (const auto node2 : __junction_tree.clique(neigh.second()))
317  if (__graph.existsEdge(node1, node2)) {
318  hasCommonEdge = true;
319  break;
320  }
321 
322  if (!hasCommonEdge) { __junction_tree.eraseEdge(neigh); }
323  }
324  }
325  }
326  }
327 
328  // if the MPS containing X is empty, then remove it, as well as the
329  // corresponding clique in the junction tree (which also only contains X)
330  if ((MPS_of_X.size() == 1) && (__T_mpd.clique(MPS_of_X[0]).size() == 0)) {
331  __junction_tree.eraseNode(__cliques_of_mps[MPS_of_X[0]][0]);
332  __T_mpd.eraseNode(MPS_of_X[0]);
333  __mps_of_clique.erase(__cliques_of_mps[MPS_of_X[0]][0]);
334  __cliques_of_mps.erase(MPS_of_X[0]);
335  __mps_affected.erase(MPS_of_X[0]);
336  }
337 
338  __mps_of_node.erase(X);
339 
340  // update the elimination orders
341 
342  if (!__require_update) {
343  for (Idx i = __reverse_elimination_order[X] + 1;
344  i < __reverse_elimination_order.size();
345  ++i)
346  __elimination_order[i - 1] = __elimination_order[i];
347 
348  __elimination_order.pop_back();
349 
350  __reverse_elimination_order.erase(X);
351 
352  __created_JT_cliques.erase(X);
353  }
354 
355  // remove X completely from the graph
356  __graph.eraseNode(X);
357 
358  __domain_sizes.erase(X);
359  }
360 
362 
364  const NodeId Mz,
365  const NodeId X,
366  const NodeId Y) {
367  // check if Mx contains Y. In this case, mark Mx and return 1 to indicate
368  // that
369  // Y has been found or 2 to indicate that Y has been found and that the
370  // nearest
371  // MPS containing X has been marked
372  const NodeSet& cliqueMX = __T_mpd.clique(Mx);
373 
374  if (cliqueMX.contains(Y)) {
375  __mps_affected[Mx] = true;
376 
377  if (cliqueMX.contains(X)) return 2;
378 
379  return 1;
380  }
381 
382  // parse Mx's neighbours until we find Y
383  for (const auto other_node : __T_mpd.neighbours(Mx))
384  if (other_node != Mz) {
385  int neighbourStatus = __markAffectedMPSsByAddLink(other_node, Mx, X, Y);
386 
387  if (neighbourStatus == 2)
388  return 2;
389  else if (neighbourStatus == 1) {
390  __mps_affected[Mx] = true;
391 
392  if (cliqueMX.contains(X)) return 2;
393 
394  return 1;
395  }
396  }
397 
398  // indicate that X was not found
399  return 0;
400  }
401 
404  void IncrementalTriangulation::addEdge(const NodeId X, const NodeId Y) {
405  // check that the edge exist
406  if ((X == Y) || !__graph.existsNode(X) || !__graph.existsNode(Y)
407  || __graph.existsEdge(Edge(X, Y)))
408  return;
409 
410  // add the edge to the graph
411  __graph.addEdge(X, Y);
412 
413  // take any MPS containing X and search its tree to find Y
414  NodeId& mps_X = __mps_of_node[X][0];
415 
416  int found = __markAffectedMPSsByAddLink(mps_X, mps_X, X, Y);
417 
418  if (found == 0) {
419  // the mps containing X do not belong to the same tree as those containing
420  // Y
421  NodeId& mps_Y = __mps_of_node[Y][0];
422 
423  // find a clique in mps_X containing X and another in mps_Y containing Y
424  // and add a clique XY to the junction tree linked to the cliques found
425  // in mps_X and mps_Y
426  const std::vector< NodeId >& cliques_X = __cliques_of_mps[mps_X];
427  const std::vector< NodeId >& cliques_Y = __cliques_of_mps[mps_Y];
428  NodeId c_X = 0, c_Y = 0;
429 
430  for (unsigned int i = 0; i < cliques_X.size(); ++i) {
431  if (__junction_tree.clique(cliques_X[i]).contains(X)) {
432  c_X = cliques_X[i];
433  break;
434  }
435  }
436 
437  for (unsigned int i = 0; i < cliques_Y.size(); ++i) {
438  if (__junction_tree.clique(cliques_Y[i]).contains(Y)) {
439  c_Y = cliques_Y[i];
440  break;
441  }
442  }
443 
444  // link c_Y and c_X through a new node containing XY
445  NodeSet nodes(2);
446 
447  nodes.insert(X);
448 
449  nodes.insert(Y);
450 
451  NodeId newNode = __junction_tree.addNode(nodes);
452 
453  __junction_tree.addEdge(newNode, c_X);
454  __junction_tree.addEdge(newNode, c_Y);
455 
456  NodeId newMPS = __T_mpd.addNode(nodes);
457 
458  __T_mpd.addEdge(newMPS, mps_X);
459  __T_mpd.addEdge(newMPS, mps_Y);
460 
461  // check that the maximal prime subgraph containing X is not X alone
462  // in this case, remove this max prime subgraph, as well as the
463  // corresponding
464  // clique in the junction tree
465  if (__T_mpd.clique(mps_X).size() == 1) {
466  __junction_tree.eraseNode(c_X);
467  __T_mpd.eraseNode(mps_X);
468  __mps_affected.erase(mps_X);
469  __mps_of_clique.erase(c_X);
470  __cliques_of_mps.erase(mps_X);
471  __created_JT_cliques[X] = newNode;
472  mps_X = newMPS;
473  } else
474  __mps_of_node[X].insert(newMPS);
475 
476  // do the same thing as above for node Y
477  if (__T_mpd.clique(mps_Y).size() == 1) {
478  __junction_tree.eraseNode(c_Y);
479  __T_mpd.eraseNode(mps_Y);
480  __mps_affected.erase(mps_Y);
481  __mps_of_clique.erase(c_Y);
482  __cliques_of_mps.erase(mps_Y);
483  __created_JT_cliques[Y] = newNode;
484  mps_Y = newMPS;
485  } else
486  __mps_of_node[Y].insert(newMPS);
487 
488  std::vector< NodeId >& cl =
489  __cliques_of_mps.insert(newMPS, std::vector< NodeId >()).second;
490 
491  cl.push_back(newNode);
492 
493  __mps_of_clique.insert(newNode, newMPS);
494 
495  __mps_affected.insert(newMPS, false);
496  } else {
497  __require_update = true;
498  __require_created_JT_cliques = true;
499  }
500 
501  // in all cases, recompute the elimination ordering
502  __require_elimination_order = true;
503  }
504 
506 
508  // just in case, update everything
509  updateTriangulation();
510 
511  bool OK = true;
512 
513  // check that all the nodes of the graph belong to the junction tree
514  {
515  NodeProperty< bool > nodesProp = __graph.nodesProperty< bool >(false);
516 
517  for (const auto cliq : __junction_tree.nodes())
518  for (const auto node : __junction_tree.clique(cliq))
519  nodesProp[node] = true;
520 
521  for (const auto& elt : nodesProp)
522  if (!elt.second) {
523  std::cerr << "check nodes" << std::endl
524  << __graph << std::endl
525  << __junction_tree << std::endl;
526  OK = false;
527  }
528 
529  if (!OK) return false;
530  }
531 
532  // check that the edgs belong to the junction tree
533  {
534  std::pair< NodeId, NodeId > thePair;
535  EdgeProperty< bool > edgesProp = __graph.edgesProperty(false);
536 
537  for (const auto cliq : __junction_tree.nodes()) {
538  const NodeSet& clique = __junction_tree.clique(cliq);
539 
540  for (auto iter2 = clique.begin(); iter2 != clique.end(); ++iter2) {
541  auto iter3 = iter2;
542 
543  for (++iter3; iter3 != clique.end(); ++iter3) {
544  thePair.first = std::min(*iter2, *iter3);
545  thePair.second = std::max(*iter2, *iter3);
546 
547  if (__graph.existsEdge(thePair.first, thePair.second))
548  edgesProp[Edge(thePair.first, thePair.second)] = true;
549  }
550  }
551  }
552 
553  for (const auto& elt : edgesProp)
554  if (!elt.second) {
555  std::cerr << "check edges" << std::endl
556  << __graph << std::endl
557  << __junction_tree << std::endl;
558  OK = false;
559  }
560 
561  if (!OK) return false;
562  }
563 
564  // check that all the nodes of the graph belong to the MPS tree
565  {
566  NodeProperty< bool > nodesProp = __graph.nodesProperty< bool >(false);
567 
568  for (const auto cliq : __T_mpd.nodes())
569  for (const auto node : __T_mpd.clique(cliq))
570  nodesProp[node] = true;
571 
572  for (const auto& elt : nodesProp)
573  if (!elt.second) {
574  std::cerr << "check nodes" << std::endl
575  << __graph << std::endl
576  << __T_mpd << std::endl;
577  OK = false;
578  }
579 
580  if (!OK) return false;
581  }
582 
583  // check that the arcs of the graph belong to the MPS tree
584  {
585  std::pair< NodeId, NodeId > thePair;
586  EdgeProperty< bool > edgesProp = __graph.edgesProperty(false);
587 
588  for (const auto cliq : __T_mpd.nodes()) {
589  const NodeSet& clique = __T_mpd.clique(cliq);
590 
591  for (auto iter2 = clique.begin(); iter2 != clique.end(); ++iter2) {
592  auto iter3 = iter2;
593 
594  for (++iter3; iter3 != clique.end(); ++iter3) {
595  thePair.first = std::min(*iter2, *iter3);
596  thePair.second = std::max(*iter2, *iter3);
597 
598  if (__graph.existsEdge(thePair.first, thePair.second))
599  edgesProp[Edge(thePair.first, thePair.second)] = true;
600  }
601  }
602  }
603 
604  for (const auto& elt : edgesProp)
605  if (!elt.second) {
606  std::cerr << "check edges" << std::endl
607  << __graph << std::endl
608  << __T_mpd << std::endl;
609  OK = false;
610  }
611 
612  if (!OK) return false;
613  }
614 
615  // check the MPS of node
616  {
617  NodeProperty< NodeProperty< bool > > chk;
618 
619  for (const auto node : __graph.nodes())
620  chk.insert(node, HashTable< NodeId, bool >());
621 
622  for (const auto cliq : __T_mpd.nodes())
623  for (auto node : __T_mpd.clique(cliq))
624  chk[node].insert(cliq, false);
625 
626  for (const auto& elt : __mps_of_node) {
627  HashTable< NodeId, bool >& hash = chk[elt.first];
628 
629  for (const auto cell : elt.second) {
630  if (!hash.exists(cell)) {
631  std::cerr << "check mps of nodes" << std::endl
632  << __T_mpd << std::endl
633  << __mps_of_node << std::endl;
634  OK = false;
635  }
636 
637  hash[cell] = true;
638  }
639  }
640 
641  for (const auto& elt : chk)
642  for (const auto& elt2 : elt.second)
643  if (!elt2.second) {
644  std::cerr << "check mps of nodes2" << std::endl
645  << __T_mpd << std::endl
646  << __mps_of_node << std::endl;
647  OK = false;
648  }
649 
650  if (!OK) return false;
651  }
652 
653  // check that the junction tree and the T_mpd are junction trees
654  {
655  if (!__junction_tree.isJoinTree()) {
656  std::cerr << "check join tree __junction_tree" << std::endl
657  << __junction_tree << std::endl;
658  return false;
659  }
660 
661  if (!__T_mpd.isJoinTree()) {
662  std::cerr << "check join tree __T_mpd" << std::endl
663  << __T_mpd << std::endl;
664  return false;
665  }
666  }
667 
668  // check elimination sequences
669  {
670  eliminationOrder();
671 
672  if (__elimination_order.size() != __graph.size()) {
673  std::cerr << "check elimination order" << std::endl
674  << __elimination_order << std::endl;
675  return false;
676  }
677 
678  NodeSet nodes;
679 
680  for (const auto node : __graph.nodes()) {
681  if (nodes.exists(node)) {
682  std::cerr << "check elimination order" << std::endl
683  << __elimination_order << std::endl;
684  return false;
685  } else
686  nodes.insert(node);
687  }
688 
689  if (nodes.size() != __graph.size()) {
690  std::cerr << "check elimination order" << std::endl
691  << __elimination_order << std::endl;
692  return false;
693  }
694 
695  if (__reverse_elimination_order.size() != __graph.size()) {
696  std::cerr << "check reverse elimination order" << std::endl
697  << __reverse_elimination_order << std::endl;
698  return false;
699  }
700 
701  for (const auto node : __graph.nodes()) {
702  if (!__reverse_elimination_order.exists(node)) {
703  std::cerr << "check reverse elimination order" << std::endl
704  << __reverse_elimination_order << std::endl;
705  return false;
706  }
707  }
708  }
709 
710  // check created junction tree cliques
711  {
712  createdJunctionTreeCliques();
713 
714  if (__created_JT_cliques.size() != __graph.size()) {
715  std::cerr << "check creating JT cliques" << std::endl
716  << __created_JT_cliques << std::endl;
717  return false;
718  }
719 
720  for (const auto node : __graph.nodes()) {
721  if (!__created_JT_cliques.exists(node)
722  || !__junction_tree.existsNode(__created_JT_cliques[node])) {
723  std::cerr << "check created JT cliques" << std::endl
724  << __created_JT_cliques << std::endl;
725  return false;
726  }
727  }
728  }
729 
730  return true;
731  }
732 
734 
736  NodeId Mx,
737  NodeId Mfrom,
738  UndiGraph& theGraph,
739  std::vector< Edge >& notAffectedneighbourCliques,
740  HashTable< NodeId, bool >& cliques_affected) {
741  // mark the clique so that we won't try to update it several times
742  cliques_affected[Mx] = false;
743 
744  // get the nodes that are concerned by the triangulation update
745  for (const auto node : __junction_tree.clique(Mx))
746  if (!theGraph.exists(node)) theGraph.addNodeWithId(node);
747 
748  // go on with the neighbour cliques in the junction tree
749  for (const auto othernode : __junction_tree.neighbours(Mx))
750  if (othernode != Mfrom) {
751  if (cliques_affected.exists(othernode)) {
752  __setUpConnectedTriangulation(othernode,
753  Mx,
754  theGraph,
755  notAffectedneighbourCliques,
756  cliques_affected);
757  } else {
758  // indicate that we have a clique not affected that is adjacent
759  // to an affected one
760  notAffectedneighbourCliques.push_back(Edge(othernode, Mx));
761  }
762  }
763  }
764 
766 
768  NodeProperty< bool >& all_cliques_affected,
769  NodeSet& new_nodes_in_junction_tree) {
770  // a temporary subgraph in which we actually perform the triangulation
771  UndiGraph tmp_graph;
772 
773  // for each triangulation, we will keep track of the cliques of the
774  // junction tree that are not affected by the triangulation but that are
775  // adjacent to cliques affected. This will enable us to connect easily the
776  // newly created cliques with the old ones.
777  std::vector< Edge > notAffectedneighbourCliques;
778 
779  // parse all the affected MPS and get the corresponding cliques
780 
781  for (const auto& elt : __mps_affected)
782  if (elt.second) {
783  // get the cliques contained in this MPS
784  const std::vector< NodeId >& cliques = __cliques_of_mps[elt.first];
785 
786  for (unsigned int i = 0; i < cliques.size(); ++i)
787  all_cliques_affected.insert(cliques[i], true);
788  }
789 
790  // for each connected set of cliques involved in the triangulations
791  // perform a new triangulation and update the max prime subgraph tree
792  for (const auto& elt : all_cliques_affected) {
793  if (elt.second) {
794  // set up the connected subgraph that need be retriangulated and the
795  // cliques that are affected by this triangulation
796  tmp_graph.clear();
797  notAffectedneighbourCliques.clear();
798  __setUpConnectedTriangulation(elt.first,
799  elt.first,
800  tmp_graph,
801  notAffectedneighbourCliques,
802  all_cliques_affected);
803 
804  // insert the edges in tmp_graph
805  for (auto edge : __graph.edges()) {
806  try {
807  tmp_graph.addEdge(edge.first(), edge.second());
808  } catch (Exception&) {} // both extremities must be in tmp_graph
809  }
810 
811  // remove from the mps_of_node table the affected mps containing the
812  // node
813  // for ( UndiGraph::NodeIterator iter_node =
814  // tmp_graph.beginNodes();iter_node
815  // != tmp_graph.endNodes(); ++iter_node ) {
816  for (const auto node : tmp_graph.nodes()) {
817  List< NodeId >& mps = __mps_of_node[node];
818 
820  __mps_affected.beginSafe(); // safe iterator needed here
821  iter_mps != __mps_affected.endSafe();
822  ++iter_mps) {
823  if (iter_mps.val()) mps.eraseByVal(iter_mps.key());
824  }
825  }
826 
827  // now tmp_graph contains the graph that should be triangulated.
828  // so triangulate it and get its junction tree
829  __triangulation->setGraph(&tmp_graph, &__domain_sizes);
830 
831  const CliqueGraph& tmp_junction_tree = __triangulation->junctionTree();
832 
833  // now, update the global junction tree
834  // first add the nodes of tmp_junction_tree to __junction_tree
835  // to do so, store the translations of the node ids of tmp_junction_tree
836  // into the node ids of __junction_tree
837  NodeProperty< NodeId > tmp2global_junction_tree(tmp_junction_tree.size());
838 
839  for (const auto cliq : tmp_junction_tree.nodes()) {
840  // get new ids for the nodes of tmp_junction_tree. These should be
841  // greater than or equal to __junction_tree.bound () so that we can
842  // use the max_old_id defined at the beginning of the method.
843  NodeId new_id = __junction_tree.addNode(tmp_junction_tree.clique(cliq));
844  // translate the id of the temprary JT into an id of the global JT
845  tmp2global_junction_tree.insert(cliq, new_id);
846  new_nodes_in_junction_tree.insert(new_id);
847  }
848 
849  // and add the edges of tmp_junction_tree to __junction_tree
850  for (const auto edge : tmp_junction_tree.edges())
851  __junction_tree.addEdge(tmp2global_junction_tree[edge.first()],
852  tmp2global_junction_tree[edge.second()]);
853 
854  // second get the edges in __junction_tree that have an extremal clique
855  // R
856  // in the affected clique set and the other one S not in the affected
857  // set
858  // and see which new node V in the __junction_tree should be connected
859  // to S. The running intersection property guarrantees that the clique
860  // in
861  // the tmp_junction_tree that results from the elimination (during the
862  // triangulation process) of the first eliminated node in the separator
863  // between R and S is an admissible candidate
864  for (unsigned int i = 0; i < notAffectedneighbourCliques.size(); ++i) {
865  // check that the separator is not empty. If this is the case, do not
866  // link the new junction tree to the old one
867  const NodeSet& sep =
868  __junction_tree.separator(notAffectedneighbourCliques[i]);
869 
870  if (sep.size() != 0) {
871  // now find the first eliminated node in the separator
872  Size __elim_order = tmp_graph.bound() + 1;
873  NodeId elim_node = 0;
874 
875  for (const auto id : sep) {
876  Size new_order = __triangulation->eliminationOrder(id);
877 
878  if (new_order < __elim_order) {
879  __elim_order = new_order;
880  elim_node = id;
881  }
882  }
883 
884  // find the __junction_tree clique corresponding to the elimination
885  // of
886  // elim_node and insert an edge between this clique and that which
887  // was
888  // not affected
889  NodeId& to_connect =
890  tmp2global_junction_tree[__triangulation->createdJunctionTreeClique(
891  elim_node)];
892 
893  NodeId not_affected =
894  all_cliques_affected.exists(notAffectedneighbourCliques[i].first())
895  ? notAffectedneighbourCliques[i].second()
896  : notAffectedneighbourCliques[i].first();
897 
898  __junction_tree.addEdge(not_affected, to_connect);
899 
900  if (!new_nodes_in_junction_tree.contains(to_connect)) {
901  __T_mpd.addEdge(__mps_of_clique[to_connect],
902  __mps_of_clique[not_affected]);
903  }
904 
905  // check that the separator created by the new edge is not equal to
906  // to_connect. If this is the case, then to_connect is included in
907  // not_affected and, hence, should be removed from the graph
908  if (__junction_tree.separator(not_affected, to_connect).size()
909  == __junction_tree.clique(to_connect).size()) {
910  __junction_tree.eraseEdge(Edge(not_affected, to_connect));
911 
912  for (const auto neighbour : __junction_tree.neighbours(to_connect)) {
913  __junction_tree.addEdge(neighbour, not_affected);
914 
915  if (!new_nodes_in_junction_tree.contains(neighbour))
916  __T_mpd.addEdge(__mps_of_clique[neighbour],
917  __mps_of_clique[not_affected]);
918  }
919 
920  __junction_tree.eraseNode(to_connect);
921 
922  to_connect = not_affected;
923  }
924  }
925  }
926  }
927  }
928 
929  // remove the mps that were affected and update the cliques_of_mps table
930  for (const auto& elt : all_cliques_affected) {
931  __mps_of_clique.erase(elt.first);
932  __junction_tree.eraseNode(elt.first);
933  }
934 
935  for (const auto& elt : __mps_affected)
936  if (elt.second) {
937  __cliques_of_mps.erase(elt.first);
938  __T_mpd.eraseNode(elt.first);
939  }
940  }
941 
943 
945  const NodeId node,
946  const NodeId from,
947  std::vector< std::pair< NodeId, NodeId > >& merged_cliques,
949  const NodeSet& new_nodes_in_junction_tree) const {
950  mark[node] = true;
951 
952  // check the separators on all the adjacent edges of Mx
953  for (const auto other_node : __junction_tree.neighbours(node))
954  if (other_node != from) {
955  const NodeSet& separator =
956  __junction_tree.separator(Edge(other_node, node));
957 
958  // check that the separator between node and other_node is complete
959  bool complete = true;
960 
961  for (auto iter_sep1 = separator.begin();
962  iter_sep1 != separator.end() && complete;
963  ++iter_sep1) {
964  auto iter_sep2 = iter_sep1;
965 
966  for (++iter_sep2; iter_sep2 != separator.end(); ++iter_sep2) {
967  if (!__graph.existsEdge(*iter_sep1, *iter_sep2)) {
968  complete = false;
969  break;
970  }
971  }
972  }
973 
974  // here complete indicates whether the separator is complete or not
975  if (!complete)
976  merged_cliques.push_back(std::pair< NodeId, NodeId >(other_node, node));
977 
978  if (new_nodes_in_junction_tree.contains(other_node))
979  __computeMaxPrimeMergings(
980  other_node, node, merged_cliques, mark, new_nodes_in_junction_tree);
981  }
982  }
983 
985 
987  NodeProperty< bool >& all_cliques_affected,
988  const NodeSet& new_nodes_in_junction_tree) {
989  // the maximal prime subgraph join tree is created by aggregation of some
990  // cliques. More precisely, when the separator between 2 cliques is not
991  // complete in the original graph, then the two cliques must be merged.
992 
993  // Create a hashtable indicating which clique has been absorbed by some
994  // other
995  // clique. Keys are the cliques absorbed, and values are the cliques that
996  // absorb them.
997  HashTable< NodeId, NodeId > T_mpd_cliques(all_cliques_affected.size());
998 
999  for (const auto clik : __junction_tree.nodes())
1000  if (new_nodes_in_junction_tree.contains(clik))
1001  T_mpd_cliques.insert(clik, clik);
1002 
1003  // parse all the separators of the junction tree and test those that are not
1004  // complete in the original graph
1005  std::vector< std::pair< NodeId, NodeId > > merged_cliques;
1006 
1007  HashTable< NodeId, bool > mark = T_mpd_cliques.map(false);
1008 
1009  for (const auto& elt : mark)
1010  if (!elt.second)
1011  __computeMaxPrimeMergings(
1012  elt.first, elt.first, merged_cliques, mark, new_nodes_in_junction_tree);
1013 
1014  // compute the transitive closure of merged_cliques. This one will contain
1015  // pairs (X,Y) indicating that clique X must be merged with clique Y.
1016  // Actually clique X will be inserted into clique Y.
1017  for (unsigned int i = 0; i < merged_cliques.size(); ++i) {
1018  if (T_mpd_cliques.exists(merged_cliques[i].second))
1019  T_mpd_cliques[merged_cliques[i].first] =
1020  T_mpd_cliques[merged_cliques[i].second];
1021  else
1022  T_mpd_cliques[merged_cliques[i].first] =
1023  __mps_of_clique[merged_cliques[i].second];
1024  }
1025 
1026  // now we can create the max prime junction tree.
1027 
1028  // create a map translating the cliques' ids in the junction tree into
1029  // cliques' id in the _T_mpd tree
1030  NodeProperty< NodeId > clique2MPS(T_mpd_cliques.size());
1031 
1032  // First, create the new cliques and create the corresponding
1033  // cliques_of_mps entries
1034  for (const auto& elt : T_mpd_cliques)
1035  if (elt.first == elt.second) {
1036  NodeId newId = __T_mpd.addNode(__junction_tree.clique(elt.second));
1037  clique2MPS.insert(elt.second, newId);
1038  std::vector< NodeId >& vect_of_cliques =
1039  __cliques_of_mps.insert(newId, std::vector< NodeId >()).second;
1040  vect_of_cliques.push_back(elt.second);
1041  }
1042 
1043  // add to the cliques previously created the nodes of the cliques that were
1044  // merged into them and update the cliques_of_mps
1045  for (const auto& elt : T_mpd_cliques)
1046  if ((elt.first != elt.second)
1047  && (new_nodes_in_junction_tree.contains(elt.second))) {
1048  const NodeId idMPS = clique2MPS[elt.second];
1049 
1050  for (const auto node : __junction_tree.clique(elt.first)) {
1051  try {
1052  __T_mpd.addToClique(idMPS, node);
1053  } catch (DuplicateElement&) {}
1054  }
1055 
1056  __cliques_of_mps[idMPS].push_back(elt.first);
1057  }
1058 
1059  // update the mps_of_node and the mps_of_clique
1060  for (const auto& elt : T_mpd_cliques) {
1061  const NodeId idMPS = clique2MPS[elt.second];
1062  __mps_of_clique.insert(elt.first, idMPS);
1063 
1064  if (elt.first == elt.second)
1065  for (const auto node : __T_mpd.clique(idMPS))
1066  __mps_of_node[node].insert(idMPS);
1067  }
1068 
1069  // add the edges to the max prime subgraph tree
1070  for (const auto& elt : T_mpd_cliques) {
1071  NodeId clique = clique2MPS[elt.second];
1072 
1073  for (const auto othernode : __junction_tree.neighbours(elt.first))
1074  if (T_mpd_cliques.exists(othernode)) {
1075  // here iter is linked to another node that has been created during
1076  // the triangulation
1077  NodeId otherClique = clique2MPS[T_mpd_cliques[othernode]];
1078 
1079  // avoid adding the same edge several times
1080  if (clique > otherClique) { __T_mpd.addEdge(clique, otherClique); }
1081  } else {
1082  __T_mpd.addEdge(clique, __mps_of_clique[othernode]);
1083  }
1084  }
1085  }
1086 
1088 
1090  if (!__require_update) return;
1091  // the set of all the cliques that should be affected by the different
1092  // triangulations we will perform (one by connected component)
1093  NodeProperty< bool > all_cliques_affected(__junction_tree.size());
1094 
1095  // we need to keep track of the new node ids that will be inserted
1096  // into __junction_tree. A priori, these should be equal to the ids
1097  // inserted into tmp2global_junction_tree. But, sometimes, some new nodes
1098  // are included into old nodes and, in this case, the translation in
1099  // tmp2global_junction_tree indicates the the new node inserted corresponds
1100  // to an old node. Here we wish to know this additional information
1101  NodeSet new_nodes_in_junction_tree;
1102 
1103  __updateJunctionTree(all_cliques_affected, new_nodes_in_junction_tree);
1104 
1105  // now update the T_mpd so that it be coherent with the junction tree
1106  __updateMaxPrimeSubgraph(all_cliques_affected, new_nodes_in_junction_tree);
1107 
1108  // reset the MPS that are affected
1109  __mps_affected.clear();
1110 
1111  for (const auto node : __T_mpd.nodes())
1112  __mps_affected.insert(node, false);
1113 
1114  // remove all the structures used by the triangulation algorithm
1115  __triangulation->clear();
1116 
1117  __require_update = false;
1118  }
1119 
1121 
1123  __graph.clear();
1124  __domain_sizes.clear();
1125  __junction_tree.clear();
1126  __T_mpd.clear();
1127  __mps_of_node.clear();
1128  __cliques_of_mps.clear();
1129  __mps_of_clique.clear();
1130  __mps_affected.clear();
1131  __triangulation->clear();
1132  __require_update = false;
1133  __require_elimination_order = false;
1134  __elimination_order.clear();
1135  __reverse_elimination_order.clear();
1136  __require_created_JT_cliques = false;
1137  __created_JT_cliques.clear();
1138  }
1139 
1141 
1143  const NodeId clique, const NodeId from, NodeProperty< bool >& examined) {
1144  // apply collect to all the neighbours except from
1145  for (const auto otherclique : __junction_tree.neighbours(clique))
1146  if (otherclique != from) __collectJTCliques(otherclique, clique, examined);
1147 
1148  // get the nodes that belong to clique and not to from
1149  examined[clique] = true;
1150 
1151  const NodeSet& cliquenodes = __junction_tree.clique(clique);
1152 
1153  if (from != clique) {
1154  const NodeSet& separator = __junction_tree.separator(clique, from);
1155 
1156  for (const auto cli : cliquenodes)
1157  if (!separator.contains(cli)) __created_JT_cliques.insert(cli, clique);
1158  } else {
1159  for (const auto cli : cliquenodes)
1160  __created_JT_cliques.insert(cli, clique);
1161  }
1162  }
1163 
1167  const NodeProperty< NodeId >&
1169  // check if we already computed the containing cliques
1170  if (!__require_created_JT_cliques) return __created_JT_cliques;
1171 
1172  // we first we compute the junction tree
1173  updateTriangulation();
1174 
1175  __created_JT_cliques.clear();
1176 
1177  __require_created_JT_cliques = false;
1178 
1179  if (__junction_tree.size() == 0) { return __created_JT_cliques; }
1180 
1181  // now we can use a collect algorithm to get the containing cliques
1182  NodeProperty< bool > examined = __junction_tree.nodesProperty< bool >(false);
1183 
1184  for (const auto& elt : examined)
1185  if (!elt.second) __collectJTCliques(elt.first, elt.first, examined);
1186 
1187  return __created_JT_cliques;
1188  }
1189 
1191 
1193  createdJunctionTreeCliques();
1194  return __created_JT_cliques[id];
1195  }
1196 
1201  // get the created junction tree clique and get its MPS
1202  return __mps_of_clique[createdJunctionTreeClique(id)];
1203  }
1204 
1206 
1207  void IncrementalTriangulation::setGraph(const UndiGraph* graph,
1208  const NodeProperty< Size >* dom_sizes) {
1209  // check that both the graph and the domain sizes are different from nullptr
1210  // or else that both are equal to nullptr
1211  if (((graph != nullptr) && (dom_sizes == nullptr))
1212  || ((graph == nullptr) && (dom_sizes != nullptr))) {
1213  GUM_ERROR(GraphError,
1214  "one of the graph or the set of domain sizes "
1215  "is a null pointer.");
1216  }
1217 
1218  // remove the current graph
1219  clear();
1220 
1221  // copy the graph passed in arent and update the structures
1222  // containing the informations useful for the triangulation
1223  if (graph != nullptr) {
1224  for (const auto node : *graph)
1225  addNode(node, (*dom_sizes)[node]);
1226 
1227  for (const auto& edge : graph->edges())
1228  addEdge(edge.first(), edge.second());
1229  }
1230  }
1231 
1233 
1235  const NodeId node,
1236  const NodeId from,
1237  NodeProperty< bool >& examined,
1238  Idx& index) {
1239  // apply collect to all the neighbours except from
1240  for (const auto othernode : __junction_tree.neighbours(node))
1241  if (othernode != from)
1242  __collectEliminationOrder(othernode, node, examined, index);
1243 
1244  // get the nodes that belong to node and not to from
1245  examined[node] = true;
1246 
1247  const NodeSet& clique = __junction_tree.clique(node);
1248 
1249  if (from != node) {
1250  const NodeSet& separator = __junction_tree.separator(node, from);
1251 
1252  for (const auto cli : clique) {
1253  if (!separator.contains(cli)) {
1254  __elimination_order[index] = cli;
1255  __reverse_elimination_order.insert(cli, index);
1256  ++index;
1257  }
1258  }
1259  } else {
1260  for (const auto cli : clique) {
1261  __elimination_order[index] = cli;
1262  __reverse_elimination_order.insert(cli, index);
1263  ++index;
1264  }
1265  }
1266  }
1267 
1269 
1270  const std::vector< NodeId >& IncrementalTriangulation::eliminationOrder() {
1271  // check if we already computed the elimination order
1272  if (!__require_elimination_order) return __elimination_order;
1273 
1274  // to compute the elimination order, we first we compute the junction tree
1275  updateTriangulation();
1276 
1277  __elimination_order.resize(__graph.size());
1278 
1279  __reverse_elimination_order.clear();
1280 
1281  __require_elimination_order = false;
1282 
1283  if (__junction_tree.size() == Size(0)) { return __elimination_order; }
1284 
1285  // now we can use a collect algorithm to get the elimination order
1286  Idx index = Idx(0);
1287 
1288  NodeProperty< bool > examined = __junction_tree.nodesProperty< bool >(false);
1289 
1290  for (const auto& elt : examined)
1291  if (!elt.second)
1292  __collectEliminationOrder(elt.first, elt.first, examined, index);
1293 
1294  return __elimination_order;
1295  }
1296 
1301  if (!__graph.existsNode(node)) {
1302  GUM_ERROR(NotFound, "the node " << node << " does not exist");
1303  }
1304 
1305  // compute the elimination order
1306  eliminationOrder();
1307 
1308  return __reverse_elimination_order[node];
1309  }
1310 
1311 } /* namespace gum */
1312 
1313 #endif /* DOXYGEN_SHOULD_SKIP_THIS */
void __collectJTCliques(const NodeId clique, const NodeId from, NodeProperty< bool > &examined)
a collect algorithm to compute, for each node, one container JT&#39;s clique
void eraseEdge(const Edge &edge)
removes an edge from the graph (the join tree may need a retriangulation)
Inline implementations for computing default triangulations of graphs.
Useful macros for maths.
virtual void clear()
clears the sequence (to prepare, for instance, a new elimination sequence)
~IncrementalTriangulation()
destructor
void __markAffectedMPSsByRemoveLink(const NodeId My, const NodeId Mz, const Edge &edge)
mark the mps affected by the deletion of a given edge
void setGraph(const UndiGraph *theGraph, const NodeProperty< Size > *domain_sizes)
changes the current graph
void eraseNode(const NodeId node)
removes a node from the graph (the join tree may need a triangulation update)
void __updateMaxPrimeSubgraph(NodeProperty< bool > &cliques_affected, const NodeSet &new_nodes_in_junction_tree)
update the max prime subgraph
Set< NodeId > NodeSet
Some typdefs and define for shortcuts ...
virtual IncrementalTriangulation * newFactory() const final
virtual clone constructor
const NodeProperty< NodeId > & createdJunctionTreeCliques()
returns the Ids of the cliques of the junction tree created by the elimination of the nodes ...
IncrementalTriangulation(const UnconstrainedTriangulation &triang_algo, const UndiGraph *theGraph, const NodeProperty< Size > *modal)
constructor
int __markAffectedMPSsByAddLink(const NodeId My, const NodeId Mz, const NodeId X, const NodeId Y)
mark the mps affected by the insertion of a new edge
bool exists(const Key &key) const
Checks whether there exists an element with a given key in the hashtable.
IncrementalTriangulation & operator=(const IncrementalTriangulation &from)
copy operator
Base classes for undirected graphs.
gum is the global namespace for all aGrUM entities
Definition: agrum.h:25
UndiGraph * graph() const noexcept
returns the current graph
bool __check()
checks that the incremental triangulation works properly
void updateTriangulation()
updates the triangulated graph using the modif list
NodeId createdMaxPrimeSubgraph(const NodeId id)
returns the Id of the maximal prime subgraph created by the elimination of a given node during the tr...
Generic class for manipulating lists.
void __setUpConnectedTriangulation(NodeId Mx, NodeId Mfrom, UndiGraph &theGraph, std::vector< Edge > &notAffectedneighborClique, HashTable< NodeId, bool > &cliques_affected)
set-up the connected subgraph that needs be retriangulated
const std::vector< NodeId > & eliminationOrder()
returns an elimination ordering compatible with the triangulated graph
void addNode(const NodeId node, Size modal)
adds a new node to the graph
NodeId createdJunctionTreeClique(const NodeId id)
returns the Id of the clique created by the elimination of a given node during the triangulation proc...
void __updateJunctionTree(NodeProperty< bool > &all_cliques_affected, NodeSet &new_nodes_in_junction_tree)
update the junction tree
void addEdge(const NodeId X, const NodeId Y)
adds a new edge to the graph (the join tree may need a triangulation update)
virtual UnconstrainedEliminationSequenceStrategy * newFactory() const =0
creates a new elimination sequence of the same type as the current object, but this sequence contains...
void __collectEliminationOrder(const NodeId node, const NodeId from, NodeProperty< bool > &examined, Idx &index)
a collect algorithm to compute elimination orderings
Class for computing default triangulations of graphs.
Size Idx
Type for indexes.
Definition: types.h:50
void clear()
sets the graph to the empty graph
std::size_t Size
In aGrUM, hashed values are unsigned long int.
Definition: types.h:45
void __computeMaxPrimeMergings(const NodeId node, const NodeId from, std::vector< std::pair< NodeId, NodeId > > &merged_cliques, NodeProperty< bool > &mark, const NodeSet &new_nodes_in_junction_tree) const
used for computing the junction tree of the maximal prime subgraphs
value_type & insert(const Key &key, const Val &val)
Adds a new element (actually a copy of this element) into the hash table.
Size NodeId
Type for node ids.
Definition: graphElements.h:97
void insert(const Key &k)
Inserts a new element into the set.
Definition: set_tpl.h:610
#define GUM_ERROR(type, msg)
Definition: exceptions.h:52
HashTable< Key, Mount, OtherAlloc > map(Mount(*f)(Val), Size size=Size(0), bool resize_pol=HashTableConst::default_resize_policy, bool key_uniqueness_pol=HashTableConst::default_uniqueness_policy) const
Transforms a hashtable of vals into a hashtable of mountains.
virtual IncrementalTriangulation * copyFactory() const final
virtual copy constructor