27 #include <agrum/tools/core/math/math_utils.h> 28 #include <agrum/tools/graphs/algorithms/simplicialSet.h> 29 #include <agrum/tools/graphs/graphElements.h> 36 # include <agrum/tools/graphs/algorithms/simplicialSet_inl.h> 39 #ifndef DOXYGEN_SHOULD_SKIP_THIS 55 SimplicialSet::SimplicialSet(UndiGraph* graph,
56 const NodeProperty<
double >* log_domain_sizes,
57 NodeProperty<
double >* log_weights,
59 double theThreshold) :
60 _graph_(graph !=
nullptr 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,
66 "requires non-null log weights")),
67 _log_domain_sizes_(log_domain_sizes !=
nullptr 69 : GUM_ERROR_IN_EXPR(OperationNotAllowed,
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); }
86 SimplicialSet::SimplicialSet(
const SimplicialSet& from,
88 const NodeProperty<
double >* log_domain_sizes,
89 NodeProperty<
double >* log_weights,
91 _graph_(graph !=
nullptr 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,
97 "requires non-null log weights")),
98 _log_domain_sizes_(log_domain_sizes !=
nullptr 100 : GUM_ERROR_IN_EXPR(OperationNotAllowed,
102 "requires non-null domain sizes")) {
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");
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_;
129 GUM_CONS_CPY(SimplicialSet);
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);
150 SimplicialSet::~SimplicialSet() { GUM_DESTRUCTOR(SimplicialSet); }
153 void SimplicialSet::makeClique(
const NodeId id) {
155 if (_changed_status_.contains(id)) _updateList_(id);
168 if (_simplicial_nodes_.contains(id)) {
170 }
else if (_almost_simplicial_nodes_.contains(id)) {
175 const NodeSet& nei = _graph_->neighbours(id);
176 Size nb_adj = nei.size();
177 Size nb = _nb_adjacent_neighbours_[id];
184 Size nb_almost = ((nb_adj - 1) * (nb_adj - 2)) / 2;
187 for (
const auto current_node: nei) {
188 if (nb_almost == nb - _nb_triangles_[Edge(current_node, id)]) {
198 node1 = current_node;
203 double log_domain_size_node1 = (*_log_domain_sizes_)[node1];
204 double& _log_weights_node1_ = (*_log_weights_)[node1];
212 unsigned int nb_n1 = 0;
216 for (
const auto node2: nei) {
217 if ((node2 != node1) && !_graph_->existsEdge(node1, node2)) {
219 const Edge e1_2(node1, node2);
220 _graph_->addEdge(node1, node2);
222 if (_we_want_fill_ins_) _fill_ins_list_.insert(e1_2);
224 if (!_changed_status_.contains(node2)) _changed_status_.insert(node2);
226 _log_weights_node1_ += (*_log_domain_sizes_)[node2];
227 (*_log_weights_)[node2] += log_domain_size_node1;
228 _nb_triangles_.insert(e1_2, 0);
233 unsigned int nb_n2 = 0;
237 if (_graph_->neighbours(node1).size() <= _graph_->neighbours(node2).size()) {
238 for (
const auto neighbor: _graph_->neighbours(node1)) {
239 if (_graph_->existsEdge(neighbor, node2)) {
245 ++_nb_adjacent_neighbours_[neighbor];
246 ++_nb_triangles_[Edge(node1, neighbor)];
247 ++_nb_triangles_[Edge(node2, neighbor)];
249 if (!_changed_status_.contains(neighbor)) _changed_status_.insert(neighbor);
253 for (
const auto neighbor: _graph_->neighbours(node2)) {
254 if (_graph_->existsEdge(neighbor, node1)) {
260 ++_nb_adjacent_neighbours_[neighbor];
261 ++_nb_triangles_[Edge(node2, neighbor)];
262 ++_nb_triangles_[Edge(node1, neighbor)];
264 if (!_changed_status_.contains(neighbor)) _changed_status_.insert(neighbor);
269 _nb_adjacent_neighbours_[node2] += nb_n2;
270 _nb_triangles_[e1_2] += nb_n2;
274 if (!_changed_status_.contains(node1)) _changed_status_.insert(node1);
276 _nb_adjacent_neighbours_[node1] += nb_n1;
282 const NodeSet& nei = _graph_->neighbours(id);
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;
291 auto iterEdge2 = iter1;
293 for (++iterEdge2; iterEdge2 != nei.end(); ++iterEdge2) {
294 const NodeId node2 = *iterEdge2;
295 const Edge e1_2(node1, node2);
297 if (!_graph_->existsEdge(e1_2)) {
299 _graph_->addEdge(node1, node2);
301 if (_we_want_fill_ins_) _fill_ins_list_.insert(e1_2);
303 if (!_changed_status_.contains(node2)) _changed_status_.insert(node2);
306 _log_weights_node1_ += (*_log_domain_sizes_)[node2];
307 (*_log_weights_)[node2] += log_domain_size_node1;
308 _nb_triangles_.insert(e1_2, 0);
312 unsigned int nb_n2 = 0;
314 if (_graph_->neighbours(node1).size() <= _graph_->neighbours(node2).size()) {
315 for (
const auto neighbor: _graph_->neighbours(node1))
316 if (_graph_->existsEdge(neighbor, node2)) {
321 ++_nb_adjacent_neighbours_[neighbor];
322 ++_nb_triangles_[Edge(node1, neighbor)];
323 ++_nb_triangles_[Edge(node2, neighbor)];
325 if (!_changed_status_.contains(neighbor)) _changed_status_.insert(neighbor);
328 for (
const auto neighbor: _graph_->neighbours(node2)) {
329 if (_graph_->existsEdge(neighbor, node1)) {
334 ++_nb_adjacent_neighbours_[neighbor];
335 ++_nb_triangles_[Edge(node2, neighbor)];
336 ++_nb_triangles_[Edge(node1, neighbor)];
338 if (!_changed_status_.contains(neighbor)) _changed_status_.insert(neighbor);
343 _nb_adjacent_neighbours_[node2] += nb_n2;
344 _nb_triangles_[e1_2] += nb_n2;
348 _nb_adjacent_neighbours_[node1] += nb_n1;
350 if (node1_status && !_changed_status_.contains(node1)) _changed_status_.insert(node1);
355 if (!_simplicial_nodes_.contains(id)) {
356 if (_changed_status_.contains(id)) _changed_status_.erase(id);
358 switch (_containing_list_[id]) {
359 case _Belong_::ALMOST_SIMPLICIAL:
360 _almost_simplicial_nodes_.erase(id);
363 case _Belong_::QUASI_SIMPLICIAL:
364 _quasi_simplicial_nodes_.erase(id);
371 _simplicial_nodes_.insert(id, (*_log_weights_)[id]);
372 _containing_list_[id] = _Belong_::SIMPLICIAL;
374 if (_changed_status_.contains(id)) { _changed_status_.erase(id); }
379 void SimplicialSet::eraseClique(
const NodeId id) {
381 if (!_graph_->exists(id)) {
382 GUM_ERROR(NotFound,
"Node " << id <<
" does not belong to the graph")
385 const NodeSet nei = _graph_->neighbours(id);
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")
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;
400 if (!_changed_status_.contains(node1)) _changed_status_.insert(node1);
402 _nb_triangles_.erase(Edge(node1, id));
405 for (++iter2; iter2 != nei.end(); ++iter2)
406 --_nb_triangles_[Edge(node1, *iter2)];
409 _log_tree_width_ = std::max(_log_tree_width_, (*_log_weights_)[id]);
411 switch (_containing_list_[id]) {
412 case _Belong_::SIMPLICIAL:
413 _simplicial_nodes_.erase(id);
416 case _Belong_::ALMOST_SIMPLICIAL:
417 _almost_simplicial_nodes_.erase(id);
420 case _Belong_::QUASI_SIMPLICIAL:
421 _quasi_simplicial_nodes_.erase(id);
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);
436 void SimplicialSet::eraseNode(
const NodeId id) {
438 if (!_graph_->exists(id)) {
439 GUM_ERROR(NotFound,
"Node " << id <<
" does not belong to the graph")
443 const NodeSet& nei = _graph_->neighbours(id);
445 for (
auto iter = nei.beginSafe(); iter != nei.endSafe();
447 eraseEdge(Edge(*iter, id));
449 switch (_containing_list_[id]) {
450 case _Belong_::SIMPLICIAL:
451 _simplicial_nodes_.erase(id);
454 case _Belong_::ALMOST_SIMPLICIAL:
455 _almost_simplicial_nodes_.erase(id);
458 case _Belong_::QUASI_SIMPLICIAL:
459 _quasi_simplicial_nodes_.erase(id);
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);
474 void SimplicialSet::eraseEdge(
const Edge& edge) {
476 if (!_graph_->existsEdge(edge)) {
477 GUM_ERROR(NotFound,
"Edge " << edge <<
" does not belong to the graph")
481 const NodeId node1 = edge.first();
482 const NodeId node2 = edge.second();
485 _graph_->eraseEdge(edge);
486 _nb_triangles_.erase(edge);
489 (*_log_weights_)[node1] -= (*_log_domain_sizes_)[node2];
490 (*_log_weights_)[node2] -= (*_log_domain_sizes_)[node1];
493 unsigned int nb_neigh_n1_n2 = 0;
494 for (
const auto othernode: _graph_->neighbours(node1)) {
495 if (_graph_->existsEdge(node2, othernode)) {
498 --_nb_triangles_[Edge(node1, othernode)];
499 --_nb_triangles_[Edge(node2, othernode)];
502 --_nb_adjacent_neighbours_[othernode];
504 if (!_changed_status_.contains(othernode)) _changed_status_.insert(othernode);
508 _nb_adjacent_neighbours_[node1] -= nb_neigh_n1_n2;
509 _nb_adjacent_neighbours_[node2] -= nb_neigh_n1_n2;
511 if (!_changed_status_.contains(node1)) _changed_status_.insert(node1);
512 if (!_changed_status_.contains(node2)) _changed_status_.insert(node2);
516 void SimplicialSet::addEdge(NodeId node1, NodeId node2) {
518 const Edge edge(node1, node2);
520 if (_graph_->existsEdge(edge))
return;
523 (*_log_weights_)[node1] += (*_log_domain_sizes_)[node2];
524 (*_log_weights_)[node2] += (*_log_domain_sizes_)[node1];
526 unsigned int nb_triangle_in_new_edge = 0;
527 unsigned int nb_neigh_n1_n2 = 0;
529 for (
const auto othernode: _graph_->neighbours(node1)) {
530 if (_graph_->existsEdge(node2, othernode)) {
533 ++_nb_triangles_[Edge(node1, othernode)];
534 ++_nb_triangles_[Edge(node2, othernode)];
535 ++nb_triangle_in_new_edge;
539 ++_nb_adjacent_neighbours_[othernode];
541 if (!_changed_status_.contains(othernode)) _changed_status_.insert(othernode);
545 _nb_adjacent_neighbours_[node1] += nb_neigh_n1_n2;
546 _nb_adjacent_neighbours_[node2] += nb_neigh_n1_n2;
549 _graph_->addEdge(node1, node2);
550 _nb_triangles_.insert(edge, nb_triangle_in_new_edge);
552 if (!_changed_status_.contains(node1)) _changed_status_.insert(node1);
553 if (!_changed_status_.contains(node2)) _changed_status_.insert(node2);
558 void SimplicialSet::_updateList_(
const NodeId id) {
560 if (!_graph_->exists(id)) { GUM_ERROR(NotFound,
"Node " << id <<
" could not be found") }
563 if (!_changed_status_.contains(id))
return;
565 _changed_status_.erase(id);
567 _Belong_& belong = _containing_list_[id];
568 const NodeSet& nei = _graph_->neighbours(id);
569 Size nb_adj = nei.size();
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);
579 _simplicial_nodes_.insert(id, (*_log_weights_)[id]);
580 belong = _Belong_::SIMPLICIAL;
587 Size nb_almost = ((nb_adj - 1) * (nb_adj - 2)) / 2;
588 Size nb = _nb_adjacent_neighbours_[id];
590 for (
const auto cur: nei) {
591 if (nb_almost == nb - _nb_triangles_[Edge(cur, id)]) {
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);
599 _almost_simplicial_nodes_.insert(id, (*_log_weights_)[id]);
600 belong = _Belong_::ALMOST_SIMPLICIAL;
602 _almost_simplicial_nodes_.setPriority(id, (*_log_weights_)[id]);
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);
616 _quasi_simplicial_nodes_.insert(id, (*_log_weights_)[id]);
617 belong = _Belong_::QUASI_SIMPLICIAL;
619 _quasi_simplicial_nodes_.setPriority(id, (*_log_weights_)[id]);
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);
633 belong = _Belong_::NO_LIST;
637 bool SimplicialSet::hasAlmostSimplicialNode() {
639 double limit = _log_tree_width_ + _log_threshold_;
643 for (
auto iter = _changed_status_.beginSafe();
644 iter != _changed_status_.endSafe();
646 if (_almost_simplicial_nodes_.contains(*iter)) _updateList_(*iter);
650 if (!_almost_simplicial_nodes_.empty()
651 && ((*_log_weights_)[_almost_simplicial_nodes_.top()] <= limit))
656 for (
auto iter = _changed_status_.beginSafe();
657 iter != _changed_status_.endSafe();
661 if (!_almost_simplicial_nodes_.empty()
662 && ((*_log_weights_)[_almost_simplicial_nodes_.top()] <= limit))
670 bool SimplicialSet::hasSimplicialNode() {
673 for (
auto iter = _changed_status_.beginSafe();
674 iter != _changed_status_.endSafe();
676 if (_simplicial_nodes_.contains(*iter)) _updateList_(*iter);
680 if (!_simplicial_nodes_.empty())
return true;
684 for (
auto iter = _changed_status_.beginSafe();
685 iter != _changed_status_.endSafe();
689 if (!_simplicial_nodes_.empty())
return true;
696 bool SimplicialSet::hasQuasiSimplicialNode() {
698 double limit = _log_tree_width_ + _log_threshold_;
702 for (
auto iter = _changed_status_.beginSafe();
703 iter != _changed_status_.endSafe();
705 if (_quasi_simplicial_nodes_.contains(*iter)) _updateList_(*iter);
709 if (!_quasi_simplicial_nodes_.empty()
710 && ((*_log_weights_)[_quasi_simplicial_nodes_.top()] <= limit))
715 for (
auto iter = _changed_status_.beginSafe();
716 iter != _changed_status_.endSafe();
720 if (!_quasi_simplicial_nodes_.empty()
721 && ((*_log_weights_)[_quasi_simplicial_nodes_.top()] <= limit))
730 void SimplicialSet::_initialize_() {
732 if (_graph_->size() == 0)
return;
736 _log_tree_width_ = std::numeric_limits<
double >::max();
737 _log_weights_->clear();
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];
744 _log_weights_->insert(nodeX, log_weight);
745 if (_log_tree_width_ > log_weight) _log_tree_width_ = log_weight;
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();
759 for (
const auto nodeX: *_graph_) {
760 Size& nb_adjacent_neighbors_idX = _nb_adjacent_neighbours_[nodeX];
761 const NodeSet& nei = _graph_->neighbours(nodeX);
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];
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)];
784 void SimplicialSet::setGraph(UndiGraph* graph,
785 const NodeProperty<
double >* log_domain_sizes,
786 NodeProperty<
double >* log_weights,
788 double theThreshold) {
790 if ((graph ==
nullptr) || (log_domain_sizes ==
nullptr) || (log_weights ==
nullptr)) {
791 GUM_ERROR(OperationNotAllowed,
"SimplicialSet requires non-null pointers")
796 _log_weights_ = log_weights;
797 _log_domain_sizes_ = log_domain_sizes;
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());
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());
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();
825 void SimplicialSet::replaceLogWeights(NodeProperty<
double >* old_weights,
826 NodeProperty<
double >* new_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");
833 _log_weights_ = new_weights;