1 /**
2  *
3  *   Copyright (c) 2005-2021 by Pierre-Henri WUILLEMIN(_at_LIP6) & Christophe GONZALES(_at_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 source implementations of simplicial set
24  *
25  * @author Christophe GONZALES(_at_AMU) and Pierre-Henri WUILLEMIN(_at_LIP6)
26  */
27 #include <agrum/tools/core/math/math_utils.h>
28 #include <agrum/tools/graphs/algorithms/simplicialSet.h>
29 #include <agrum/tools/graphs/graphElements.h>
30 #include <limits>
31 #include <limits>
32 #include <sstream>
33 #include <sstream>
34 
35 #ifdef GUM_NO_INLINE
36 #  include <agrum/tools/graphs/algorithms/simplicialSet_inl.h>
37 #endif   // GUM_NO_INLINE
38 
39 #ifndef DOXYGEN_SHOULD_SKIP_THIS
40 
41 namespace gum {
42 
43   /* ===========================================================================
44    */
45   /* ===========================================================================
46    */
47   /* ===  CLASS FOR RETRIEVING SIMPLICIAL, ALMOST AND QUASI SIMPLICIAL NODES ===
48    */
49   /* ===========================================================================
50    */
51   /* ===========================================================================
52    */
53 
54   /// constructor. initialize the simplicial set w.r.t. a given  _graph_
SimplicialSet(UndiGraph * graph,const NodeProperty<double> * log_domain_sizes,NodeProperty<double> * log_weights,double theRatio,double theThreshold)55   SimplicialSet::SimplicialSet(UndiGraph*                    graph,
56                                const NodeProperty< double >* log_domain_sizes,
57                                NodeProperty< double >*       log_weights,
58                                double                        theRatio,
59                                double                        theThreshold) :
60       _graph_(graph != nullptr
61                  ? graph
62                  : GUM_ERROR_IN_EXPR(OperationNotAllowed, "SimplicialSet requires a valid graph")),
63       _log_weights_(log_weights != nullptr ? log_weights
64                                            : GUM_ERROR_IN_EXPR(OperationNotAllowed,
65                                                                "SimplicialSet "
66                                                                "requires non-null log weights")),
67       _log_domain_sizes_(log_domain_sizes != nullptr
68                             ? log_domain_sizes
69                             : GUM_ERROR_IN_EXPR(OperationNotAllowed,
70                                                 "SimplicialSet "
71                                                 "requires non-null domain sizes")),
72       _simplicial_nodes_(std::less< double >(), _graph_->size()),
73       _almost_simplicial_nodes_(std::less< double >(), _graph_->size()),
74       _quasi_simplicial_nodes_(std::less< double >(), _graph_->size()),
75       _containing_list_(_graph_->size()), _nb_triangles_(_graph_->size() * _graph_->size() / 2),
76       _nb_adjacent_neighbours_(_graph_->size()), _quasi_ratio_(theRatio),
77       _log_threshold_(std::log(1 + theThreshold)) {
78     if (graph != nullptr) { GUM_CONSTRUCTOR(SimplicialSet); }
79 
80     // end of initialization: compute  _nb_triangles_,  _nb_adjacent_neighbours_,
81     // etc
82     _initialize_();
83   }
84 
85   /// copy constructor
SimplicialSet(const SimplicialSet & from,UndiGraph * graph,const NodeProperty<double> * log_domain_sizes,NodeProperty<double> * log_weights,bool avoid_check)86   SimplicialSet::SimplicialSet(const SimplicialSet&          from,
87                                UndiGraph*                    graph,
88                                const NodeProperty< double >* log_domain_sizes,
89                                NodeProperty< double >*       log_weights,
90                                bool                          avoid_check) :
91       _graph_(graph != nullptr
92                  ? graph
93                  : GUM_ERROR_IN_EXPR(OperationNotAllowed, "SimplicialSet requires a valid graph")),
94       _log_weights_(log_weights != nullptr ? log_weights
95                                            : GUM_ERROR_IN_EXPR(OperationNotAllowed,
96                                                                "SimplicialSet "
97                                                                "requires non-null log weights")),
98       _log_domain_sizes_(log_domain_sizes != nullptr
99                             ? log_domain_sizes
100                             : GUM_ERROR_IN_EXPR(OperationNotAllowed,
101                                                 "SimplicialSet "
102                                                 "requires non-null domain sizes")) {
103     if (!avoid_check) {
104       // check whether the graph, the log weights and the log_modalities
105       // are similar to those of from
106       if ((_graph_ == from._graph_) || (_log_weights_ == from._log_weights_)
107           || (*_graph_ != *from._graph_) || (*_log_domain_sizes_ != *from._log_domain_sizes_)) {
108         GUM_ERROR(OperationNotAllowed,
109                   "SimplicialSet requires fresh copies of "
110                   "graph, log weights and log domain sizes");
111       }
112     }
113 
114     // copy the current content of from
115     *_log_weights_            = *from._log_weights_;
116     _simplicial_nodes_        = from._simplicial_nodes_;
117     _almost_simplicial_nodes_ = from._almost_simplicial_nodes_;
118     _quasi_simplicial_nodes_  = from._quasi_simplicial_nodes_;
119     _containing_list_         = from._containing_list_;
120     _nb_triangles_            = from._nb_triangles_;
121     _nb_adjacent_neighbours_  = from._nb_adjacent_neighbours_;
122     _log_tree_width_          = from._log_tree_width_;
123     _quasi_ratio_             = from._quasi_ratio_;
124     _log_threshold_           = from._log_threshold_;
125     _changed_status_          = from._changed_status_;
126     _we_want_fill_ins_        = from._we_want_fill_ins_;
127     _fill_ins_list_           = from._fill_ins_list_;
128 
129     GUM_CONS_CPY(SimplicialSet);
130   }
131 
132   /// move constructor
SimplicialSet(SimplicialSet && from)133   SimplicialSet::SimplicialSet(SimplicialSet&& from) :
134       _graph_(from._graph_), _log_weights_(from._log_weights_),
135       _log_domain_sizes_(from._log_domain_sizes_),
136       _simplicial_nodes_(std::move(from._simplicial_nodes_)),
137       _almost_simplicial_nodes_(std::move(from._almost_simplicial_nodes_)),
138       _quasi_simplicial_nodes_(std::move(from._quasi_simplicial_nodes_)),
139       _containing_list_(std::move(from._containing_list_)),
140       _nb_triangles_(std::move(from._nb_triangles_)),
141       _nb_adjacent_neighbours_(std::move(from._nb_adjacent_neighbours_)),
142       _log_tree_width_(from._log_tree_width_), _quasi_ratio_(from._quasi_ratio_),
143       _log_threshold_(from._log_threshold_), _changed_status_(std::move(from._changed_status_)),
144       _we_want_fill_ins_(from._we_want_fill_ins_),
145       _fill_ins_list_(std::move(from._fill_ins_list_)) {
146     GUM_CONS_MOV(SimplicialSet);
147   }
148 
149   /// destructor
~SimplicialSet()150   SimplicialSet::~SimplicialSet() { GUM_DESTRUCTOR(SimplicialSet); }
151 
152   /// adds the necessary edges so that node 'id' and its neighbors form a clique
makeClique(const NodeId id)153   void SimplicialSet::makeClique(const NodeId id) {
154     // first, update the list to which belongs the node
155     if (_changed_status_.contains(id)) _updateList_(id);
156 
157     // to make id be a clique, we may have to add edges. Hence, this will create
158     // new triangles and we should update the number of triangles passing
159     // through the new edges. Moreover, we should also update the number of
160     // adjacent neighbors for each node involved in these new triangles as well
161     // as the new weights of the nodes. Finally, we should update the
162     // simplicial,
163     // almost and quasi simplicial lists. However, to optimize the code, we use
164     // a lazy list update: we just update a hashtable indicating whether a list
165     // update should be performed for a given node. This enables performing the
166     // updates only when necessary. if the node is known to be simplicial, there
167     // is nothing to do
168     if (_simplicial_nodes_.contains(id)) {
169       return;
170     } else if (_almost_simplicial_nodes_.contains(id)) {
171       // get the neighbor that does not form a clique with the other neighbors
172       // recall that id is an almost simplicial node if there exists a node,
173       // say Y, such that, after deleting Y, id and its adjacent nodes form a
174       // clique.
175       const NodeSet& nei    = _graph_->neighbours(id);
176       Size           nb_adj = nei.size();
177       Size           nb     = _nb_adjacent_neighbours_[id];
178 
179       // nb_almost = the number of edges that should link the neighbors of
180       // node id, after node Y mentioned above has been removed. Recall that
181       // these neighbors and id form a clique. Hence this corresponds to the
182       // number of triangles involving id and 2 of its neighbors, after node
183       // Y has been removed.
184       Size   nb_almost = ((nb_adj - 1) * (nb_adj - 2)) / 2;
185       NodeId node1     = 0;
186 
187       for (const auto current_node: nei) {
188         if (nb_almost == nb - _nb_triangles_[Edge(current_node, id)]) {
189           // we found the neighbor we were looking for: nb = the number of
190           // pairs of neighbors of id that are adjacent. In other words, this
191           // is the number of triangles involving node id. Now remove from it
192           // the
193           // triangles involving edge (id,node1), and you get the number of
194           // triangles involving id, but not node1. If id is almost simplicial,
195           // then this number should be equal to the set of combinations of
196           // all the possible pairs of neighbors of id except node1, hence
197           // to nb_almost.
198           node1 = current_node;
199           break;
200         }
201       }
202 
203       double  log_domain_size_node1 = (*_log_domain_sizes_)[node1];
204       double& _log_weights_node1_   = (*_log_weights_)[node1];
205 
206       // now, to make a clique between id and its neighbors, there just remains
207       // to add the missing edges between node1 and the other neighbors of id.
208 
209       // nb_n1 will contain the number of pairs of neighbors of node1 that
210       // will be adjacent after the clique is constructed but that
211       // are not yet adjacent
212       unsigned int nb_n1 = 0;
213 
214       // update the number of triangles of the edges and keep track of the
215       // nodes involved.
216       for (const auto node2: nei) {
217         if ((node2 != node1) && !_graph_->existsEdge(node1, node2)) {
218           // add the edge
219           const Edge e1_2(node1, node2);
220           _graph_->addEdge(node1, node2);
221 
222           if (_we_want_fill_ins_) _fill_ins_list_.insert(e1_2);
223 
224           if (!_changed_status_.contains(node2)) _changed_status_.insert(node2);
225 
226           _log_weights_node1_ += (*_log_domain_sizes_)[node2];
227           (*_log_weights_)[node2] += log_domain_size_node1;
228           _nb_triangles_.insert(e1_2, 0);
229 
230           // nb_n2 will contain the number of pairs of neighbors of node2 that
231           // will be adjacent after the clique is constructed but that
232           // are not yet adjacent
233           unsigned int nb_n2 = 0;
234 
235           // for all common neighbors of node1 and node2, update the number of
236           // triangles as well as the number of adjacent neighbors
237           if (_graph_->neighbours(node1).size() <= _graph_->neighbours(node2).size()) {
238             for (const auto neighbor: _graph_->neighbours(node1)) {
239               if (_graph_->existsEdge(neighbor, node2)) {
240                 // here iter->other (node1) is a neighbor of node1 and node2
241                 // hence there is a new triangle to be taken into account:
242                 // that between node1, node2 and iterEdge->other( node1 )
243                 ++nb_n1;
244                 ++nb_n2;
245                 ++_nb_adjacent_neighbours_[neighbor];
246                 ++_nb_triangles_[Edge(node1, neighbor)];
247                 ++_nb_triangles_[Edge(node2, neighbor)];
248 
249                 if (!_changed_status_.contains(neighbor)) _changed_status_.insert(neighbor);
250               }
251             }
252           } else {
253             for (const auto neighbor: _graph_->neighbours(node2)) {
254               if (_graph_->existsEdge(neighbor, node1)) {
255                 // here iter->other (node2) is a neighbor of node1 and node2
256                 // hence there is a new triangle to be taken into account:
257                 // that between node1, node2 and iterEdge->other( node2 )
258                 ++nb_n1;
259                 ++nb_n2;
260                 ++_nb_adjacent_neighbours_[neighbor];
261                 ++_nb_triangles_[Edge(node2, neighbor)];
262                 ++_nb_triangles_[Edge(node1, neighbor)];
263 
264                 if (!_changed_status_.contains(neighbor)) _changed_status_.insert(neighbor);
265               }
266             }
267           }
268 
269           _nb_adjacent_neighbours_[node2] += nb_n2;
270           _nb_triangles_[e1_2] += nb_n2;
271         }
272       }
273 
274       if (!_changed_status_.contains(node1)) _changed_status_.insert(node1);
275 
276       _nb_adjacent_neighbours_[node1] += nb_n1;
277     } else {
278       // here, id is neither a simplicial node nor an almost simplicial node
279 
280       // update the number of triangles of the edges and keep track of the
281       // nodes involved.
282       const NodeSet& nei = _graph_->neighbours(id);
283 
284       for (auto iter1 = nei.begin(); iter1 != nei.end(); ++iter1) {
285         NodeId       node1                 = *iter1;
286         double       log_domain_size_node1 = (*_log_domain_sizes_)[node1];
287         double&      _log_weights_node1_   = (*_log_weights_)[node1];
288         bool         node1_status          = false;
289         unsigned int nb_n1                 = 0;
290 
291         auto iterEdge2 = iter1;
292 
293         for (++iterEdge2; iterEdge2 != nei.end(); ++iterEdge2) {
294           const NodeId node2 = *iterEdge2;
295           const Edge   e1_2(node1, node2);
296 
297           if (!_graph_->existsEdge(e1_2)) {
298             // add the edge
299             _graph_->addEdge(node1, node2);
300 
301             if (_we_want_fill_ins_) _fill_ins_list_.insert(e1_2);
302 
303             if (!_changed_status_.contains(node2)) _changed_status_.insert(node2);
304 
305             node1_status = true;
306             _log_weights_node1_ += (*_log_domain_sizes_)[node2];
307             (*_log_weights_)[node2] += log_domain_size_node1;
308             _nb_triangles_.insert(e1_2, 0);
309 
310             // for all common neighbors of node1 and node2, update the number
311             // of triangles as well as the number of adjacent neighbors
312             unsigned int nb_n2 = 0;
313 
314             if (_graph_->neighbours(node1).size() <= _graph_->neighbours(node2).size()) {
315               for (const auto neighbor: _graph_->neighbours(node1))
316                 if (_graph_->existsEdge(neighbor, node2)) {
317                   // here iterEdge->other (node1) is a neighbor of
318                   // both node1 and node2
319                   ++nb_n1;
320                   ++nb_n2;
321                   ++_nb_adjacent_neighbours_[neighbor];
322                   ++_nb_triangles_[Edge(node1, neighbor)];
323                   ++_nb_triangles_[Edge(node2, neighbor)];
324 
325                   if (!_changed_status_.contains(neighbor)) _changed_status_.insert(neighbor);
326                 }
327             } else {
328               for (const auto neighbor: _graph_->neighbours(node2)) {
329                 if (_graph_->existsEdge(neighbor, node1)) {
330                   // here iterEdge->other (node2) is a neighbor of
331                   // both node1 and node2
332                   ++nb_n1;
333                   ++nb_n2;
334                   ++_nb_adjacent_neighbours_[neighbor];
335                   ++_nb_triangles_[Edge(node2, neighbor)];
336                   ++_nb_triangles_[Edge(node1, neighbor)];
337 
338                   if (!_changed_status_.contains(neighbor)) _changed_status_.insert(neighbor);
339                 }
340               }
341             }
342 
343             _nb_adjacent_neighbours_[node2] += nb_n2;
344             _nb_triangles_[e1_2] += nb_n2;
345           }
346         }
347 
348         _nb_adjacent_neighbours_[node1] += nb_n1;
349 
350         if (node1_status && !_changed_status_.contains(node1)) _changed_status_.insert(node1);
351       }
352     }
353 
354     // update the  _changed_status_ of node id as well as its containing list
355     if (!_simplicial_nodes_.contains(id)) {
356       if (_changed_status_.contains(id)) _changed_status_.erase(id);
357 
358       switch (_containing_list_[id]) {
359         case _Belong_::ALMOST_SIMPLICIAL:
360           _almost_simplicial_nodes_.erase(id);
361           break;
362 
363         case _Belong_::QUASI_SIMPLICIAL:
364           _quasi_simplicial_nodes_.erase(id);
365           break;
366 
367         default:
368           break;
369       }
370 
371       _simplicial_nodes_.insert(id, (*_log_weights_)[id]);
372       _containing_list_[id] = _Belong_::SIMPLICIAL;
373     } else {
374       if (_changed_status_.contains(id)) { _changed_status_.erase(id); }
375     }
376   }
377 
378   /// removes a node and its adjacent edges from the underlying  _graph_
eraseClique(const NodeId id)379   void SimplicialSet::eraseClique(const NodeId id) {
380     // check if the node we wish to remove actually belongs to the  _graph_
381     if (!_graph_->exists(id)) {
382       GUM_ERROR(NotFound, "Node " << id << " does not belong to the graph")
383     }
384 
385     const NodeSet nei = _graph_->neighbours(id);
386 
387     // check that node id is actually a clique
388     Size nb_adj = nei.size();
389     if (_nb_adjacent_neighbours_[id] != (nb_adj * (nb_adj - 1)) / 2) {
390       GUM_ERROR(NotFound, "Node " << id << " is not a clique")
391     }
392 
393     // remove the node and its adjacent edges
394     double log_domain_size_id = (*_log_domain_sizes_)[id];
395     for (auto iter1 = nei.begin(); iter1 != nei.end(); ++iter1) {
396       const NodeId node1 = *iter1;
397       _nb_adjacent_neighbours_[node1] -= nb_adj - 1;
398       (*_log_weights_)[node1] -= log_domain_size_id;
399 
400       if (!_changed_status_.contains(node1)) _changed_status_.insert(node1);
401 
402       _nb_triangles_.erase(Edge(node1, id));
403 
404       auto iter2 = iter1;
405       for (++iter2; iter2 != nei.end(); ++iter2)
406         --_nb_triangles_[Edge(node1, *iter2)];
407     }
408 
409     _log_tree_width_ = std::max(_log_tree_width_, (*_log_weights_)[id]);
410 
411     switch (_containing_list_[id]) {
412       case _Belong_::SIMPLICIAL:
413         _simplicial_nodes_.erase(id);
414         break;
415 
416       case _Belong_::ALMOST_SIMPLICIAL:
417         _almost_simplicial_nodes_.erase(id);
418         break;
419 
420       case _Belong_::QUASI_SIMPLICIAL:
421         _quasi_simplicial_nodes_.erase(id);
422         break;
423 
424       default:
425         break;
426     }
427 
428     _nb_adjacent_neighbours_.erase(id);
429     _containing_list_.erase(id);
430     _changed_status_.erase(id);
431     _graph_->eraseNode(id);
432     _log_weights_->erase(id);
433   }
434 
435   /// removes a node and its adjacent edges from the underlying  _graph_
eraseNode(const NodeId id)436   void SimplicialSet::eraseNode(const NodeId id) {
437     // check if the node we wish to remove actually belongs to the  _graph_
438     if (!_graph_->exists(id)) {
439       GUM_ERROR(NotFound, "Node " << id << " does not belong to the graph")
440     }
441 
442     // remove the node and its adjacent edges
443     const NodeSet& nei = _graph_->neighbours(id);
444 
445     for (auto iter = nei.beginSafe(); iter != nei.endSafe();
446          ++iter)   // safe iterator needed here for deletions
447       eraseEdge(Edge(*iter, id));
448 
449     switch (_containing_list_[id]) {
450       case _Belong_::SIMPLICIAL:
451         _simplicial_nodes_.erase(id);
452         break;
453 
454       case _Belong_::ALMOST_SIMPLICIAL:
455         _almost_simplicial_nodes_.erase(id);
456         break;
457 
458       case _Belong_::QUASI_SIMPLICIAL:
459         _quasi_simplicial_nodes_.erase(id);
460         break;
461 
462       default:
463         break;
464     }
465 
466     _nb_adjacent_neighbours_.erase(id);
467     _containing_list_.erase(id);
468     _changed_status_.erase(id);
469     _graph_->eraseNode(id);
470     _log_weights_->erase(id);
471   }
472 
473   /// removes a node and its adjacent edges from the underlying  _graph_
eraseEdge(const Edge & edge)474   void SimplicialSet::eraseEdge(const Edge& edge) {
475     // check if the edge we wish to remove actually belongs to the  _graph_
476     if (!_graph_->existsEdge(edge)) {
477       GUM_ERROR(NotFound, "Edge " << edge << " does not belong to the graph")
478     }
479 
480     // get the extremal nodes of the edge
481     const NodeId node1 = edge.first();
482     const NodeId node2 = edge.second();
483 
484     // remove the edge
485     _graph_->eraseEdge(edge);
486     _nb_triangles_.erase(edge);
487 
488     // update the  _log_weights_ of both nodes
489     (*_log_weights_)[node1] -= (*_log_domain_sizes_)[node2];
490     (*_log_weights_)[node2] -= (*_log_domain_sizes_)[node1];
491 
492     // update the number of triangles and the adjacent neighbors
493     unsigned int nb_neigh_n1_n2 = 0;
494     for (const auto othernode: _graph_->neighbours(node1)) {
495       if (_graph_->existsEdge(node2, othernode)) {
496         // udate the number of triangles passing through the egdes of the
497         //  _graph_
498         --_nb_triangles_[Edge(node1, othernode)];
499         --_nb_triangles_[Edge(node2, othernode)];
500         // update the neighbors
501         ++nb_neigh_n1_n2;
502         --_nb_adjacent_neighbours_[othernode];
503 
504         if (!_changed_status_.contains(othernode)) _changed_status_.insert(othernode);
505       }
506     }
507 
508     _nb_adjacent_neighbours_[node1] -= nb_neigh_n1_n2;
509     _nb_adjacent_neighbours_[node2] -= nb_neigh_n1_n2;
510 
511     if (!_changed_status_.contains(node1)) _changed_status_.insert(node1);
512     if (!_changed_status_.contains(node2)) _changed_status_.insert(node2);
513   }
514 
515   /// adds a new edge to the  _graph_ and recomputes the simplicial set
addEdge(NodeId node1,NodeId node2)516   void SimplicialSet::addEdge(NodeId node1, NodeId node2) {
517     // if the edge already exists, do nothing
518     const Edge edge(node1, node2);
519 
520     if (_graph_->existsEdge(edge)) return;
521 
522     // update the  _log_weights_ of both nodes
523     (*_log_weights_)[node1] += (*_log_domain_sizes_)[node2];
524     (*_log_weights_)[node2] += (*_log_domain_sizes_)[node1];
525 
526     unsigned int nb_triangle_in_new_edge = 0;
527     unsigned int nb_neigh_n1_n2          = 0;
528 
529     for (const auto othernode: _graph_->neighbours(node1)) {
530       if (_graph_->existsEdge(node2, othernode)) {
531         // udate the number of triangles passing through the egdes of the
532         //  _graph_
533         ++_nb_triangles_[Edge(node1, othernode)];
534         ++_nb_triangles_[Edge(node2, othernode)];
535         ++nb_triangle_in_new_edge;
536 
537         // update the neighbors
538         ++nb_neigh_n1_n2;
539         ++_nb_adjacent_neighbours_[othernode];
540 
541         if (!_changed_status_.contains(othernode)) _changed_status_.insert(othernode);
542       }
543     }
544 
545     _nb_adjacent_neighbours_[node1] += nb_neigh_n1_n2;
546     _nb_adjacent_neighbours_[node2] += nb_neigh_n1_n2;
547 
548     // add the edge
549     _graph_->addEdge(node1, node2);
550     _nb_triangles_.insert(edge, nb_triangle_in_new_edge);
551 
552     if (!_changed_status_.contains(node1)) _changed_status_.insert(node1);
553     if (!_changed_status_.contains(node2)) _changed_status_.insert(node2);
554   }
555 
556   /// put node id in the correct simplicial/almost simplicial/quasi simplicial
557   /// list
_updateList_(const NodeId id)558   void SimplicialSet::_updateList_(const NodeId id) {
559     // check if the node belongs to the  _graph_
560     if (!_graph_->exists(id)) { GUM_ERROR(NotFound, "Node " << id << " could not be found") }
561 
562     // check if the status of the node has changed
563     if (!_changed_status_.contains(id)) return;
564 
565     _changed_status_.erase(id);
566 
567     _Belong_&      belong = _containing_list_[id];
568     const NodeSet& nei    = _graph_->neighbours(id);
569     Size           nb_adj = nei.size();
570 
571     // check if the node should belong to the simplicial set
572     if (_nb_adjacent_neighbours_[id] == (nb_adj * (nb_adj - 1)) / 2) {
573       if (belong != _Belong_::SIMPLICIAL) {
574         if (belong == _Belong_::ALMOST_SIMPLICIAL)
575           _almost_simplicial_nodes_.erase(id);
576         else if (belong == _Belong_::QUASI_SIMPLICIAL)
577           _quasi_simplicial_nodes_.erase(id);
578 
579         _simplicial_nodes_.insert(id, (*_log_weights_)[id]);
580         belong = _Belong_::SIMPLICIAL;
581       }
582 
583       return;
584     }
585 
586     // check if the node should belong to the almost simplicial set
587     Size nb_almost = ((nb_adj - 1) * (nb_adj - 2)) / 2;
588     Size nb        = _nb_adjacent_neighbours_[id];
589 
590     for (const auto cur: nei) {
591       if (nb_almost == nb - _nb_triangles_[Edge(cur, id)]) {
592         // the node is an almost simplicial node
593         if (belong != _Belong_::ALMOST_SIMPLICIAL) {
594           if (belong == _Belong_::SIMPLICIAL)
595             _simplicial_nodes_.erase(id);
596           else if (belong == _Belong_::QUASI_SIMPLICIAL)
597             _quasi_simplicial_nodes_.erase(id);
598 
599           _almost_simplicial_nodes_.insert(id, (*_log_weights_)[id]);
600           belong = _Belong_::ALMOST_SIMPLICIAL;
601         } else
602           _almost_simplicial_nodes_.setPriority(id, (*_log_weights_)[id]);
603 
604         return;
605       }
606     }
607 
608     // check if the node should belong to the quasi simplicial nodes
609     if (_nb_adjacent_neighbours_[id] / ((nb_adj * (nb_adj - 1)) / 2) >= _quasi_ratio_) {
610       if (belong != _Belong_::QUASI_SIMPLICIAL) {
611         if (belong == _Belong_::SIMPLICIAL)
612           _simplicial_nodes_.erase(id);
613         else if (belong == _Belong_::ALMOST_SIMPLICIAL)
614           _almost_simplicial_nodes_.erase(id);
615 
616         _quasi_simplicial_nodes_.insert(id, (*_log_weights_)[id]);
617         belong = _Belong_::QUASI_SIMPLICIAL;
618       } else
619         _quasi_simplicial_nodes_.setPriority(id, (*_log_weights_)[id]);
620 
621       return;
622     }
623 
624     // the node does not belong to any list, so remove the node from
625     // its current list
626     if (belong == _Belong_::QUASI_SIMPLICIAL)
627       _quasi_simplicial_nodes_.erase(id);
628     else if (belong == _Belong_::ALMOST_SIMPLICIAL)
629       _almost_simplicial_nodes_.erase(id);
630     else if (belong == _Belong_::SIMPLICIAL)
631       _simplicial_nodes_.erase(id);
632 
633     belong = _Belong_::NO_LIST;
634   }
635 
636   /// indicates whether there exists an almost simplicial node
hasAlmostSimplicialNode()637   bool SimplicialSet::hasAlmostSimplicialNode() {
638     // set the limit weight value
639     double limit = _log_tree_width_ + _log_threshold_;
640 
641     // update the elements currently in the almost simplicial list that may
642     // now be contained in another list
643     for (auto iter = _changed_status_.beginSafe();   // safe iterator needed here
644          iter != _changed_status_.endSafe();
645          ++iter) {
646       if (_almost_simplicial_nodes_.contains(*iter)) _updateList_(*iter);
647     }
648 
649     // check the current almost simplicial list
650     if (!_almost_simplicial_nodes_.empty()
651         && ((*_log_weights_)[_almost_simplicial_nodes_.top()] <= limit))
652       return true;
653 
654     // if the almost simplicial list does not contain any node that has a low
655     // weight, check if a node can enter the almost simplicial list
656     for (auto iter = _changed_status_.beginSafe();   // safe iterator needed here
657          iter != _changed_status_.endSafe();
658          ++iter) {
659       _updateList_(*iter);
660 
661       if (!_almost_simplicial_nodes_.empty()
662           && ((*_log_weights_)[_almost_simplicial_nodes_.top()] <= limit))
663         return true;
664     }
665 
666     return false;
667   }
668 
669   /// indicates whether there exists an almost simplicial node
hasSimplicialNode()670   bool SimplicialSet::hasSimplicialNode() {
671     // update the elements currently in the simplicial list that may
672     // now be contained in another list
673     for (auto iter = _changed_status_.beginSafe();   // safe iterator needed here
674          iter != _changed_status_.endSafe();
675          ++iter) {
676       if (_simplicial_nodes_.contains(*iter)) _updateList_(*iter);
677     }
678 
679     // check the current almost simplicial list
680     if (!_simplicial_nodes_.empty()) return true;
681 
682     // if the simplicial list does not contain any node, check if a
683     // node can enter the simplicial list
684     for (auto iter = _changed_status_.beginSafe();   // safe iterator needed here
685          iter != _changed_status_.endSafe();
686          ++iter) {
687       _updateList_(*iter);
688 
689       if (!_simplicial_nodes_.empty()) return true;
690     }
691 
692     return false;
693   }
694 
695   /// indicates whether there exists a quasi simplicial node
hasQuasiSimplicialNode()696   bool SimplicialSet::hasQuasiSimplicialNode() {
697     // set the limit weight value
698     double limit = _log_tree_width_ + _log_threshold_;
699 
700     // update the elements currently in the quasi simplicial list that may
701     // now be contained in another list
702     for (auto iter = _changed_status_.beginSafe();   // safe iterator needed here
703          iter != _changed_status_.endSafe();
704          ++iter) {
705       if (_quasi_simplicial_nodes_.contains(*iter)) _updateList_(*iter);
706     }
707 
708     // check the current quasi simplicial list
709     if (!_quasi_simplicial_nodes_.empty()
710         && ((*_log_weights_)[_quasi_simplicial_nodes_.top()] <= limit))
711       return true;
712 
713     // if the quasi simplicial list does not contain any node that has a low
714     // weight, check if a node can enter the quasi simplicial list
715     for (auto iter = _changed_status_.beginSafe();   // safe iterator needed here
716          iter != _changed_status_.endSafe();
717          ++iter) {
718       _updateList_(*iter);
719 
720       if (!_quasi_simplicial_nodes_.empty()
721           && ((*_log_weights_)[_quasi_simplicial_nodes_.top()] <= limit))
722         return true;
723     }
724 
725     return false;
726   }
727 
728   /** @brief initialize: compute  _nb_triangles_,  _nb_adjacent_neighbours_, etc
729    * when a new graph is set */
_initialize_()730   void SimplicialSet::_initialize_() {
731     // if the  _graph_ is empty, do nothing
732     if (_graph_->size() == 0) return;
733 
734     // set the weights of the nodes and the initial tree_width (min of the
735     // weights)
736     _log_tree_width_ = std::numeric_limits< double >::max();
737     _log_weights_->clear();
738 
739     for (const auto nodeX: *_graph_) {
740       double log_weight = (*_log_domain_sizes_)[nodeX];
741       for (const auto& nei: _graph_->neighbours(nodeX))
742         log_weight += (*_log_domain_sizes_)[nei];
743 
744       _log_weights_->insert(nodeX, log_weight);
745       if (_log_tree_width_ > log_weight) _log_tree_width_ = log_weight;
746     }
747 
748     // initialize the  _nb_triangles_ so that there is no need to check whether
749     //  _nb_triangles_ need new insertions
750     _nb_triangles_           = _graph_->edgesProperty(Size(0));
751     _nb_adjacent_neighbours_ = _graph_->nodesProperty(Size(0));
752     _containing_list_        = _graph_->nodesProperty(_Belong_::NO_LIST);
753     _changed_status_         = _graph_->asNodeSet();
754 
755     // set the  _nb_triangles_ and the  _nb_adjacent_neighbours_: for each
756     // triangle, update the  _nb_triangles_. To count the triangles only once,
757     // parse for each node X the set of its neighbors Y,Z that are adjacent to
758     // each other and such that the Id of Y and Z are greater than X.
759     for (const auto nodeX: *_graph_) {
760       Size&          nb_adjacent_neighbors_idX = _nb_adjacent_neighbours_[nodeX];
761       const NodeSet& nei                       = _graph_->neighbours(nodeX);
762 
763       for (auto iterY = nei.begin(); iterY != nei.end(); ++iterY)
764         if (*iterY > nodeX) {
765           const NodeId node_idY                  = *iterY;
766           Size&        nb_adjacent_neighbors_idY = _nb_adjacent_neighbours_[node_idY];
767 
768           auto iterZ = iterY;
769           for (++iterZ; iterZ != nei.end(); ++iterZ)
770             if ((*iterZ > nodeX) && _graph_->existsEdge(node_idY, *iterZ)) {
771               const NodeId node_idZ = *iterZ;
772               ++nb_adjacent_neighbors_idX;
773               ++nb_adjacent_neighbors_idY;
774               ++_nb_adjacent_neighbours_[node_idZ];
775               ++_nb_triangles_[Edge(nodeX, node_idY)];
776               ++_nb_triangles_[Edge(nodeX, node_idZ)];
777               ++_nb_triangles_[Edge(node_idZ, node_idY)];
778             }
779         }
780     }
781   }
782 
783   /// initialize the simplicial set w.r.t. a new graph
setGraph(UndiGraph * graph,const NodeProperty<double> * log_domain_sizes,NodeProperty<double> * log_weights,double theRatio,double theThreshold)784   void SimplicialSet::setGraph(UndiGraph*                    graph,
785                                const NodeProperty< double >* log_domain_sizes,
786                                NodeProperty< double >*       log_weights,
787                                double                        theRatio,
788                                double                        theThreshold) {
789     // check that the pointers passed in argument are all different from 0
790     if ((graph == nullptr) || (log_domain_sizes == nullptr) || (log_weights == nullptr)) {
791       GUM_ERROR(OperationNotAllowed, "SimplicialSet requires non-null pointers")
792     }
793 
794     // clear the structures used for the previous graph and assign the new graph
795     _graph_            = graph;
796     _log_weights_      = log_weights;
797     _log_domain_sizes_ = log_domain_sizes;
798 
799     _simplicial_nodes_.clear();
800     _almost_simplicial_nodes_.clear();
801     _quasi_simplicial_nodes_.clear();
802     _simplicial_nodes_.resize(_graph_->size());
803     _almost_simplicial_nodes_.resize(_graph_->size());
804     _quasi_simplicial_nodes_.resize(_graph_->size());
805 
806     _containing_list_.clear();
807     _containing_list_.resize(_graph_->size());
808     _nb_triangles_.clear();
809     _nb_triangles_.resize(_graph_->size() * _graph_->size() / 2);
810     _nb_adjacent_neighbours_.clear();
811     _nb_adjacent_neighbours_.resize(_graph_->size());
812 
813     _log_tree_width_ = std::numeric_limits< double >::max();
814     _quasi_ratio_    = theRatio;
815     _log_threshold_  = std::log(1 + theThreshold);
816     _changed_status_.clear();
817     _fill_ins_list_.clear();
818 
819     // end of initialization: compute  _nb_triangles_,  _nb_adjacent_neighbours_,
820     // etc
821     _initialize_();
822   }
823 
824   /// reassigns a new set of cliques' log weights (with the same content)
replaceLogWeights(NodeProperty<double> * old_weights,NodeProperty<double> * new_weights)825   void SimplicialSet::replaceLogWeights(NodeProperty< double >* old_weights,
826                                         NodeProperty< double >* new_weights) {
827     // check that the current weights are the old_weights
828     if (old_weights != _log_weights_)
829       GUM_ERROR(InvalidArgument,
830                 "the old set of weights shall be identical "
831                 "to the current one");
832 
833     _log_weights_ = new_weights;
834   }
835 
836 } /* namespace gum */
837 
838 #endif /* DOXYGEN_SHOULD_SKIP_THIS */
839