24 #ifndef OR_TOOLS_GRAPH_CLIQUES_H_ 25 #define OR_TOOLS_GRAPH_CLIQUES_H_ 31 #include "absl/strings/str_cat.h" 32 #include "ortools/base/int_type.h" 33 #include "ortools/base/int_type_indexed_vector.h" 34 #include "ortools/base/logging.h" 35 #include "ortools/util/time_limit.h" 44 void FindCliques(std::function<
bool(
int,
int)> graph,
int node_count,
45 std::function<
bool(
const std::vector<int>&)> callback);
54 std::function<
bool(
const std::vector<int>&)> callback);
142 template <
typename NodeIndex>
165 : is_arc_(std::move(is_arc)),
166 clique_callback_(std::move(clique_callback)),
167 num_nodes_(num_nodes) {}
192 TimeLimit* time_limit);
208 DEFINE_INT_TYPE(CandidateIndex, ptrdiff_t);
220 State(
const State& other)
221 : pivot(other.pivot),
222 num_remaining_candidates(other.num_remaining_candidates),
223 candidates(other.candidates),
224 first_candidate_index(other.first_candidate_index),
225 candidate_for_recursion(other.candidate_for_recursion) {}
227 State& operator=(
const State& other) {
229 num_remaining_candidates = other.num_remaining_candidates;
230 candidates = other.candidates;
231 first_candidate_index = other.first_candidate_index;
232 candidate_for_recursion = other.candidate_for_recursion;
239 inline void MoveFirstCandidateToNotSet() {
240 ++first_candidate_index;
241 --num_remaining_candidates;
245 std::string DebugString() {
247 absl::StrAppend(&buffer,
"pivot = ", pivot,
248 "\nnum_remaining_candidates = ", num_remaining_candidates,
250 for (CandidateIndex i(0); i < candidates.size(); ++i) {
251 if (i > 0) buffer +=
", ";
252 absl::StrAppend(&buffer, candidates[i]);
255 &buffer,
"]\nfirst_candidate_index = ", first_candidate_index.value(),
256 "\ncandidate_for_recursion = ", candidate_for_recursion.value());
265 int num_remaining_candidates;
274 gtl::ITIVector<CandidateIndex, NodeIndex> candidates;
278 CandidateIndex first_candidate_index;
282 CandidateIndex candidate_for_recursion;
295 static const double kPushStateDeterministicTimeSecondsPerCandidate;
311 void InitializeState(State* state);
315 return node1 == node2 || is_arc_(node1, node2);
321 CandidateIndex SelectCandidateIndexForRecursion(State* state);
324 std::string CliqueDebugString(
const std::vector<NodeIndex>& clique);
342 std::vector<State> states_;
345 std::vector<NodeIndex> current_clique_;
349 int64 num_remaining_iterations_;
353 TimeLimit* time_limit_;
356 template <
typename NodeIndex>
357 void BronKerboschAlgorithm<NodeIndex>::InitializeState(State* state) {
358 DCHECK(state !=
nullptr);
359 const int num_candidates = state->candidates.size();
360 int num_disconnected_candidates = num_candidates;
362 CandidateIndex pivot_index(-1);
363 for (CandidateIndex pivot_candidate_index(0);
364 pivot_candidate_index < num_candidates &&
365 num_disconnected_candidates > 0;
366 ++pivot_candidate_index) {
367 const NodeIndex pivot_candidate = state->candidates[pivot_candidate_index];
369 for (CandidateIndex i(state->first_candidate_index); i < num_candidates;
371 if (!IsArc(pivot_candidate, state->candidates[i])) {
375 if (count < num_disconnected_candidates) {
376 pivot_index = pivot_candidate_index;
377 state->pivot = pivot_candidate;
378 num_disconnected_candidates = count;
381 state->num_remaining_candidates = num_disconnected_candidates;
382 if (pivot_index >= state->first_candidate_index) {
383 std::swap(state->candidates[pivot_index],
384 state->candidates[state->first_candidate_index]);
385 ++state->num_remaining_candidates;
389 template <
typename NodeIndex>
390 typename BronKerboschAlgorithm<NodeIndex>::CandidateIndex
391 BronKerboschAlgorithm<NodeIndex>::SelectCandidateIndexForRecursion(
393 DCHECK(state !=
nullptr);
394 CandidateIndex disconnected_node_index =
395 std::max(state->first_candidate_index, state->candidate_for_recursion);
396 while (disconnected_node_index < state->candidates.size() &&
397 state->candidates[disconnected_node_index] != state->pivot &&
398 IsArc(state->pivot, state->candidates[disconnected_node_index])) {
399 ++disconnected_node_index;
401 state->candidate_for_recursion = disconnected_node_index;
402 return disconnected_node_index;
405 template <
typename NodeIndex>
406 void BronKerboschAlgorithm<NodeIndex>::Initialize() {
407 DCHECK(states_.empty());
408 states_.reserve(num_nodes_);
409 states_.emplace_back();
411 State*
const root_state = &states_.back();
412 root_state->first_candidate_index = 0;
413 root_state->candidate_for_recursion = 0;
414 root_state->candidates.resize(num_nodes_, 0);
415 std::iota(root_state->candidates.begin(), root_state->candidates.end(), 0);
416 root_state->num_remaining_candidates = num_nodes_;
417 InitializeState(root_state);
419 DVLOG(2) <<
"Initialized";
422 template <
typename NodeIndex>
423 void BronKerboschAlgorithm<NodeIndex>::PopState() {
424 DCHECK(!states_.empty());
426 if (!states_.empty()) {
427 State*
const state = &states_.back();
428 current_clique_.pop_back();
429 state->MoveFirstCandidateToNotSet();
433 template <
typename NodeIndex>
434 std::string BronKerboschAlgorithm<NodeIndex>::CliqueDebugString(
435 const std::vector<NodeIndex>& clique) {
436 std::string message =
"Clique: [ ";
438 absl::StrAppend(&message, node,
" ");
444 template <
typename NodeIndex>
445 void BronKerboschAlgorithm<NodeIndex>::PushState(
NodeIndex selected) {
446 DCHECK(!states_.empty());
447 DCHECK(time_limit_ !=
nullptr);
448 DVLOG(2) <<
"PushState: New depth = " << states_.size() + 1
449 <<
", selected node = " << selected;
450 gtl::ITIVector<CandidateIndex, NodeIndex> new_candidates;
452 State*
const previous_state = &states_.back();
453 const double deterministic_time =
454 kPushStateDeterministicTimeSecondsPerCandidate *
455 previous_state->candidates.size();
456 time_limit_->AdvanceDeterministicTime(deterministic_time,
"PushState");
463 new_candidates.reserve(previous_state->candidates.size());
464 for (CandidateIndex i(0); i < previous_state->first_candidate_index; ++i) {
465 const NodeIndex candidate = previous_state->candidates[i];
466 if (IsArc(selected, candidate)) {
467 new_candidates.push_back(candidate);
470 const CandidateIndex new_first_candidate_index(new_candidates.size());
471 for (CandidateIndex i = previous_state->first_candidate_index + 1;
472 i < previous_state->candidates.size(); ++i) {
473 const NodeIndex candidate = previous_state->candidates[i];
474 if (IsArc(selected, candidate)) {
475 new_candidates.push_back(candidate);
479 current_clique_.push_back(selected);
480 if (new_candidates.empty()) {
483 DVLOG(2) << CliqueDebugString(current_clique_);
484 const CliqueResponse response = clique_callback_(current_clique_);
489 num_remaining_iterations_ = 1;
491 current_clique_.pop_back();
492 previous_state->MoveFirstCandidateToNotSet();
499 states_.emplace_back();
500 State*
const new_state = &states_.back();
501 new_state->candidates.swap(new_candidates);
502 new_state->first_candidate_index = new_first_candidate_index;
504 InitializeState(new_state);
507 template <
typename NodeIndex>
509 int64 max_num_iterations, TimeLimit* time_limit) {
510 CHECK(time_limit !=
nullptr);
511 time_limit_ = time_limit;
512 if (states_.empty()) {
515 for (num_remaining_iterations_ = max_num_iterations;
516 !states_.empty() && num_remaining_iterations_ > 0 &&
517 !time_limit->LimitReached();
518 --num_remaining_iterations_) {
519 State*
const state = &states_.back();
520 DVLOG(2) <<
"Loop: " << states_.size() <<
" states, " 521 << state->num_remaining_candidates <<
" candidate to explore\n" 522 << state->DebugString();
523 if (state->num_remaining_candidates == 0) {
528 const CandidateIndex selected_index =
529 SelectCandidateIndexForRecursion(state);
530 DVLOG(2) <<
"selected_index = " << selected_index;
531 const NodeIndex selected = state->candidates[selected_index];
532 DVLOG(2) <<
"Selected candidate = " << selected;
534 NodeIndex& f = state->candidates[state->first_candidate_index];
535 NodeIndex& s = state->candidates[selected_index];
540 time_limit_ =
nullptr;
545 template <
typename NodeIndex>
547 int64 max_num_iterations) {
548 TimeLimit time_limit(std::numeric_limits<double>::infinity());
549 return RunWithTimeLimit(max_num_iterations, &time_limit);
552 template <
typename NodeIndex>
554 return RunIterations(kint64max);
557 template <
typename NodeIndex>
559 NodeIndex>::kPushStateDeterministicTimeSecondsPerCandidate = 0.54663e-7;
562 #endif // OR_TOOLS_GRAPH_CLIQUES_H_ void FindCliques(std::function< bool(int, int)> graph, int node_count, std::function< bool(const std::vector< int > &)> callback)
Finds all maximal cliques, even of size 1, in the graph described by the graph callback.
BronKerboschAlgorithmStatus
The status value returned by BronKerboschAlgorithm::Run and BronKerboschAlgorithm::RunIterations.
BronKerboschAlgorithmStatus RunWithTimeLimit(int64 max_num_iterations, TimeLimit *time_limit)
Runs at most 'max_num_iterations' iterations of the Bron-Kerbosch algorithm, until the time limit is ...
CliqueResponse
Possible return values of the callback for reporting cliques.
std::function< bool(NodeIndex, NodeIndex)> IsArcCallback
A callback called by the algorithm to test if there is an arc between a pair of nodes.
BronKerboschAlgorithmStatus RunWithTimeLimit(TimeLimit *time_limit)
Runs the Bron-Kerbosch algorithm for at most kint64max iterations, until the time limit is excceded o...
Implements the Bron-Kerbosch algorithm for finding maximal cliques.
void CoverArcsByCliques(std::function< bool(int, int)> graph, int node_count, std::function< bool(const std::vector< int > &)> callback)
Covers the maximum number of arcs of the graph with cliques.
BronKerboschAlgorithmStatus Run()
Runs the Bron-Kerbosch algorithm for kint64max iterations.
The algorithm will stop the search immediately.
BronKerboschAlgorithm(IsArcCallback is_arc, NodeIndex num_nodes, CliqueCallback clique_callback)
Initializes the Bron-Kerbosch algorithm for the given graph and clique callback function.
The algorithm will continue searching for other maximal cliques.
The algorithm has enumerated all maximal cliques.
BronKerboschAlgorithmStatus RunIterations(int64 max_num_iterations)
Runs at most 'max_num_iterations' iterations of the Bron-Kerbosch algorithm.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in c...
The search algorithm was interrupted either because it reached the iteration limit or because the cli...
int32 NodeIndex
Standard instantiation of ForwardEbertGraph (named 'ForwardStarGraph') of EbertGraph (named 'StarGrap...
std::function< CliqueResponse(const std::vector< NodeIndex > &)> CliqueCallback
A callback called by the algorithm to report a maximal clique to the user.