OR-Tools  8.1
cliques.cc
Go to the documentation of this file.
1 // Copyright 2010-2018 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
14 #include "ortools/graph/cliques.h"
15 
16 #include <algorithm>
17 #include <memory>
18 #include <utility>
19 #include <vector>
20 
21 #include "absl/container/flat_hash_set.h"
22 #include "ortools/base/hash.h"
23 
24 namespace operations_research {
25 namespace {
26 // Encapsulates graph() to make all nodes self-connected.
27 inline bool Connects(std::function<bool(int, int)> graph, int i, int j) {
28  return i == j || graph(i, j);
29 }
30 
31 // Implements the recursive step of the Bron-Kerbosch algorithm with pivoting.
32 // - graph is a callback such that graph->Run(i, j) returns true iff there is an
33 // arc between i and j.
34 // - callback is a callback called for all maximal cliques discovered by the
35 // algorithm.
36 // - input_candidates is an array that contains the list of nodes connected to
37 // all nodes in the current clique. It is composed of two parts; the first
38 // part contains the "not" set (nodes that were already processed and must not
39 // be added to the clique - see the description of the algorithm in the
40 // paper), and nodes that are candidates for addition. The candidates from the
41 // "not" set are at the beginning of the array.
42 // - first_candidate_index elements is the index of the first candidate that is
43 // not in the "not" set (which is also the number of candidates in the "not"
44 // set).
45 // - num_input_candidates is the number of elements in input_candidates,
46 // including both the "not" set and the actual candidates.
47 // - current_clique is the current clique discovered by the algorithm.
48 // - stop is a stopping condition for the algorithm; if the value it points to
49 // is true, the algorithm stops further exploration and returns.
50 // TODO(user) : rewrite this algorithm without recursion.
51 void Search(std::function<bool(int, int)> graph,
52  std::function<bool(const std::vector<int>&)> callback,
53  int* input_candidates, int first_candidate_index,
54  int num_input_candidates, std::vector<int>* current_clique,
55  bool* stop) {
56  // The pivot is a node from input_candidates that is disconnected from the
57  // minimal number of nodes in the actual candidates (excluding the "not" set);
58  // the algorithm then selects only candidates that are disconnected from the
59  // pivot (and the pivot itself), to reach the termination condition as quickly
60  // as possible (see the original paper for more details).
61  int pivot = 0;
62 
63  // A node that is disconnected from the selected pivot. This node is selected
64  // during the pivot matching phase to speed up the first iteration of the
65  // recursive call.
66  int disconnected_node = 0;
67 
68  // The number of candidates (that are not in "not") disconnected from the
69  // selected pivot. The value is computed during pivot selection. In the
70  // "recursive" phase, we only need to do explore num_disconnected_candidates
71  // nodes, because after this step, all remaining candidates will all be
72  // connected to the pivot node (which is in "not"), so they can't form a
73  // maximal clique.
74  int num_disconnected_candidates = num_input_candidates;
75 
76  // If the selected pivot is not in "not", we need to process one more
77  // candidate (the pivot itself). pre_increment is added to
78  // num_disconnected_candidates to compensate for this fact.
79  int pre_increment = 0;
80 
81  // Find Pivot.
82  for (int i = 0; i < num_input_candidates && num_disconnected_candidates != 0;
83  ++i) {
84  int pivot_candidate = input_candidates[i];
85 
86  // Count is the number of candidates (not including nodes in the "not" set)
87  // that are disconnected from the pivot candidate.
88  int count = 0;
89 
90  // The index of a candidate node that is not connected to pivot_candidate.
91  // This node will be used to quickly start the nested iteration (we keep
92  // track of the index so that we don't have to find a node that is
93  // disconnected from the pivot later in the iteration).
94  int disconnected_node_candidate = 0;
95 
96  // Compute the number of candidate nodes that are disconnected from
97  // pivot_candidate. Note that this computation is the same for the "not"
98  // candidates and the normal candidates.
99  for (int j = first_candidate_index;
100  j < num_input_candidates && count < num_disconnected_candidates; ++j) {
101  if (!Connects(graph, pivot_candidate, input_candidates[j])) {
102  count++;
103  disconnected_node_candidate = j;
104  }
105  }
106 
107  // Update the pivot candidate if we found a new minimum for
108  // num_disconnected_candidates.
109  if (count < num_disconnected_candidates) {
110  pivot = pivot_candidate;
111  num_disconnected_candidates = count;
112 
113  if (i < first_candidate_index) {
114  disconnected_node = disconnected_node_candidate;
115  } else {
116  disconnected_node = i;
117  // The pivot candidate is not in the "not" set. We need to pre-increment
118  // the counter for the node to compensate for that.
119  pre_increment = 1;
120  }
121  }
122  }
123 
124  std::vector<int> new_candidates;
125  new_candidates.reserve(num_input_candidates);
126  for (int remaining_candidates = num_disconnected_candidates + pre_increment;
127  remaining_candidates >= 1; remaining_candidates--) {
128  // Swap a node that is disconnected from the pivot (or the pivot itself)
129  // with the first candidate, so that we can later move it to "not" simply by
130  // increasing the index of the first candidate that is not in "not".
131  const int selected = input_candidates[disconnected_node];
132  std::swap(input_candidates[disconnected_node],
133  input_candidates[first_candidate_index]);
134 
135  // Fill the list of candidates and the "not" set for the recursive call:
136  new_candidates.clear();
137  for (int i = 0; i < first_candidate_index; ++i) {
138  if (Connects(graph, selected, input_candidates[i])) {
139  new_candidates.push_back(input_candidates[i]);
140  }
141  }
142  const int new_first_candidate_index = new_candidates.size();
143  for (int i = first_candidate_index + 1; i < num_input_candidates; ++i) {
144  if (Connects(graph, selected, input_candidates[i])) {
145  new_candidates.push_back(input_candidates[i]);
146  }
147  }
148  const int new_candidate_size = new_candidates.size();
149 
150  // Add the selected candidate to the current clique.
151  current_clique->push_back(selected);
152 
153  // If there are no remaining candidates, we have found a maximal clique.
154  // Otherwise, do the recursive step.
155  if (new_candidate_size == 0) {
156  *stop = callback(*current_clique);
157  } else {
158  if (new_first_candidate_index < new_candidate_size) {
159  Search(graph, callback, new_candidates.data(),
160  new_first_candidate_index, new_candidate_size, current_clique,
161  stop);
162  if (*stop) {
163  return;
164  }
165  }
166  }
167 
168  // Remove the selected candidate from the current clique.
169  current_clique->pop_back();
170  // Add the selected candidate to the set "not" - we've already processed
171  // all possible maximal cliques that use this node in 'current_clique'. The
172  // current candidate is the element of the new candidate set, so we can move
173  // it to "not" simply by increasing first_candidate_index.
174  first_candidate_index++;
175 
176  // Find the next candidate that is disconnected from the pivot.
177  if (remaining_candidates > 1) {
178  disconnected_node = first_candidate_index;
179  while (disconnected_node < num_input_candidates &&
180  Connects(graph, pivot, input_candidates[disconnected_node])) {
181  disconnected_node++;
182  }
183  }
184  }
185 }
186 
187 class FindAndEliminate {
188  public:
189  FindAndEliminate(std::function<bool(int, int)> graph, int node_count,
190  std::function<bool(const std::vector<int>&)> callback)
191  : graph_(graph), node_count_(node_count), callback_(callback) {}
192 
193  bool GraphCallback(int node1, int node2) {
194  if (visited_.find(
195  std::make_pair(std::min(node1, node2), std::max(node1, node2))) !=
196  visited_.end()) {
197  return false;
198  }
199  return Connects(graph_, node1, node2);
200  }
201 
202  bool SolutionCallback(const std::vector<int>& solution) {
203  const int size = solution.size();
204  if (size > 1) {
205  for (int i = 0; i < size - 1; ++i) {
206  for (int j = i + 1; j < size; ++j) {
207  visited_.insert(std::make_pair(std::min(solution[i], solution[j]),
208  std::max(solution[i], solution[j])));
209  }
210  }
211  callback_(solution);
212  }
213  return false;
214  }
215 
216  private:
217  std::function<bool(int, int)> graph_;
218  int node_count_;
219  std::function<bool(const std::vector<int>&)> callback_;
220  absl::flat_hash_set<std::pair<int, int>> visited_;
221 };
222 } // namespace
223 
224 // This method implements the 'version2' of the Bron-Kerbosch
225 // algorithm to find all maximal cliques in a undirected graph.
226 void FindCliques(std::function<bool(int, int)> graph, int node_count,
227  std::function<bool(const std::vector<int>&)> callback) {
228  std::unique_ptr<int[]> initial_candidates(new int[node_count]);
229  std::vector<int> actual;
230 
231  for (int c = 0; c < node_count; ++c) {
232  initial_candidates[c] = c;
233  }
234 
235  bool stop = false;
236  Search(graph, callback, initial_candidates.get(), 0, node_count, &actual,
237  &stop);
238 }
239 
240 void CoverArcsByCliques(std::function<bool(int, int)> graph, int node_count,
241  std::function<bool(const std::vector<int>&)> callback) {
242  FindAndEliminate cache(graph, node_count, callback);
243  std::unique_ptr<int[]> initial_candidates(new int[node_count]);
244  std::vector<int> actual;
245 
246  std::function<bool(int, int)> cached_graph = [&cache](int i, int j) {
247  return cache.GraphCallback(i, j);
248  };
249  std::function<bool(const std::vector<int>&)> cached_callback =
250  [&cache](const std::vector<int>& res) {
251  return cache.SolutionCallback(res);
252  };
253 
254  for (int c = 0; c < node_count; ++c) {
255  initial_candidates[c] = c;
256  }
257 
258  bool stop = false;
259  Search(cached_graph, cached_callback, initial_candidates.get(), 0, node_count,
260  &actual, &stop);
261 }
262 
263 } // namespace operations_research
operations_research::Search
Definition: constraint_solver.cc:953
min
int64 min
Definition: alldiff_cst.cc:138
max
int64 max
Definition: alldiff_cst.cc:139
operations_research
The vehicle routing library lets one model and solve generic vehicle routing problems ranging from th...
Definition: dense_doubly_linked_list.h:21
operations_research::CoverArcsByCliques
void CoverArcsByCliques(std::function< bool(int, int)> graph, int node_count, std::function< bool(const std::vector< int > &)> callback)
Definition: cliques.cc:240
operations_research::FindCliques
void FindCliques(std::function< bool(int, int)> graph, int node_count, std::function< bool(const std::vector< int > &)> callback)
Definition: cliques.cc:226
callback
MPCallback * callback
Definition: gurobi_interface.cc:510
cliques.h
hash.h