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