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