aGrUM  0.21.0
a C++ library for (probabilistic) graphical models
simplicialSet.cpp
Go to the documentation of this file.
1 /**
2  *
3  * Copyright (c) 2005-2021 by Pierre-Henri WUILLEMIN(@LIP6) & Christophe GONZALES(@AMU)
4  * info_at_agrum_dot_org
5  *
6  * This library is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this library. If not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20 
21 
22 /** @file
23  * @brief source implementations of simplicial set
24  *
25  * @author Christophe GONZALES(@AMU) and Pierre-Henri WUILLEMIN(@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_
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
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
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
150  SimplicialSet::~SimplicialSet() { GUM_DESTRUCTOR(SimplicialSet); }
151 
152  /// adds the necessary edges so that node 'id' and its neighbors form a clique
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_
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_
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_
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
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
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
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
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
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 */
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
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)
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 */