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