OR-Tools  9.6
inclusion.h
Go to the documentation of this file.
1 // Copyright 2010-2022 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 #ifndef OR_TOOLS_SAT_INCLUSION_H_
15 #define OR_TOOLS_SAT_INCLUSION_H_
16 
17 #include <stddef.h>
18 #include <stdint.h>
19 
20 #include <algorithm>
21 #include <cstdint>
22 #include <functional>
23 #include <limits>
24 #include <tuple>
25 #include <utility>
26 #include <vector>
27 
28 #include "absl/types/span.h"
29 #include "ortools/base/logging.h"
30 
31 namespace operations_research {
32 namespace sat {
33 
34 // Small utility class to store a vector<vector<>> where one can only append new
35 // vector and never change previously added ones.
36 //
37 // Note that we implement a really small subset of the vector<vector<>> API.
38 template <typename T>
40  public:
41  // Same as push_back().
42  // Returns the previous size() as this is convenient for how we use it.
43  int Add(absl::Span<const T> data) {
44  const int index = size();
45  starts_.push_back(buffer_.size());
46  sizes_.push_back(data.size());
47  buffer_.insert(buffer_.end(), data.begin(), data.end());
48  return index;
49  }
50 
51  // Same as Add() but for sat::Literal or any type from which we can get
52  // indices.
53  template <typename L>
54  int AddLiterals(const std::vector<L>& data) {
55  const int index = size();
56  starts_.push_back(buffer_.size());
57  sizes_.push_back(data.size());
58  for (const L literal : data) {
59  buffer_.push_back(literal.Index().value());
60  }
61  return index;
62  }
63 
64  // Warning: this is only valid until the next clear() or Add() call.
65  absl::Span<const T> operator[](int index) const {
66  DCHECK_GE(index, 0);
67  DCHECK_LT(index, starts_.size());
68  DCHECK_LT(index, sizes_.size());
69  const size_t size = static_cast<size_t>(sizes_[index]);
70  if (size == 0) return {};
71  return {&buffer_[starts_[index]], size};
72  }
73 
74  void clear() {
75  starts_.clear();
76  sizes_.clear();
77  buffer_.clear();
78  }
79 
80  size_t size() const { return starts_.size(); }
81 
82  private:
83  std::vector<int> starts_;
84  std::vector<int> sizes_;
85  std::vector<T> buffer_;
86 };
87 
88 // An helper class to process many sets of integer in [0, n] and detects all the
89 // set included in each others. This is a common operations in presolve, and
90 // while it can be slow the algorithm used here is pretty efficient in practice.
91 //
92 // The algorithm is based on the SAT preprocessing algorithm to detect clauses
93 // that subsumes others. It uses a one-watcher scheme where each subset
94 // candidate has only one element watched. To identify all potential subset of a
95 // superset, one need to inspect the watch list for all element of the superset
96 // candidate.
97 //
98 // The number n will be detected automatically but we allocate various vector
99 // of size n, so avoid having large integer values in your sets.
100 //
101 // All set contents will be accessed via storage_[index]. And of course Storage
102 // can be the CompactVectorVector defined above. But it can also be something
103 // that return a class that support .size() and integer range iteration over the
104 // element in the set on the fly.
105 template <class Storage>
107  public:
108  explicit InclusionDetector(const Storage& storage) : storage_(storage) {}
109 
110  // Resets the class to an empty state.
111  void Reset() {
112  num_potential_subsets_ = 0;
113  num_potential_supersets_ = 0;
114  candidates_.clear();
115  }
116 
117  // Adds a candidate set to consider for the next DetectInclusions() call.
118  // The argument is an index that will only be used via storage_[index] to get
119  // the content of the candidate set.
120  //
121  // Note that set with no element are just ignored and will never be returned
122  // as part of an inclusion.
123  void AddPotentialSubset(int index);
124  void AddPotentialSuperset(int index);
125  void AddPotentialSet(int index);
126 
127  // By default we will detect all inclusions. It is possible to make sure we
128  // don't do more than O(work_limit) operations and eventually abort early by
129  // setting this. Note that we don't reset it on Reset().
130  //
131  // This is needed, because for m candidates of size n, we can have O(m ^ 2)
132  // inclusions, each requiring O(n) work to check.
133  void SetWorkLimit(uint64_t work_limit) { work_limit_ = work_limit; }
134 
135  // Finds all subset included in a superset and call "process" on each of the
136  // detected inclusion. The std::function argument corresponds to indices
137  // passed to the Add*() calls.
138  //
139  // The order of detection will be by increasing superset size. For superset
140  // with the same size, the order will be deterministic but not specified. And
141  // similarly, for a given superset, the order of the included subsets is
142  // deterministic but not specified.
143  //
144  // Note that only the candidate marked as such can be a subset/superset.
145  // For the candidate than can be both and are duplicates (i.e. same set), only
146  // one pair will be returned. We will also never return identity inclusion and
147  // we always have subset != superset.
148  void DetectInclusions(
149  const std::function<void(int subset, int superset)>& process);
150 
151  // Function that should only be used from within "process()".
152  // Returns the bitset corresponsing to the elements of the current superset
153  // passed to the process() function.
154  const std::vector<bool> IsInSuperset() const { return is_in_superset_; }
155 
156  // Function that should only be used from within "process()".
157  // Stop will abort the current search. The other two will cause the
158  // corresponding candidate set to never appear in any future inclusion.
159  void StopProcessingCurrentSubset() { stop_with_current_subset_ = true; }
160  void StopProcessingCurrentSuperset() { stop_with_current_superset_ = true; }
161  void Stop() {
162  stop_ = true;
163  signatures_.clear();
164  one_watcher_.clear();
165  is_in_superset_.clear();
166  }
167 
168  // The algorithm here can detect many small set included in a big set while
169  // only scanning the superset once. So if we do scan the superset in the
170  // process function, we can do a lot more work. This is here to reuse the
171  // deterministic limit mechanism.
172  void IncreaseWorkDone(uint64_t increase) { work_done_ += increase; }
173 
174  // Stats.
175  int num_potential_subsets() const { return num_potential_subsets_; }
176  int num_potential_supersets() const { return num_potential_supersets_; }
177  uint64_t work_done() const { return work_done_; }
178 
179  private:
180  // Allows to access the elements of each candidates via storage_[index];
181  const Storage& storage_;
182 
183  // List of candidates, this will be sorted.
184  struct Candidate {
185  int index; // Storage index.
186  int size;
187 
188  // For identical sizes, we need this order for correctness
189  // 0: subset only, 1: both, 2: superset only.
190  int order = 1;
191 
192  bool CanBeSubset() const { return order <= 1; }
193  bool CanBeSuperset() const { return order >= 1; }
194 
195  // We use this with stable_sort, so no need to add the index.
196  bool operator<(const Candidate& other) const {
197  return std::tie(size, order) < std::tie(other.size, other.order);
198  }
199  };
200  std::vector<Candidate> candidates_;
201 
202  int num_potential_subsets_ = 0;
203  int num_potential_supersets_ = 0;
204  uint64_t work_done_ = 0;
205  uint64_t work_limit_ = std::numeric_limits<uint64_t>::max();
206 
207  // Temporary data only used by DetectInclusions().
208  bool stop_;
209  bool stop_with_current_subset_;
210  bool stop_with_current_superset_;
211  std::vector<uint64_t> signatures_;
212  std::vector<std::vector<int>> one_watcher_; // Index in candidates_.
213  std::vector<bool> is_in_superset_;
214 };
215 
216 // Deduction guide.
217 template <typename Storage>
219 
220 template <typename Storage>
222  DCHECK_GE(index, 0);
223  DCHECK_LT(index, storage_.size());
224  const int num_elements = storage_[index].size();
225  if (num_elements == 0) return;
226 
227  ++num_potential_subsets_;
228  ++num_potential_supersets_;
229  candidates_.push_back({index, num_elements, /*order=*/1});
230 }
231 
232 template <typename Storage>
234  DCHECK_GE(index, 0);
235  DCHECK_LT(index, storage_.size());
236  const int num_elements = storage_[index].size();
237  if (num_elements == 0) return;
238 
239  ++num_potential_subsets_;
240  candidates_.push_back({index, num_elements, /*order=*/0});
241 }
242 
243 template <typename Storage>
245  DCHECK_GE(index, 0);
246  DCHECK_LT(index, storage_.size());
247  const int num_elements = storage_[index].size();
248  if (num_elements == 0) return;
249 
250  DCHECK_GE(index, 0);
251  DCHECK_LT(index, storage_.size());
252  ++num_potential_supersets_;
253  candidates_.push_back({index, num_elements, /*order=*/2});
254 }
255 
256 template <typename Storage>
258  const std::function<void(int subset, int superset)>& process) {
259  // No need to do any work in these cases.
260  if (candidates_.size() <= 1) return;
261  if (num_potential_subsets_ == 0) return;
262  if (num_potential_supersets_ == 0) return;
263 
264  // Temp data must be ready to use.
265  stop_ = false;
266  DCHECK(is_in_superset_.empty());
267  DCHECK(signatures_.empty());
268  DCHECK(one_watcher_.empty());
269 
270  // Main algo.
271  work_done_ = 0;
272  std::stable_sort(candidates_.begin(), candidates_.end());
273  for (const Candidate& candidate : candidates_) {
274  const auto& candidate_elements = storage_[candidate.index];
275  const int candidate_index = signatures_.size();
276 
277  // Compute the signature and also resize vector if needed. We want a
278  // signature that is order invariant and is compatible with inclusion.
279  uint64_t signature = 0;
280  int max_element = 0;
281  for (const int e : candidate_elements) {
282  DCHECK_GE(e, 0);
283  max_element = std::max(max_element, e);
284  signature |= (int64_t{1} << (e & 63));
285  }
286  DCHECK_EQ(is_in_superset_.size(), one_watcher_.size());
287  if (max_element >= is_in_superset_.size()) {
288  is_in_superset_.resize(max_element + 1, false);
289  one_watcher_.resize(max_element + 1);
290  }
291  signatures_.push_back(signature);
292 
293  stop_with_current_superset_ = false;
294  if (candidate.CanBeSuperset()) {
295  const Candidate& superset = candidate;
296  const auto& superset_elements = candidate_elements;
297 
298  // Bitset should be cleared.
299  DCHECK(std::all_of(is_in_superset_.begin(), is_in_superset_.end(),
300  [](bool b) { return !b; }));
301 
302  // Find any subset included in current superset.
303  work_done_ += 2 * superset.size;
304  if (work_done_ > work_limit_) return Stop();
305  for (const int e : superset_elements) {
306  is_in_superset_[e] = true;
307  }
308 
309  const uint64_t superset_signature = signatures_.back();
310  for (const int superset_e : superset_elements) {
311  for (int i = 0; i < one_watcher_[superset_e].size(); ++i) {
312  const int c_index = one_watcher_[superset_e][i];
313  const Candidate& subset = candidates_[c_index];
314  DCHECK_LE(subset.size, superset.size);
315 
316  // Quick check with signature.
317  if ((signatures_[c_index] & ~superset_signature) != 0) continue;
318 
319  // Long check with bitset.
320  bool is_included = true;
321  work_done_ += subset.size;
322  if (work_done_ > work_limit_) return Stop();
323  for (const int subset_e : storage_[subset.index]) {
324  if (!is_in_superset_[subset_e]) {
325  is_included = false;
326  break;
327  }
328  }
329  if (!is_included) continue;
330 
331  stop_with_current_subset_ = false;
332  process(subset.index, superset.index);
333 
334  if (stop_) return;
335  if (work_done_ > work_limit_) return Stop();
336 
337  if (stop_with_current_subset_) {
338  // Remove from the watcher list.
339  std::swap(one_watcher_[superset_e][i],
340  one_watcher_[superset_e].back());
341  one_watcher_[superset_e].pop_back();
342  --i;
343  }
344  if (stop_with_current_superset_) break;
345  }
346  if (stop_with_current_superset_) break;
347  }
348 
349  // Cleanup.
350  for (const int e : superset_elements) {
351  is_in_superset_[e] = false;
352  }
353  }
354 
355  // Add new subset candidate to the watchers.
356  //
357  // Tricky: If this was also a superset and has been removed, we don't want
358  // to watch it!
359  if (candidate.CanBeSubset() && !stop_with_current_superset_) {
360  // Choose to watch the one with smallest list.
361  int best_choice = -1;
362  work_done_ += candidate.size;
363  if (work_done_ > work_limit_) return Stop();
364  for (const int e : candidate_elements) {
365  DCHECK_GE(e, 0);
366  DCHECK_LT(e, one_watcher_.size());
367  if (best_choice == -1 ||
368  one_watcher_[e].size() < one_watcher_[best_choice].size()) {
369  best_choice = e;
370  }
371  }
372  DCHECK_NE(best_choice, -1);
373  one_watcher_[best_choice].push_back(candidate_index);
374  }
375  }
376 
377  // Stop also performs some cleanup.
378  Stop();
379 }
380 
381 } // namespace sat
382 } // namespace operations_research
383 
384 #endif // OR_TOOLS_SAT_INCLUSION_H_
int64_t max
Definition: alldiff_cst.cc:140
absl::Span< const T > operator[](int index) const
Definition: inclusion.h:65
int Add(absl::Span< const T > data)
Definition: inclusion.h:43
int AddLiterals(const std::vector< L > &data)
Definition: inclusion.h:54
const std::vector< bool > IsInSuperset() const
Definition: inclusion.h:154
void SetWorkLimit(uint64_t work_limit)
Definition: inclusion.h:133
void IncreaseWorkDone(uint64_t increase)
Definition: inclusion.h:172
InclusionDetector(const Storage &storage)
Definition: inclusion.h:108
void DetectInclusions(const std::function< void(int subset, int superset)> &process)
Definition: inclusion.h:257
int64_t b
int index
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:269
InclusionDetector(const Storage &storage) -> InclusionDetector< Storage >
Collection of objects used to extend the Constraint Solver library.
Literal literal
Definition: optimization.cc:88