aGrUM 2.3.2
a C++ library for (probabilistic) graphical models
incrementalTriangulation.cpp
Go to the documentation of this file.
1/****************************************************************************
2 * This file is part of the aGrUM/pyAgrum library. *
3 * *
4 * Copyright (c) 2005-2025 by *
5 * - Pierre-Henri WUILLEMIN(_at_LIP6) *
6 * - Christophe GONZALES(_at_AMU) *
7 * *
8 * The aGrUM/pyAgrum library is free software; you can redistribute it *
9 * and/or modify it under the terms of either : *
10 * *
11 * - the GNU Lesser General Public License as published by *
12 * the Free Software Foundation, either version 3 of the License, *
13 * or (at your option) any later version, *
14 * - the MIT license (MIT), *
15 * - or both in dual license, as here. *
16 * *
17 * (see https://agrum.gitlab.io/articles/dual-licenses-lgplv3mit.html) *
18 * *
19 * This aGrUM/pyAgrum library is distributed in the hope that it will be *
20 * useful, but WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
21 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES MERCHANTABILITY or FITNESS *
22 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
23 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
24 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
25 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
26 * OTHER DEALINGS IN THE SOFTWARE. *
27 * *
28 * See LICENCES for more details. *
29 * *
30 * SPDX-FileCopyrightText: Copyright 2005-2025 *
31 * - Pierre-Henri WUILLEMIN(_at_LIP6) *
32 * - Christophe GONZALES(_at_AMU) *
33 * SPDX-License-Identifier: LGPL-3.0-or-later OR MIT *
34 * *
35 * Contact : info_at_agrum_dot_org *
36 * homepage : http://agrum.gitlab.io *
37 * gitlab : https://gitlab.com/agrumery/agrum *
38 * *
39 ****************************************************************************/
40
41
47
48#include <limits>
49#include <utility>
50
51#include <agrum/agrum.h>
52
56
58
59#ifdef GUM_NO_INLINE
61#endif // GUM_NO_INLINE
62
63#ifndef DOXYGEN_SHOULD_SKIP_THIS
64
65namespace gum {
66
69 const UndiGraph* theGraph,
70 const NodeProperty< Size >* domsizes) :
71 Triangulation(domsizes), _triangulation_(triang_algo.newFactory()) {
72 GUM_CONSTRUCTOR(IncrementalTriangulation);
73
74 // reset the triangulation algorithm => it starts with an empty graph
75 _triangulation_->clear();
76
77 // copy the graph passed in argument and update the structures
78 // containing the informations useful for the triangulation
79
80 for (const auto node: *theGraph)
81 addNode(node, (*domsizes)[node]);
82
83 // insert all the edges of the graph into the structure. This will
84 // implicitly update the "require_update" field
85 for (const auto& edge: theGraph->edges())
86 addEdge(edge.first(), edge.second());
87 }
88
90 IncrementalTriangulation::IncrementalTriangulation(
91 const UnconstrainedTriangulation& triang_algo) : _triangulation_(triang_algo.newFactory()) {
92 GUM_CONSTRUCTOR(IncrementalTriangulation);
93
94 // reset the triangulation algorithm => it starts with an empty graph
95 _triangulation_->clear();
96 }
97
99 IncrementalTriangulation::IncrementalTriangulation(const IncrementalTriangulation& from) :
100 Triangulation(from), _graph_(from._graph_), _junction_tree_(from._junction_tree_),
101 _T_mpd_(from._T_mpd_), _mps_of_node_(from._mps_of_node_),
102 _cliques_of_mps_(from._cliques_of_mps_), _mps_of_clique_(from._mps_of_clique_),
103 _mps_affected_(from._mps_affected_), _triangulation_(from._triangulation_->newFactory()),
104 _require_update_(from._require_update_),
105 _require_elimination_order_(from._require_elimination_order_),
106 _elimination_order_(from._elimination_order_),
107 _reverse_elimination_order_(from._reverse_elimination_order_),
108 _require_created_JT_cliques_(from._require_created_JT_cliques_),
109 _created_JT_cliques_(from._created_JT_cliques_) {
110 GUM_CONS_CPY(IncrementalTriangulation);
111
112 _domain_sizes_ = from._domain_sizes_;
113 }
114
116 IncrementalTriangulation::~IncrementalTriangulation() {
117 GUM_DESTRUCTOR(IncrementalTriangulation);
118
119 // remove things that were allocated within the current class
120 delete _triangulation_;
121 }
122
124 IncrementalTriangulation* IncrementalTriangulation::newFactory() const {
125 return new IncrementalTriangulation(*_triangulation_);
126 }
127
129 IncrementalTriangulation* IncrementalTriangulation::copyFactory() const {
130 return new IncrementalTriangulation(*this);
131 }
132
134 IncrementalTriangulation&
135 IncrementalTriangulation::operator=(const IncrementalTriangulation& from) {
136 // avoid self assignment
137 if (this != &from) {
138 GUM_OP_CPY(IncrementalTriangulation)
139
140 // copy all the structures stored in "from"
141 _graph_ = from._graph_;
142 _domain_sizes_ = from._domain_sizes_;
143 _junction_tree_ = from._junction_tree_;
144 _T_mpd_ = from._T_mpd_;
145 _mps_of_node_ = from._mps_of_node_;
146 _cliques_of_mps_ = from._cliques_of_mps_;
147 _mps_of_clique_ = from._mps_of_clique_;
148 _mps_affected_ = from._mps_affected_;
149 _require_update_ = from._require_update_;
150 _require_elimination_order_ = from._require_elimination_order_;
151 _elimination_order_ = from._elimination_order_;
152 _reverse_elimination_order_ = from._reverse_elimination_order_;
153 _require_created_JT_cliques_ = from._require_created_JT_cliques_;
154 _created_JT_cliques_ = from._created_JT_cliques_;
155
156 // just in case we changed the triangulation algorithm, we remove it
157 // and create it again
158 delete _triangulation_;
159 _triangulation_ = from._triangulation_->newFactory();
160 }
161
162 return *this;
163 }
164
166 void IncrementalTriangulation::addNode(const NodeId node, Size modal) {
167 // check if the node already exists
168 if (_graph_.existsNode(node)) return;
169
170 // add the new node to the graph
171 _graph_.addNodeWithId(node);
172 _domain_sizes_.insert(node, modal);
173
174 // add a new clique to T_mpd and the junction tree
175 NodeSet clique_nodes(2);
176 clique_nodes.insert(node);
177
178 NodeId MPS = _T_mpd_.addNode(clique_nodes);
179 NodeId new_clique = _junction_tree_.addNode(clique_nodes);
180
181 // indicate in which MPS node belongs
182 List< NodeId >& list_of_mps = _mps_of_node_.insert(node, List< NodeId >()).second;
183 list_of_mps.insert(MPS);
184
185 // indicate in which MPS the clique added to the junction tree belongs
186 std::vector< NodeId >& cliquesMPS
187 = _cliques_of_mps_.insert(MPS, std::vector< NodeId >()).second;
188
189 cliquesMPS.push_back(new_clique);
190 _mps_of_clique_.insert(new_clique, MPS);
191
192 // indicate that the new MPS should not be affected by a triangulation
193 _mps_affected_.insert(MPS, false);
194
195 // insert the node into the elimination order sequence
196 _elimination_order_.push_back(node);
197
198 if (!_reverse_elimination_order_.exists(node))
199 _reverse_elimination_order_.insert(node, Size(_elimination_order_.size()));
200
201 if (!_created_JT_cliques_.exists(node)) _created_JT_cliques_.insert(node, new_clique);
202 }
203
205
206 void IncrementalTriangulation::_markAffectedMPSsByRemoveLink_(const NodeId My,
207 const NodeId Mz,
208 const Edge& edge) {
209 // mark the MPS so that it will be retriangulated
210 _mps_affected_[My] = true;
211
212 // mark all the neighbour MPS that contain edge
213 for (const auto nei: _T_mpd_.neighbours(My))
214 if (nei != Mz) {
215 const NodeSet& Syk = _T_mpd_.separator(Edge(nei, My));
216
217 if (Syk.contains(edge.first()) && Syk.contains(edge.second()))
218 _markAffectedMPSsByRemoveLink_(nei, My, edge);
219 }
220 }
221
223
224 void IncrementalTriangulation::eraseEdge(const Edge& edge) {
225 // check that the edge exist
226 if (!_graph_.existsEdge(edge)) return;
227
228 // find a MPS containing the edge (X,Y)
229 const NodeId X = edge.first();
230 const NodeId Y = edge.second();
231
232 const List< NodeId >& mps1 = _mps_of_node_[X];
233 const List< NodeId >& mps2 = _mps_of_node_[Y];
234
235 NodeId Mx = mps1[0];
236
237 if (mps1.size() <= mps2.size()) {
238 for (const auto node: mps1)
239 if (_T_mpd_.clique(node).contains(Y)) {
240 Mx = node;
241 break;
242 }
243 } else {
244 for (const auto node: mps2)
245 if (_T_mpd_.clique(node).contains(X)) {
246 Mx = node;
247 break;
248 }
249 }
250
251 // mark the MPS that need be updated
252 _markAffectedMPSsByRemoveLink_(Mx, Mx, edge);
253
254 _require_update_ = true;
255
256 _require_elimination_order_ = true;
257
258 _require_created_JT_cliques_ = true;
259
260 // remove the edge (X,Y) from the graph
261 _graph_.eraseEdge(edge);
262 }
263
266
267 void IncrementalTriangulation::eraseNode(const NodeId X) {
268 // check if the node exists
269 if (!_graph_.existsNode(X)) return;
270
271 // remove all the edges adjacent to the node
272 {
273 const NodeSet& neighbours = _graph_.neighbours(X);
274
275 for (auto neighbour_edge = neighbours.beginSafe(); // safe iterator needed here
276 neighbour_edge != neighbours.endSafe();
277 ++neighbour_edge) {
278 eraseEdge(Edge(*neighbour_edge, X));
279 }
280 }
281
282 auto& MPS_of_X = _mps_of_node_[X];
283
284 // remove X from the MPS containing X
285 for (const auto node: MPS_of_X) {
286 _T_mpd_.eraseFromClique(node, X);
287
288 // if the intersection between *iter and one of its neighbour is empty,
289 // remove the edge linking them
290 auto& neighbours = _T_mpd_.neighbours(node);
291
292 for (auto it_neighbour = neighbours.beginSafe(); it_neighbour != neighbours.endSafe();
293 ++it_neighbour) { // safe iterator needed here
294 Edge neigh(*it_neighbour, node);
295
296 if (_T_mpd_.separator(neigh).size() == 0) _T_mpd_.eraseEdge(neigh);
297 }
298 }
299
300 // remove X from the cliques containing X
301 for (const auto clique: MPS_of_X) {
302 const std::vector< NodeId >& cliques_of_X = _cliques_of_mps_[clique];
303
304 for (unsigned int i = 0; i < cliques_of_X.size(); ++i) {
305 _junction_tree_.eraseFromClique(cliques_of_X[i], X);
306
307 // if the intersection between clique and one of its neighbour is empty,
308 // remove the edge linking them only if, in addition, there is no
309 // edge in _graph_ between a node of clique and a node in the neighbour
310 auto& neighbours = _junction_tree_.neighbours(cliques_of_X[i]);
311
312 for (auto it_neighbour = neighbours.beginSafe(); it_neighbour != neighbours.endSafe();
313 ++it_neighbour) { // safe iterator needed here
314 Edge neigh(*it_neighbour, cliques_of_X[i]);
315
316 if (_junction_tree_.separator(neigh).size() == 0) {
317 // try to see if there is an edge between the nodes of one extremity
318 // of *neighbour and those of the other extremity
319 bool hasCommonEdge = false;
320
321 for (const auto node1: _junction_tree_.clique(neigh.first()))
322 for (const auto node2: _junction_tree_.clique(neigh.second()))
323 if (_graph_.existsEdge(node1, node2)) {
324 hasCommonEdge = true;
325 break;
326 }
327
328 if (!hasCommonEdge) { _junction_tree_.eraseEdge(neigh); }
329 }
330 }
331 }
332 }
333
334 // if the MPS containing X is empty, then remove it, as well as the
335 // corresponding clique in the junction tree (which also only contains X)
336 if ((MPS_of_X.size() == 1) && (_T_mpd_.clique(MPS_of_X[0]).size() == 0)) {
337 _junction_tree_.eraseNode(_cliques_of_mps_[MPS_of_X[0]][0]);
338 _T_mpd_.eraseNode(MPS_of_X[0]);
339 _mps_of_clique_.erase(_cliques_of_mps_[MPS_of_X[0]][0]);
340 _cliques_of_mps_.erase(MPS_of_X[0]);
341 _mps_affected_.erase(MPS_of_X[0]);
342 }
343
344 _mps_of_node_.erase(X);
345
346 // update the elimination orders
347
348 if (!_require_update_) {
349 for (Idx i = _reverse_elimination_order_[X] + 1; i < _reverse_elimination_order_.size(); ++i)
350 _elimination_order_[i - 1] = _elimination_order_[i];
351
352 _elimination_order_.pop_back();
353
354 _reverse_elimination_order_.erase(X);
355
356 _created_JT_cliques_.erase(X);
357 }
358
359 // remove X completely from the graph
360 _graph_.eraseNode(X);
361
362 _domain_sizes_.erase(X);
363 }
364
366
367 int IncrementalTriangulation::_markAffectedMPSsByAddLink_(const NodeId Mx,
368 const NodeId Mz,
369 const NodeId X,
370 const NodeId Y) {
371 // check if Mx contains Y. In this case, mark Mx and return 1 to indicate
372 // that
373 // Y has been found or 2 to indicate that Y has been found and that the
374 // nearest
375 // MPS containing X has been marked
376 const NodeSet& cliqueMX = _T_mpd_.clique(Mx);
377
378 if (cliqueMX.contains(Y)) {
379 _mps_affected_[Mx] = true;
380
381 if (cliqueMX.contains(X)) return 2;
382
383 return 1;
384 }
385
386 // parse Mx's neighbours until we find Y
387 for (const auto other_node: _T_mpd_.neighbours(Mx))
388 if (other_node != Mz) {
389 int neighbourStatus = _markAffectedMPSsByAddLink_(other_node, Mx, X, Y);
390
391 if (neighbourStatus == 2) return 2;
392 else if (neighbourStatus == 1) {
393 _mps_affected_[Mx] = true;
394
395 if (cliqueMX.contains(X)) return 2;
396
397 return 1;
398 }
399 }
400
401 // indicate that X was not found
402 return 0;
403 }
404
407 void IncrementalTriangulation::addEdge(const NodeId X, const NodeId Y) {
408 // check that the edge exist
409 if ((X == Y) || !_graph_.existsNode(X) || !_graph_.existsNode(Y)
410 || _graph_.existsEdge(Edge(X, Y)))
411 return;
412
413 // add the edge to the graph
414 _graph_.addEdge(X, Y);
415
416 // take any MPS containing X and search its tree to find Y
417 NodeId& mps_X = _mps_of_node_[X][0];
418
419 int found = _markAffectedMPSsByAddLink_(mps_X, mps_X, X, Y);
420
421 if (found == 0) {
422 // the mps containing X do not belong to the same tree as those containing
423 // Y
424 NodeId& mps_Y = _mps_of_node_[Y][0];
425
426 // find a clique in mps_X containing X and another in mps_Y containing Y
427 // and add a clique XY to the junction tree linked to the cliques found
428 // in mps_X and mps_Y
429 const std::vector< NodeId >& cliques_X = _cliques_of_mps_[mps_X];
430 const std::vector< NodeId >& cliques_Y = _cliques_of_mps_[mps_Y];
431 NodeId c_X = 0, c_Y = 0;
432
433 for (unsigned int i = 0; i < cliques_X.size(); ++i) {
434 if (_junction_tree_.clique(cliques_X[i]).contains(X)) {
435 c_X = cliques_X[i];
436 break;
437 }
438 }
439
440 for (unsigned int i = 0; i < cliques_Y.size(); ++i) {
441 if (_junction_tree_.clique(cliques_Y[i]).contains(Y)) {
442 c_Y = cliques_Y[i];
443 break;
444 }
445 }
446
447 // link c_Y and c_X through a new node containing XY
448 NodeSet nodes(2);
449
450 nodes.insert(X);
451
452 nodes.insert(Y);
453
454 NodeId newNode = _junction_tree_.addNode(nodes);
455
456 _junction_tree_.addEdge(newNode, c_X);
457 _junction_tree_.addEdge(newNode, c_Y);
458
459 NodeId newMPS = _T_mpd_.addNode(nodes);
460
461 _T_mpd_.addEdge(newMPS, mps_X);
462 _T_mpd_.addEdge(newMPS, mps_Y);
463
464 // check that the maximal prime subgraph containing X is not X alone
465 // in this case, remove this max prime subgraph, as well as the
466 // corresponding
467 // clique in the junction tree
468 if (_T_mpd_.clique(mps_X).size() == 1) {
469 _junction_tree_.eraseNode(c_X);
470 _T_mpd_.eraseNode(mps_X);
471 _mps_affected_.erase(mps_X);
472 _mps_of_clique_.erase(c_X);
473 _cliques_of_mps_.erase(mps_X);
474 _created_JT_cliques_[X] = newNode;
475 mps_X = newMPS;
476 } else _mps_of_node_[X].insert(newMPS);
477
478 // do the same thing as above for node Y
479 if (_T_mpd_.clique(mps_Y).size() == 1) {
480 _junction_tree_.eraseNode(c_Y);
481 _T_mpd_.eraseNode(mps_Y);
482 _mps_affected_.erase(mps_Y);
483 _mps_of_clique_.erase(c_Y);
484 _cliques_of_mps_.erase(mps_Y);
485 _created_JT_cliques_[Y] = newNode;
486 mps_Y = newMPS;
487 } else _mps_of_node_[Y].insert(newMPS);
488
489 std::vector< NodeId >& cl = _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
507 bool IncrementalTriangulation::_check_() {
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_.nodesPropertyFromVal< 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_.nodesPropertyFromVal< 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 << _graph_ << std::endl << _T_mpd_ << std::endl;
575 OK = false;
576 }
577
578 if (!OK) return false;
579 }
580
581 // check that the arcs of the graph belong to the MPS tree
582 {
583 std::pair< NodeId, NodeId > thePair;
584 EdgeProperty< bool > edgesProp = _graph_.edgesProperty(false);
585
586 for (const auto cliq: _T_mpd_.nodes()) {
587 const NodeSet& clique = _T_mpd_.clique(cliq);
588
589 for (auto iter2 = clique.begin(); iter2 != clique.end(); ++iter2) {
590 auto iter3 = iter2;
591
592 for (++iter3; iter3 != clique.end(); ++iter3) {
593 thePair.first = std::min(*iter2, *iter3);
594 thePair.second = std::max(*iter2, *iter3);
595
596 if (_graph_.existsEdge(thePair.first, thePair.second))
597 edgesProp[Edge(thePair.first, thePair.second)] = true;
598 }
599 }
600 }
601
602 for (const auto& elt: edgesProp)
603 if (!elt.second) {
604 std::cerr << "check edges" << std::endl << _graph_ << std::endl << _T_mpd_ << std::endl;
605 OK = false;
606 }
607
608 if (!OK) return false;
609 }
610
611 // check the MPS of node
612 {
613 NodeProperty< NodeProperty< bool > > chk;
614
615 for (const auto node: _graph_.nodes())
616 chk.insert(node, HashTable< NodeId, bool >());
617
618 for (const auto cliq: _T_mpd_.nodes())
619 for (auto node: _T_mpd_.clique(cliq))
620 chk[node].insert(cliq, false);
621
622 for (const auto& elt: _mps_of_node_) {
623 HashTable< NodeId, bool >& hash = chk[elt.first];
624
625 for (const auto cell: elt.second) {
626 if (!hash.exists(cell)) {
627 std::cerr << "check mps of nodes" << std::endl
628 << _T_mpd_ << std::endl
629 << _mps_of_node_ << std::endl;
630 OK = false;
631 }
632
633 hash[cell] = true;
634 }
635 }
636
637 for (const auto& elt: chk)
638 for (const auto& elt2: elt.second)
639 if (!elt2.second) {
640 std::cerr << "check mps of nodes2" << std::endl
641 << _T_mpd_ << std::endl
642 << _mps_of_node_ << std::endl;
643 OK = false;
644 }
645
646 if (!OK) return false;
647 }
648
649 // check that the junction tree and the T_mpd are junction trees
650 {
651 if (!_junction_tree_.isJoinTree()) {
652 std::cerr << "check join tree _junction_tree_" << std::endl
653 << _junction_tree_ << std::endl;
654 return false;
655 }
656
657 if (!_T_mpd_.isJoinTree()) {
658 std::cerr << "check join tree _T_mpd_" << std::endl << _T_mpd_ << std::endl;
659 return false;
660 }
661 }
662
663 // check elimination sequences
664 {
665 eliminationOrder();
666
667 if (_elimination_order_.size() != _graph_.size()) {
668 std::cerr << "check elimination order" << std::endl << _elimination_order_ << std::endl;
669 return false;
670 }
671
672 NodeSet nodes;
673
674 for (const auto node: _graph_.nodes()) {
675 if (nodes.exists(node)) {
676 std::cerr << "check elimination order" << std::endl << _elimination_order_ << std::endl;
677 return false;
678 } else nodes.insert(node);
679 }
680
681 if (nodes.size() != _graph_.size()) {
682 std::cerr << "check elimination order" << std::endl << _elimination_order_ << std::endl;
683 return false;
684 }
685
686 if (_reverse_elimination_order_.size() != _graph_.size()) {
687 std::cerr << "check reverse elimination order" << std::endl
688 << _reverse_elimination_order_ << std::endl;
689 return false;
690 }
691
692 for (const auto node: _graph_.nodes()) {
693 if (!_reverse_elimination_order_.exists(node)) {
694 std::cerr << "check reverse elimination order" << std::endl
695 << _reverse_elimination_order_ << std::endl;
696 return false;
697 }
698 }
699 }
700
701 // check created junction tree cliques
702 {
703 createdJunctionTreeCliques();
704
705 if (_created_JT_cliques_.size() != _graph_.size()) {
706 std::cerr << "check creating JT cliques" << std::endl << _created_JT_cliques_ << std::endl;
707 return false;
708 }
709
710 for (const auto node: _graph_.nodes()) {
711 if (!_created_JT_cliques_.exists(node)
712 || !_junction_tree_.existsNode(_created_JT_cliques_[node])) {
713 std::cerr << "check created JT cliques" << std::endl << _created_JT_cliques_ << std::endl;
714 return false;
715 }
716 }
717 }
718
719 return true;
720 }
721
723
724 void IncrementalTriangulation::_setUpConnectedTriangulation_(
725 NodeId Mx,
726 NodeId Mfrom,
727 UndiGraph& theGraph,
728 std::vector< Edge >& notAffectedneighbourCliques,
729 HashTable< NodeId, bool >& cliques_affected) {
730 // mark the clique so that we won't try to update it several times
731 cliques_affected[Mx] = false;
732
733 // get the nodes that are concerned by the triangulation update
734 for (const auto node: _junction_tree_.clique(Mx))
735 if (!theGraph.exists(node)) theGraph.addNodeWithId(node);
736
737 // go on with the neighbour cliques in the junction tree
738 for (const auto othernode: _junction_tree_.neighbours(Mx))
739 if (othernode != Mfrom) {
740 if (cliques_affected.exists(othernode)) {
741 _setUpConnectedTriangulation_(othernode,
742 Mx,
743 theGraph,
744 notAffectedneighbourCliques,
745 cliques_affected);
746 } else {
747 // indicate that we have a clique not affected that is adjacent
748 // to an affected one
749 notAffectedneighbourCliques.push_back(Edge(othernode, Mx));
750 }
751 }
752 }
753
755
756 void IncrementalTriangulation::_updateJunctionTree_(NodeProperty< bool >& all_cliques_affected,
757 NodeSet& new_nodes_in_junction_tree) {
758 // a temporary subgraph in which we actually perform the triangulation
759 UndiGraph tmp_graph;
760
761 // for each triangulation, we will keep track of the cliques of the
762 // junction tree that are not affected by the triangulation but that are
763 // adjacent to cliques affected. This will enable us to connect easily the
764 // newly created cliques with the old ones.
765 std::vector< Edge > notAffectedneighbourCliques;
766
767 // parse all the affected MPS and get the corresponding cliques
768
769 for (const auto& elt: _mps_affected_)
770 if (elt.second) {
771 // get the cliques contained in this MPS
772 const std::vector< NodeId >& cliques = _cliques_of_mps_[elt.first];
773
774 for (unsigned int i = 0; i < cliques.size(); ++i)
775 all_cliques_affected.insert(cliques[i], true);
776 }
777
778 // for each connected set of cliques involved in the triangulations
779 // perform a new triangulation and update the max prime subgraph tree
780 for (const auto& elt: all_cliques_affected) {
781 if (elt.second) {
782 // set up the connected subgraph that need be retriangulated and the
783 // cliques that are affected by this triangulation
784 tmp_graph.clear();
785 notAffectedneighbourCliques.clear();
786 _setUpConnectedTriangulation_(elt.first,
787 elt.first,
788 tmp_graph,
789 notAffectedneighbourCliques,
790 all_cliques_affected);
791
792 // insert the edges in tmp_graph
793 for (auto edge: _graph_.edges()) {
794 try {
795 tmp_graph.addEdge(edge.first(), edge.second());
796 } catch (Exception const&) {} // both extremities must be in tmp_graph
797 }
798
799 // remove from the mps_of_node table the affected mps containing the
800 // node
801 // for ( UndiGraph::NodeIterator iter_node =
802 // tmp_graph.beginNodes();iter_node
803 // != tmp_graph.endNodes(); ++iter_node ) {
804 for (const auto node: tmp_graph.nodes()) {
805 List< NodeId >& mps = _mps_of_node_[node];
806
807 for (HashTableConstIteratorSafe< NodeId, bool > iter_mps
808 = _mps_affected_.beginSafe(); // safe iterator needed here
809 iter_mps != _mps_affected_.endSafe();
810 ++iter_mps) {
811 if (iter_mps.val()) mps.eraseByVal(iter_mps.key());
812 }
813 }
814
815 // now tmp_graph contains the graph that should be triangulated.
816 // so triangulate it and get its junction tree
817 _triangulation_->setGraph(&tmp_graph, &_domain_sizes_);
818
819 const CliqueGraph& tmp_junction_tree = _triangulation_->junctionTree();
820
821 // now, update the global junction tree
822 // first add the nodes of tmp_junction_tree to _junction_tree_
823 // to do so, store the translations of the node ids of tmp_junction_tree
824 // into the node ids of _junction_tree_
825 NodeProperty< NodeId > tmp2global_junction_tree(tmp_junction_tree.size());
826
827 for (const auto cliq: tmp_junction_tree.nodes()) {
828 // get new ids for the nodes of tmp_junction_tree. These should be
829 // greater than or equal to _junction_tree_.bound () so that we can
830 // use the max_old_id defined at the beginning of the method.
831 NodeId new_id = _junction_tree_.addNode(tmp_junction_tree.clique(cliq));
832 // translate the id of the temprary JT into an id of the global JT
833 tmp2global_junction_tree.insert(cliq, new_id);
834 new_nodes_in_junction_tree.insert(new_id);
835 }
836
837 // and add the edges of tmp_junction_tree to _junction_tree_
838 for (const auto& edge: tmp_junction_tree.edges())
839 _junction_tree_.addEdge(tmp2global_junction_tree[edge.first()],
840 tmp2global_junction_tree[edge.second()]);
841
842 // second get the edges in _junction_tree_ that have an extremal clique
843 // R
844 // in the affected clique set and the other one S not in the affected
845 // set
846 // and see which new node V in the _junction_tree_ should be connected
847 // to S. The running intersection property guarrantees that the clique
848 // in
849 // the tmp_junction_tree that results from the elimination (during the
850 // triangulation process) of the first eliminated node in the separator
851 // between R and S is an admissible candidate
852 for (unsigned int i = 0; i < notAffectedneighbourCliques.size(); ++i) {
853 // check that the separator is not empty. If this is the case, do not
854 // link the new junction tree to the old one
855 const NodeSet& sep = _junction_tree_.separator(notAffectedneighbourCliques[i]);
856
857 if (sep.size() != 0) {
858 // now find the first eliminated node in the separator
859 Size _elim_order_ = tmp_graph.bound() + 1;
860 NodeId elim_node = 0;
861
862 for (const auto id: sep) {
863 Size new_order = _triangulation_->eliminationOrder(id);
864
865 if (new_order < _elim_order_) {
866 _elim_order_ = new_order;
867 elim_node = id;
868 }
869 }
870
871 // find the _junction_tree_ clique corresponding to the elimination
872 // of
873 // elim_node and insert an edge between this clique and that which
874 // was
875 // not affected
876 NodeId& to_connect
877 = tmp2global_junction_tree[_triangulation_->createdJunctionTreeClique(elim_node)];
878
879 NodeId not_affected
880 = all_cliques_affected.exists(notAffectedneighbourCliques[i].first())
881 ? notAffectedneighbourCliques[i].second()
882 : notAffectedneighbourCliques[i].first();
883
884 _junction_tree_.addEdge(not_affected, to_connect);
885
886 if (!new_nodes_in_junction_tree.contains(to_connect)) {
887 _T_mpd_.addEdge(_mps_of_clique_[to_connect], _mps_of_clique_[not_affected]);
888 }
889
890 // check that the separator created by the new edge is not equal to
891 // to_connect. If this is the case, then to_connect is included in
892 // not_affected and, hence, should be removed from the graph
893 if (_junction_tree_.separator(not_affected, to_connect).size()
894 == _junction_tree_.clique(to_connect).size()) {
895 _junction_tree_.eraseEdge(Edge(not_affected, to_connect));
896
897 for (const auto neighbour: _junction_tree_.neighbours(to_connect)) {
898 _junction_tree_.addEdge(neighbour, not_affected);
899
900 if (!new_nodes_in_junction_tree.contains(neighbour))
901 _T_mpd_.addEdge(_mps_of_clique_[neighbour], _mps_of_clique_[not_affected]);
902 }
903
904 _junction_tree_.eraseNode(to_connect);
905
906 to_connect = not_affected;
907 }
908 }
909 }
910 }
911 }
912
913 // remove the mps that were affected and update the cliques_of_mps table
914 for (const auto& elt: all_cliques_affected) {
915 _mps_of_clique_.erase(elt.first);
916 _junction_tree_.eraseNode(elt.first);
917 }
918
919 for (const auto& elt: _mps_affected_)
920 if (elt.second) {
921 _cliques_of_mps_.erase(elt.first);
922 _T_mpd_.eraseNode(elt.first);
923 }
924 }
925
927
928 void IncrementalTriangulation::_computeMaxPrimeMergings_(
929 const NodeId node,
930 const NodeId from,
931 std::vector< std::pair< NodeId, NodeId > >& merged_cliques,
932 HashTable< NodeId, bool >& mark,
933 const NodeSet& new_nodes_in_junction_tree) const {
934 mark[node] = true;
935
936 // check the separators on all the adjacent edges of Mx
937 for (const auto other_node: _junction_tree_.neighbours(node))
938 if (other_node != from) {
939 const NodeSet& separator = _junction_tree_.separator(Edge(other_node, node));
940
941 // check that the separator between node and other_node is complete
942 bool complete = true;
943
944 for (auto iter_sep1 = separator.begin(); iter_sep1 != separator.end() && complete;
945 ++iter_sep1) {
946 auto iter_sep2 = iter_sep1;
947
948 for (++iter_sep2; iter_sep2 != separator.end(); ++iter_sep2) {
949 if (!_graph_.existsEdge(*iter_sep1, *iter_sep2)) {
950 complete = false;
951 break;
952 }
953 }
954 }
955
956 // here complete indicates whether the separator is complete or not
957 if (!complete) merged_cliques.push_back(std::pair< NodeId, NodeId >(other_node, node));
958
959 if (new_nodes_in_junction_tree.contains(other_node))
960 _computeMaxPrimeMergings_(other_node,
961 node,
962 merged_cliques,
963 mark,
964 new_nodes_in_junction_tree);
965 }
966 }
967
969
970 void IncrementalTriangulation::_updateMaxPrimeSubgraph_(
971 NodeProperty< bool >& all_cliques_affected,
972 const NodeSet& new_nodes_in_junction_tree) {
973 // the maximal prime subgraph join tree is created by aggregation of some
974 // cliques. More precisely, when the separator between 2 cliques is not
975 // complete in the original graph, then the two cliques must be merged.
976
977 // Create a hashtable indicating which clique has been absorbed by some
978 // other
979 // clique. Keys are the cliques absorbed, and values are the cliques that
980 // absorb them.
981 HashTable< NodeId, NodeId > T_mpd_cliques(all_cliques_affected.size());
982
983 for (const auto clik: _junction_tree_.nodes())
984 if (new_nodes_in_junction_tree.contains(clik)) T_mpd_cliques.insert(clik, clik);
985
986 // parse all the separators of the junction tree and test those that are not
987 // complete in the original graph
988 std::vector< std::pair< NodeId, NodeId > > merged_cliques;
989
990 HashTable< NodeId, bool > mark = T_mpd_cliques.map(false);
991
992 for (const auto& elt: mark)
993 if (!elt.second)
994 _computeMaxPrimeMergings_(elt.first,
995 elt.first,
996 merged_cliques,
997 mark,
998 new_nodes_in_junction_tree);
999
1000 // compute the transitive closure of merged_cliques. This one will contain
1001 // pairs (X,Y) indicating that clique X must be merged with clique Y.
1002 // Actually clique X will be inserted into clique Y.
1003 for (unsigned int i = 0; i < merged_cliques.size(); ++i) {
1004 if (T_mpd_cliques.exists(merged_cliques[i].second))
1005 T_mpd_cliques[merged_cliques[i].first] = T_mpd_cliques[merged_cliques[i].second];
1006 else T_mpd_cliques[merged_cliques[i].first] = _mps_of_clique_[merged_cliques[i].second];
1007 }
1008
1009 // now we can create the max prime junction tree.
1010
1011 // create a map translating the cliques' ids in the junction tree into
1012 // cliques' id in the T_mpd_ tree
1013 NodeProperty< NodeId > clique2MPS(T_mpd_cliques.size());
1014
1015 // First, create the new cliques and create the corresponding
1016 // cliques_of_mps entries
1017 for (const auto& elt: T_mpd_cliques)
1018 if (elt.first == elt.second) {
1019 NodeId newId = _T_mpd_.addNode(_junction_tree_.clique(elt.second));
1020 clique2MPS.insert(elt.second, newId);
1021 std::vector< NodeId >& vect_of_cliques
1022 = _cliques_of_mps_.insert(newId, std::vector< NodeId >()).second;
1023 vect_of_cliques.push_back(elt.second);
1024 }
1025
1026 // add to the cliques previously created the nodes of the cliques that were
1027 // merged into them and update the cliques_of_mps
1028 for (const auto& elt: T_mpd_cliques)
1029 if ((elt.first != elt.second) && (new_nodes_in_junction_tree.contains(elt.second))) {
1030 const NodeId idMPS = clique2MPS[elt.second];
1031
1032 for (const auto node: _junction_tree_.clique(elt.first)) {
1033 try {
1034 _T_mpd_.addToClique(idMPS, node);
1035 } catch (DuplicateElement const&) {}
1036 }
1037
1038 _cliques_of_mps_[idMPS].push_back(elt.first);
1039 }
1040
1041 // update the mps_of_node and the mps_of_clique
1042 for (const auto& elt: T_mpd_cliques) {
1043 const NodeId idMPS = clique2MPS[elt.second];
1044 _mps_of_clique_.insert(elt.first, idMPS);
1045
1046 if (elt.first == elt.second)
1047 for (const auto node: _T_mpd_.clique(idMPS))
1048 _mps_of_node_[node].insert(idMPS);
1049 }
1050
1051 // add the edges to the max prime subgraph tree
1052 for (const auto& elt: T_mpd_cliques) {
1053 NodeId clique = clique2MPS[elt.second];
1054
1055 for (const auto othernode: _junction_tree_.neighbours(elt.first))
1056 if (T_mpd_cliques.exists(othernode)) {
1057 // here iter is linked to another node that has been created during
1058 // the triangulation
1059 NodeId otherClique = clique2MPS[T_mpd_cliques[othernode]];
1060
1061 // avoid adding the same edge several times
1062 if (clique > otherClique) { _T_mpd_.addEdge(clique, otherClique); }
1063 } else {
1064 _T_mpd_.addEdge(clique, _mps_of_clique_[othernode]);
1065 }
1066 }
1067 }
1068
1070
1071 void IncrementalTriangulation::updateTriangulation() {
1072 if (!_require_update_) return;
1073 // the set of all the cliques that should be affected by the different
1074 // triangulations we will perform (one by connected component)
1075 NodeProperty< bool > all_cliques_affected(_junction_tree_.size());
1076
1077 // we need to keep track of the new node ids that will be inserted
1078 // into _junction_tree_. A priori, these should be equal to the ids
1079 // inserted into tmp2global_junction_tree. But, sometimes, some new nodes
1080 // are included into old nodes and, in this case, the translation in
1081 // tmp2global_junction_tree indicates the the new node inserted corresponds
1082 // to an old node. Here we wish to know this additional information
1083 NodeSet new_nodes_in_junction_tree;
1084
1085 _updateJunctionTree_(all_cliques_affected, new_nodes_in_junction_tree);
1086
1087 // now update the T_mpd so that it be coherent with the junction tree
1088 _updateMaxPrimeSubgraph_(all_cliques_affected, new_nodes_in_junction_tree);
1089
1090 // reset the MPS that are affected
1091 _mps_affected_.clear();
1092
1093 for (const auto node: _T_mpd_.nodes())
1094 _mps_affected_.insert(node, false);
1095
1096 // remove all the structures used by the triangulation algorithm
1097 _triangulation_->clear();
1098
1099 _require_update_ = false;
1100 }
1101
1103
1104 void IncrementalTriangulation::clear() {
1105 _graph_.clear();
1106 _domain_sizes_.clear();
1107 _junction_tree_.clear();
1108 _T_mpd_.clear();
1109 _mps_of_node_.clear();
1110 _cliques_of_mps_.clear();
1111 _mps_of_clique_.clear();
1112 _mps_affected_.clear();
1113 _triangulation_->clear();
1114 _require_update_ = false;
1115 _require_elimination_order_ = false;
1116 _elimination_order_.clear();
1117 _reverse_elimination_order_.clear();
1118 _require_created_JT_cliques_ = false;
1119 _created_JT_cliques_.clear();
1120 }
1121
1123
1124 void IncrementalTriangulation::_collectJTCliques_(const NodeId clique,
1125 const NodeId from,
1126 NodeProperty< bool >& examined) {
1127 // apply collect to all the neighbours except from
1128 for (const auto otherclique: _junction_tree_.neighbours(clique))
1129 if (otherclique != from) _collectJTCliques_(otherclique, clique, examined);
1130
1131 // get the nodes that belong to clique and not to from
1132 examined[clique] = true;
1133
1134 const NodeSet& cliquenodes = _junction_tree_.clique(clique);
1135
1136 if (from != clique) {
1137 const NodeSet& separator = _junction_tree_.separator(clique, from);
1138
1139 for (const auto cli: cliquenodes)
1140 if (!separator.contains(cli)) _created_JT_cliques_.insert(cli, clique);
1141 } else {
1142 for (const auto cli: cliquenodes)
1143 _created_JT_cliques_.insert(cli, clique);
1144 }
1145 }
1146
1149
1150 const NodeProperty< NodeId >& IncrementalTriangulation::createdJunctionTreeCliques() {
1151 // check if we already computed the containing cliques
1152 if (!_require_created_JT_cliques_) return _created_JT_cliques_;
1153
1154 // we first we compute the junction tree
1155 updateTriangulation();
1156
1157 _created_JT_cliques_.clear();
1158
1159 _require_created_JT_cliques_ = false;
1160
1161 if (_junction_tree_.size() == 0) { return _created_JT_cliques_; }
1162
1163 // now we can use a collect algorithm to get the containing cliques
1164 NodeProperty< bool > examined = _junction_tree_.nodesPropertyFromVal< bool >(false);
1165
1166 for (const auto& elt: examined)
1167 if (!elt.second) _collectJTCliques_(elt.first, elt.first, examined);
1168
1169 return _created_JT_cliques_;
1170 }
1171
1173
1174 NodeId IncrementalTriangulation::createdJunctionTreeClique(NodeId id) {
1175 createdJunctionTreeCliques();
1176 return _created_JT_cliques_[id];
1177 }
1178
1181
1182 NodeId IncrementalTriangulation::createdMaxPrimeSubgraph(const NodeId id) {
1183 // get the created junction tree clique and get its MPS
1184 return _mps_of_clique_[createdJunctionTreeClique(id)];
1185 }
1186
1188
1189 void IncrementalTriangulation::setGraph(const UndiGraph* graph,
1190 const NodeProperty< Size >* dom_sizes) {
1191 // check that both the graph and the domain sizes are different from nullptr
1192 // or else that both are equal to nullptr
1193 if (((graph != nullptr) && (dom_sizes == nullptr))
1194 || ((graph == nullptr) && (dom_sizes != nullptr))) {
1196 "one of the graph or the set of domain sizes "
1197 "is a null pointer.");
1198 }
1199
1200 // remove the current graph
1201 clear();
1202
1203 // copy the graph passed in arent and update the structures
1204 // containing the informations useful for the triangulation
1205 if (graph != nullptr) {
1206 for (const auto node: *graph)
1207 addNode(node, (*dom_sizes)[node]);
1208
1209 for (const auto& edge: graph->edges())
1210 addEdge(edge.first(), edge.second());
1211 }
1212 }
1213
1215
1216 void IncrementalTriangulation::_collectEliminationOrder_(const NodeId node,
1217 const NodeId from,
1218 NodeProperty< bool >& examined,
1219 Idx& index) {
1220 // apply collect to all the neighbours except from
1221 for (const auto othernode: _junction_tree_.neighbours(node))
1222 if (othernode != from) _collectEliminationOrder_(othernode, node, examined, index);
1223
1224 // get the nodes that belong to node and not to from
1225 examined[node] = true;
1226
1227 const NodeSet& clique = _junction_tree_.clique(node);
1228
1229 if (from != node) {
1230 const NodeSet& separator = _junction_tree_.separator(node, from);
1231
1232 for (const auto cli: clique) {
1233 if (!separator.contains(cli)) {
1234 _elimination_order_[index] = cli;
1235 _reverse_elimination_order_.insert(cli, index);
1236 ++index;
1237 }
1238 }
1239 } else {
1240 for (const auto cli: clique) {
1241 _elimination_order_[index] = cli;
1242 _reverse_elimination_order_.insert(cli, index);
1243 ++index;
1244 }
1245 }
1246 }
1247
1249
1250 const std::vector< NodeId >& IncrementalTriangulation::eliminationOrder() {
1251 // check if we already computed the elimination order
1252 if (!_require_elimination_order_) return _elimination_order_;
1253
1254 // to compute the elimination order, we first we compute the junction tree
1255 updateTriangulation();
1256
1257 _elimination_order_.resize(_graph_.size());
1258
1259 _reverse_elimination_order_.clear();
1260
1261 _require_elimination_order_ = false;
1262
1263 if (_junction_tree_.size() == Size(0)) { return _elimination_order_; }
1264
1265 // now we can use a collect algorithm to get the elimination order
1266 Idx index = Idx(0);
1267
1268 NodeProperty< bool > examined = _junction_tree_.nodesPropertyFromVal< bool >(false);
1269
1270 for (const auto& elt: examined)
1271 if (!elt.second) _collectEliminationOrder_(elt.first, elt.first, examined, index);
1272
1273 return _elimination_order_;
1274 }
1275
1278
1279 Idx IncrementalTriangulation::eliminationOrder(const NodeId node) {
1280 if (!_graph_.existsNode(node)) { GUM_ERROR(NotFound, "the node " << node << " does not exist") }
1281
1282 // compute the elimination order
1283 eliminationOrder();
1284
1285 return _reverse_elimination_order_[node];
1286 }
1287
1288} /* namespace gum */
1289
1290#endif /* DOXYGEN_SHOULD_SKIP_THIS */
Exception : a similar element already exists.
Exception base for graph error.
IncrementalTriangulation(const UnconstrainedTriangulation &triang_algo, const UndiGraph *theGraph, const NodeProperty< Size > *modal)
constructor
Exception : the element we looked for cannot be found.
void insert(const Key &k)
Inserts a new element into the set.
Definition set_tpl.h:539
Interface for all the triangulation methods.
Interface for all triangulation methods without constraints on node elimination orderings.
Base class for undirected graphs.
Definition undiGraph.h:128
#define GUM_ERROR(type, msg)
Definition exceptions.h:72
HashTable< NodeId, VAL > NodeProperty
Property on graph elements.
Set< NodeId > NodeSet
Some typdefs and define for shortcuts ...
Class for computing default triangulations of graphs.
Inline implementations for computing default triangulations of graphs.
Generic class for manipulating lists.
Useful macros for maths.
gum is the global namespace for all aGrUM entities
Definition agrum.h:46
Base classes for undirected graphs.