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,
63 "SimplicialSet requires a valid graph")),
64 log_weights__(log_weights !=
nullptr 66 : GUM_ERROR_IN_EXPR(OperationNotAllowed,
68 "requires non-null log weights")),
69 log_domain_sizes__(log_domain_sizes !=
nullptr 71 : GUM_ERROR_IN_EXPR(OperationNotAllowed,
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) {
83 GUM_CONSTRUCTOR(SimplicialSet);
92 SimplicialSet::SimplicialSet(
const SimplicialSet& from,
94 const NodeProperty<
double >* log_domain_sizes,
95 NodeProperty<
double >* log_weights,
97 graph__(graph !=
nullptr 99 : GUM_ERROR_IN_EXPR(OperationNotAllowed,
100 "SimplicialSet requires a valid graph")),
101 log_weights__(log_weights !=
nullptr 103 : GUM_ERROR_IN_EXPR(OperationNotAllowed,
105 "requires non-null log weights")),
107 log_domain_sizes !=
nullptr 109 : GUM_ERROR_IN_EXPR(OperationNotAllowed,
111 "requires non-null domain sizes")) {
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");
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__;
140 GUM_CONS_CPY(SimplicialSet);
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__)) {
159 GUM_CONS_MOV(SimplicialSet);
163 SimplicialSet::~SimplicialSet() {
165 GUM_DESTRUCTOR(SimplicialSet);
169 void SimplicialSet::makeClique(
const NodeId id) {
171 if (changed_status__.contains(id)) updateList__(id);
184 if (simplicial_nodes__.contains(id)) {
186 }
else if (almost_simplicial_nodes__.contains(id)) {
191 const NodeSet& nei = graph__->neighbours(id);
192 Size nb_adj = nei.size();
193 Size nb = nb_adjacent_neighbours__[id];
200 Size nb_almost = ((nb_adj - 1) * (nb_adj - 2)) / 2;
203 for (
const auto current_node: nei) {
204 if (nb_almost == nb - nb_triangles__[Edge(current_node, id)]) {
214 node1 = current_node;
219 double log_domain_size_node1 = (*log_domain_sizes__)[node1];
220 double& log_weights_node1__ = (*log_weights__)[node1];
228 unsigned int nb_n1 = 0;
232 for (
const auto node2: nei) {
233 if ((node2 != node1) && !graph__->existsEdge(node1, node2)) {
235 const Edge e1_2(node1, node2);
236 graph__->addEdge(node1, node2);
238 if (we_want_fill_ins__) fill_ins_list__.insert(e1_2);
240 if (!changed_status__.contains(node2)) changed_status__.insert(node2);
242 log_weights_node1__ += (*log_domain_sizes__)[node2];
243 (*log_weights__)[node2] += log_domain_size_node1;
244 nb_triangles__.insert(e1_2, 0);
249 unsigned int nb_n2 = 0;
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)) {
262 ++nb_adjacent_neighbours__[neighbor];
263 ++nb_triangles__[Edge(node1, neighbor)];
264 ++nb_triangles__[Edge(node2, neighbor)];
266 if (!changed_status__.contains(neighbor))
267 changed_status__.insert(neighbor);
271 for (
const auto neighbor: graph__->neighbours(node2)) {
272 if (graph__->existsEdge(neighbor, node1)) {
278 ++nb_adjacent_neighbours__[neighbor];
279 ++nb_triangles__[Edge(node2, neighbor)];
280 ++nb_triangles__[Edge(node1, neighbor)];
282 if (!changed_status__.contains(neighbor))
283 changed_status__.insert(neighbor);
288 nb_adjacent_neighbours__[node2] += nb_n2;
289 nb_triangles__[e1_2] += nb_n2;
293 if (!changed_status__.contains(node1)) changed_status__.insert(node1);
295 nb_adjacent_neighbours__[node1] += nb_n1;
301 const NodeSet& nei = graph__->neighbours(id);
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;
310 auto iterEdge2 = iter1;
312 for (++iterEdge2; iterEdge2 != nei.end(); ++iterEdge2) {
313 const NodeId node2 = *iterEdge2;
314 const Edge e1_2(node1, node2);
316 if (!graph__->existsEdge(e1_2)) {
318 graph__->addEdge(node1, node2);
320 if (we_want_fill_ins__) fill_ins_list__.insert(e1_2);
322 if (!changed_status__.contains(node2)) changed_status__.insert(node2);
325 log_weights_node1__ += (*log_domain_sizes__)[node2];
326 (*log_weights__)[node2] += log_domain_size_node1;
327 nb_triangles__.insert(e1_2, 0);
331 unsigned int nb_n2 = 0;
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)) {
341 ++nb_adjacent_neighbours__[neighbor];
342 ++nb_triangles__[Edge(node1, neighbor)];
343 ++nb_triangles__[Edge(node2, neighbor)];
345 if (!changed_status__.contains(neighbor))
346 changed_status__.insert(neighbor);
349 for (
const auto neighbor: graph__->neighbours(node2)) {
350 if (graph__->existsEdge(neighbor, node1)) {
355 ++nb_adjacent_neighbours__[neighbor];
356 ++nb_triangles__[Edge(node2, neighbor)];
357 ++nb_triangles__[Edge(node1, neighbor)];
359 if (!changed_status__.contains(neighbor))
360 changed_status__.insert(neighbor);
365 nb_adjacent_neighbours__[node2] += nb_n2;
366 nb_triangles__[e1_2] += nb_n2;
370 nb_adjacent_neighbours__[node1] += nb_n1;
372 if (node1_status && !changed_status__.contains(node1))
373 changed_status__.insert(node1);
378 if (!simplicial_nodes__.contains(id)) {
379 if (changed_status__.contains(id)) changed_status__.erase(id);
381 switch (containing_list__[id]) {
382 case Belong__::ALMOST_SIMPLICIAL:
383 almost_simplicial_nodes__.erase(id);
386 case Belong__::QUASI_SIMPLICIAL:
387 quasi_simplicial_nodes__.erase(id);
394 simplicial_nodes__.insert(id, (*log_weights__)[id]);
395 containing_list__[id] = Belong__::SIMPLICIAL;
397 if (changed_status__.contains(id)) { changed_status__.erase(id); }
402 void SimplicialSet::eraseClique(
const NodeId id) {
404 if (!graph__->exists(id)) {
405 GUM_ERROR(NotFound,
"Node " << id <<
" does not belong to the graph");
408 const NodeSet nei = graph__->neighbours(id);
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");
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;
423 if (!changed_status__.contains(node1)) changed_status__.insert(node1);
425 nb_triangles__.erase(Edge(node1, id));
428 for (++iter2; iter2 != nei.end(); ++iter2)
429 --nb_triangles__[Edge(node1, *iter2)];
432 log_tree_width__ = std::max(log_tree_width__, (*log_weights__)[id]);
434 switch (containing_list__[id]) {
435 case Belong__::SIMPLICIAL:
436 simplicial_nodes__.erase(id);
439 case Belong__::ALMOST_SIMPLICIAL:
440 almost_simplicial_nodes__.erase(id);
443 case Belong__::QUASI_SIMPLICIAL:
444 quasi_simplicial_nodes__.erase(id);
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);
459 void SimplicialSet::eraseNode(
const NodeId id) {
461 if (!graph__->exists(id)) {
462 GUM_ERROR(NotFound,
"Node " << id <<
" does not belong to the graph");
466 const NodeSet& nei = graph__->neighbours(id);
468 for (
auto iter = nei.beginSafe(); iter != nei.endSafe();
470 eraseEdge(Edge(*iter, id));
472 switch (containing_list__[id]) {
473 case Belong__::SIMPLICIAL:
474 simplicial_nodes__.erase(id);
477 case Belong__::ALMOST_SIMPLICIAL:
478 almost_simplicial_nodes__.erase(id);
481 case Belong__::QUASI_SIMPLICIAL:
482 quasi_simplicial_nodes__.erase(id);
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);
497 void SimplicialSet::eraseEdge(
const Edge& edge) {
499 if (!graph__->existsEdge(edge)) {
500 GUM_ERROR(NotFound,
"Edge " << edge <<
" does not belong to the graph");
504 const NodeId node1 = edge.first();
505 const NodeId node2 = edge.second();
508 graph__->eraseEdge(edge);
509 nb_triangles__.erase(edge);
512 (*log_weights__)[node1] -= (*log_domain_sizes__)[node2];
513 (*log_weights__)[node2] -= (*log_domain_sizes__)[node1];
516 unsigned int nb_neigh_n1_n2 = 0;
517 for (
const auto othernode: graph__->neighbours(node1)) {
518 if (graph__->existsEdge(node2, othernode)) {
521 --nb_triangles__[Edge(node1, othernode)];
522 --nb_triangles__[Edge(node2, othernode)];
525 --nb_adjacent_neighbours__[othernode];
527 if (!changed_status__.contains(othernode))
528 changed_status__.insert(othernode);
532 nb_adjacent_neighbours__[node1] -= nb_neigh_n1_n2;
533 nb_adjacent_neighbours__[node2] -= nb_neigh_n1_n2;
535 if (!changed_status__.contains(node1)) changed_status__.insert(node1);
536 if (!changed_status__.contains(node2)) changed_status__.insert(node2);
540 void SimplicialSet::addEdge(NodeId node1, NodeId node2) {
542 const Edge edge(node1, node2);
544 if (graph__->existsEdge(edge))
return;
547 (*log_weights__)[node1] += (*log_domain_sizes__)[node2];
548 (*log_weights__)[node2] += (*log_domain_sizes__)[node1];
550 unsigned int nb_triangle_in_new_edge = 0;
551 unsigned int nb_neigh_n1_n2 = 0;
553 for (
const auto othernode: graph__->neighbours(node1)) {
554 if (graph__->existsEdge(node2, othernode)) {
557 ++nb_triangles__[Edge(node1, othernode)];
558 ++nb_triangles__[Edge(node2, othernode)];
559 ++nb_triangle_in_new_edge;
563 ++nb_adjacent_neighbours__[othernode];
565 if (!changed_status__.contains(othernode))
566 changed_status__.insert(othernode);
570 nb_adjacent_neighbours__[node1] += nb_neigh_n1_n2;
571 nb_adjacent_neighbours__[node2] += nb_neigh_n1_n2;
574 graph__->addEdge(node1, node2);
575 nb_triangles__.insert(edge, nb_triangle_in_new_edge);
577 if (!changed_status__.contains(node1)) changed_status__.insert(node1);
578 if (!changed_status__.contains(node2)) changed_status__.insert(node2);
583 void SimplicialSet::updateList__(
const NodeId id) {
585 if (!graph__->exists(id)) {
586 GUM_ERROR(NotFound,
"Node " << id <<
" could not be found");
590 if (!changed_status__.contains(id))
return;
592 changed_status__.erase(id);
594 Belong__& belong = containing_list__[id];
595 const NodeSet& nei = graph__->neighbours(id);
596 Size nb_adj = nei.size();
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);
606 simplicial_nodes__.insert(id, (*log_weights__)[id]);
607 belong = Belong__::SIMPLICIAL;
614 Size nb_almost = ((nb_adj - 1) * (nb_adj - 2)) / 2;
615 Size nb = nb_adjacent_neighbours__[id];
617 for (
const auto cur: nei) {
618 if (nb_almost == nb - nb_triangles__[Edge(cur, id)]) {
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);
626 almost_simplicial_nodes__.insert(id, (*log_weights__)[id]);
627 belong = Belong__::ALMOST_SIMPLICIAL;
629 almost_simplicial_nodes__.setPriority(id, (*log_weights__)[id]);
636 if (nb_adjacent_neighbours__[id] / ((nb_adj * (nb_adj - 1)) / 2)
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);
644 quasi_simplicial_nodes__.insert(id, (*log_weights__)[id]);
645 belong = Belong__::QUASI_SIMPLICIAL;
647 quasi_simplicial_nodes__.setPriority(id, (*log_weights__)[id]);
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);
661 belong = Belong__::NO_LIST;
665 bool SimplicialSet::hasAlmostSimplicialNode() {
667 double limit = log_tree_width__ + log_threshold__;
671 for (
auto iter = changed_status__.beginSafe();
672 iter != changed_status__.endSafe();
674 if (almost_simplicial_nodes__.contains(*iter)) updateList__(*iter);
678 if (!almost_simplicial_nodes__.empty()
679 && ((*log_weights__)[almost_simplicial_nodes__.top()] <= limit))
684 for (
auto iter = changed_status__.beginSafe();
685 iter != changed_status__.endSafe();
689 if (!almost_simplicial_nodes__.empty()
690 && ((*log_weights__)[almost_simplicial_nodes__.top()] <= limit))
698 bool SimplicialSet::hasSimplicialNode() {
701 for (
auto iter = changed_status__.beginSafe();
702 iter != changed_status__.endSafe();
704 if (simplicial_nodes__.contains(*iter)) updateList__(*iter);
708 if (!simplicial_nodes__.empty())
return true;
712 for (
auto iter = changed_status__.beginSafe();
713 iter != changed_status__.endSafe();
717 if (!simplicial_nodes__.empty())
return true;
724 bool SimplicialSet::hasQuasiSimplicialNode() {
726 double limit = log_tree_width__ + log_threshold__;
730 for (
auto iter = changed_status__.beginSafe();
731 iter != changed_status__.endSafe();
733 if (quasi_simplicial_nodes__.contains(*iter)) updateList__(*iter);
737 if (!quasi_simplicial_nodes__.empty()
738 && ((*log_weights__)[quasi_simplicial_nodes__.top()] <= limit))
743 for (
auto iter = changed_status__.beginSafe();
744 iter != changed_status__.endSafe();
748 if (!quasi_simplicial_nodes__.empty()
749 && ((*log_weights__)[quasi_simplicial_nodes__.top()] <= limit))
758 void SimplicialSet::initialize__() {
760 if (graph__->size() == 0)
return;
764 log_tree_width__ = std::numeric_limits<
double >::max();
765 log_weights__->clear();
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];
772 log_weights__->insert(nodeX, log_weight);
773 if (log_tree_width__ > log_weight) log_tree_width__ = log_weight;
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();
787 for (
const auto nodeX: *graph__) {
788 Size& nb_adjacent_neighbors_idX = nb_adjacent_neighbours__[nodeX];
789 const NodeSet& nei = graph__->neighbours(nodeX);
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];
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)];
812 void SimplicialSet::setGraph(UndiGraph* graph,
813 const NodeProperty<
double >* log_domain_sizes,
814 NodeProperty<
double >* log_weights,
816 double theThreshold) {
818 if ((graph ==
nullptr) || (log_domain_sizes ==
nullptr)
819 || (log_weights ==
nullptr)) {
820 GUM_ERROR(OperationNotAllowed,
"SimplicialSet requires non-null pointers");
825 log_weights__ = log_weights;
826 log_domain_sizes__ = log_domain_sizes;
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());
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());
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();
854 void SimplicialSet::replaceLogWeights(NodeProperty<
double >* old_weights,
855 NodeProperty<
double >* new_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");
862 log_weights__ = new_weights;