Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
util.cc
Go to the documentation of this file.
1// Copyright 2010-2024 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/sat/util.h"
15
16#include <algorithm>
17#include <cmath>
18#include <cstdint>
19#include <deque>
20#include <functional>
21#include <limits>
22#include <numeric>
23#include <string>
24#include <utility>
25#include <vector>
26
27#include "absl/algorithm/container.h"
28#include "absl/container/btree_set.h"
29#include "absl/container/flat_hash_map.h"
30#include "absl/container/inlined_vector.h"
31#include "absl/log/check.h"
32#include "absl/numeric/int128.h"
33#include "absl/random/bit_gen_ref.h"
34#include "absl/random/distributions.h"
35#include "absl/strings/str_cat.h"
36#include "absl/types/span.h"
37#include "google/protobuf/descriptor.h"
42#include "ortools/sat/sat_parameters.pb.h"
45
46namespace operations_research {
47namespace sat {
48
49std::string FormatCounter(int64_t num) {
50 std::string s = absl::StrCat(num);
51 std::string out;
52 const int size = s.size();
53 for (int i = 0; i < size; ++i) {
54 if (i > 0 && (size - i) % 3 == 0) {
55 out.push_back('\'');
56 }
57 out.push_back(s[i]);
58 }
59 return out;
60}
61
62namespace {
63
64inline std::string LeftAlign(std::string s, int size = 16) {
65 if (s.size() >= size) return s;
66 s.resize(size, ' ');
67 return s;
68}
69
70inline std::string RightAlign(std::string s, int size = 16) {
71 if (s.size() >= size) return s;
72 return absl::StrCat(std::string(size - s.size(), ' '), s);
73}
74
75} // namespace
76
77std::string FormatTable(std::vector<std::vector<std::string>>& table,
78 int spacing) {
79 if (table.size() > 1) {
80 // We order by name.
81 std::sort(table.begin() + 1, table.end());
82 }
83
84 std::vector<int> widths;
85 for (const std::vector<std::string>& line : table) {
86 if (line.size() > widths.size()) widths.resize(line.size(), spacing);
87 for (int j = 0; j < line.size(); ++j) {
88 widths[j] = std::max<int>(widths[j], line[j].size() + spacing);
89 }
90 }
91 std::string output;
92 for (int i = 0; i < table.size(); ++i) {
93 for (int j = 0; j < table[i].size(); ++j) {
94 if (i == 0 && j == 0) {
95 // We currently only left align the table name.
96 absl::StrAppend(&output, LeftAlign(table[i][j], widths[j]));
97 } else {
98 absl::StrAppend(&output, RightAlign(table[i][j], widths[j]));
99 }
100 }
101 absl::StrAppend(&output, "\n");
102 }
103 return output;
104}
105
106void RandomizeDecisionHeuristic(absl::BitGenRef random,
107 SatParameters* parameters) {
108#if !defined(__PORTABLE_PLATFORM__)
109 // Random preferred variable order.
110 const google::protobuf::EnumDescriptor* order_d =
111 SatParameters::VariableOrder_descriptor();
112 parameters->set_preferred_variable_order(
113 static_cast<SatParameters::VariableOrder>(
114 order_d->value(absl::Uniform(random, 0, order_d->value_count()))
115 ->number()));
116
117 // Random polarity initial value.
118 const google::protobuf::EnumDescriptor* polarity_d =
119 SatParameters::Polarity_descriptor();
120 parameters->set_initial_polarity(static_cast<SatParameters::Polarity>(
121 polarity_d->value(absl::Uniform(random, 0, polarity_d->value_count()))
122 ->number()));
123#endif // __PORTABLE_PLATFORM__
124 // Other random parameters.
125 parameters->set_use_phase_saving(absl::Bernoulli(random, 0.5));
126 parameters->set_random_polarity_ratio(absl::Bernoulli(random, 0.5) ? 0.01
127 : 0.0);
128 parameters->set_random_branches_ratio(absl::Bernoulli(random, 0.5) ? 0.01
129 : 0.0);
130}
131
132namespace {
133
134// This will be optimized into one division. I tested that in other places:
135// 3/ortools/sat/integer_test.cc;l=1223-1228;bpv=0
136//
137// Note that I am not 100% sure we need the indirection for the optimization
138// to kick in though, but this seemed safer given our weird r[i ^ 1] inputs.
139void QuotientAndRemainder(int64_t a, int64_t b, int64_t& q, int64_t& r) {
140 q = a / b;
141 r = a % b;
142}
143
144} // namespace
145
146// Using the extended Euclidian algo, we find a and b such that
147// a x + b m = gcd(x, m)
148// https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm
149int64_t ModularInverse(int64_t x, int64_t m) {
150 DCHECK_GE(x, 0);
151 DCHECK_LT(x, m);
152
153 int64_t r[2] = {m, x};
154 int64_t t[2] = {0, 1};
155 int64_t q;
156
157 // We only keep the last two terms of the sequences with the "^1" trick:
158 //
159 // q = r[i-2] / r[i-1]
160 // r[i] = r[i-2] % r[i-1]
161 // t[i] = t[i-2] - t[i-1] * q
162 //
163 // We always have:
164 // - gcd(r[i], r[i - 1]) = gcd(r[i - 1], r[i - 2])
165 // - x * t[i] + m * t[i - 1] = r[i]
166 int i = 0;
167 for (; r[i ^ 1] != 0; i ^= 1) {
168 QuotientAndRemainder(r[i], r[i ^ 1], q, r[i]);
169 t[i] -= t[i ^ 1] * q;
170 }
171
172 // If the gcd is not one, there is no inverse, we returns 0.
173 if (r[i] != 1) return 0;
174
175 // Correct the result so that it is in [0, m). Note that abs(t[i]) is known to
176 // be less than or equal to x / 2, and we have thorough unit-tests.
177 if (t[i] < 0) t[i] += m;
178
179 return t[i];
180}
181
182int64_t PositiveMod(int64_t x, int64_t m) {
183 const int64_t r = x % m;
184 return r < 0 ? r + m : r;
185}
186
187int64_t ProductWithModularInverse(int64_t coeff, int64_t mod, int64_t rhs) {
188 DCHECK_NE(coeff, 0);
189 DCHECK_NE(mod, 0);
190
191 mod = std::abs(mod);
192 if (rhs == 0 || mod == 1) return 0;
193 DCHECK_EQ(std::gcd(std::abs(coeff), mod), 1);
194
195 // Make both in [0, mod).
196 coeff = PositiveMod(coeff, mod);
197 rhs = PositiveMod(rhs, mod);
198
199 // From X * coeff % mod = rhs
200 // We deduce that X % mod = rhs * inverse % mod
201 const int64_t inverse = ModularInverse(coeff, mod);
202 CHECK_NE(inverse, 0);
203
204 // We make the operation in 128 bits to be sure not to have any overflow here.
205 const absl::int128 p = absl::int128{inverse} * absl::int128{rhs};
206 return static_cast<int64_t>(p % absl::int128{mod});
207}
208
209bool SolveDiophantineEquationOfSizeTwo(int64_t& a, int64_t& b, int64_t& cte,
210 int64_t& x0, int64_t& y0) {
211 CHECK_NE(a, 0);
212 CHECK_NE(b, 0);
213 CHECK_NE(a, std::numeric_limits<int64_t>::min());
214 CHECK_NE(b, std::numeric_limits<int64_t>::min());
215
216 const int64_t gcd = std::gcd(std::abs(a), std::abs(b));
217 if (cte % gcd != 0) return false;
218 a /= gcd;
219 b /= gcd;
220 cte /= gcd;
221
222 // The simple case where (0, 0) is a solution.
223 if (cte == 0) {
224 x0 = y0 = 0;
225 return true;
226 }
227
228 // We solve a * X + b * Y = cte
229 // We take a valid x0 in [0, b) by considering the equation mod b.
230 x0 = ProductWithModularInverse(a, b, cte);
231
232 // We choose x0 of the same sign as cte.
233 if (cte < 0 && x0 != 0) x0 -= std::abs(b);
234
235 // By plugging X = x0 + b * Z
236 // We have a * (x0 + b * Z) + b * Y = cte
237 // so a * b * Z + b * Y = cte - a * x0;
238 // and y0 = (cte - a * x0) / b (with an exact division by construction).
239 const absl::int128 t = absl::int128{cte} - absl::int128{a} * absl::int128{x0};
240 DCHECK_EQ(t % absl::int128{b}, absl::int128{0});
241
242 // Overflow-wise, there is two cases for cte > 0:
243 // - a * x0 <= cte, in this case y0 will not overflow (<= cte).
244 // - a * x0 > cte, in this case y0 will be in (-a, 0].
245 const absl::int128 r = t / absl::int128{b};
246 DCHECK_LE(r, absl::int128{std::numeric_limits<int64_t>::max()});
247 DCHECK_GE(r, absl::int128{std::numeric_limits<int64_t>::min()});
248
249 y0 = static_cast<int64_t>(r);
250 return true;
251}
252
253// TODO(user): Find better implementation? In pratice passing via double is
254// almost always correct, but the CapProd() might be a bit slow. However this
255// is only called when we do propagate something.
256int64_t FloorSquareRoot(int64_t a) {
257 int64_t result =
258 static_cast<int64_t>(std::floor(std::sqrt(static_cast<double>(a))));
259 while (CapProd(result, result) > a) --result;
260 while (CapProd(result + 1, result + 1) <= a) ++result;
261 return result;
262}
263
264// TODO(user): Find better implementation?
265int64_t CeilSquareRoot(int64_t a) {
266 int64_t result =
267 static_cast<int64_t>(std::ceil(std::sqrt(static_cast<double>(a))));
268 while (CapProd(result, result) < a) ++result;
269 while ((result - 1) * (result - 1) >= a) --result;
270 return result;
271}
272
273int64_t ClosestMultiple(int64_t value, int64_t base) {
274 if (value < 0) return -ClosestMultiple(-value, base);
275 int64_t result = value / base * base;
276 if (value - result > base / 2) result += base;
277 return result;
278}
279
281 int64_t base, absl::Span<const int64_t> coeffs,
282 absl::Span<const int64_t> lbs, absl::Span<const int64_t> ubs, int64_t rhs,
283 int64_t* new_rhs) {
284 // Precompute some bounds for the equation base * X + error <= rhs.
285 int64_t max_activity = 0;
286 int64_t max_x = 0;
287 int64_t min_error = 0;
288 const int num_terms = coeffs.size();
289 if (num_terms == 0) return false;
290 for (int i = 0; i < num_terms; ++i) {
291 const int64_t coeff = coeffs[i];
292 CHECK_GT(coeff, 0);
293 const int64_t closest = ClosestMultiple(coeff, base);
294 max_activity += coeff * ubs[i];
295 max_x += closest / base * ubs[i];
296
297 const int64_t error = coeff - closest;
298 if (error >= 0) {
299 min_error += error * lbs[i];
300 } else {
301 min_error += error * ubs[i];
302 }
303 }
304
305 if (max_activity <= rhs) {
306 // The constraint is trivially true.
307 *new_rhs = max_x;
308 return true;
309 }
310
311 // This is the max error assuming that activity > rhs.
312 int64_t max_error_if_invalid = 0;
313 const int64_t slack = max_activity - rhs - 1;
314 for (int i = 0; i < num_terms; ++i) {
315 const int64_t coeff = coeffs[i];
316 const int64_t closest = ClosestMultiple(coeff, base);
317 const int64_t error = coeff - closest;
318 if (error >= 0) {
319 max_error_if_invalid += error * ubs[i];
320 } else {
321 const int64_t lb = std::max(lbs[i], ubs[i] - slack / coeff);
322 max_error_if_invalid += error * lb;
323 }
324 }
325
326 // We have old solution valid =>
327 // base * X + error <= rhs
328 // base * X <= rhs - error
329 // base * X <= rhs - min_error
330 // X <= new_rhs
331 *new_rhs = std::min(max_x, MathUtil::FloorOfRatio(rhs - min_error, base));
332
333 // And we have old solution invalid =>
334 // base * X + error >= rhs + 1
335 // base * X >= rhs + 1 - max_error_if_invalid
336 // X >= infeasibility_bound
337 const int64_t infeasibility_bound =
338 MathUtil::CeilOfRatio(rhs + 1 - max_error_if_invalid, base);
339
340 // If the two bounds can be separated, we have an equivalence !
341 return *new_rhs < infeasibility_bound;
342}
343
345 const absl::btree_set<LiteralIndex>& processed, int relevant_prefix_size,
346 std::vector<Literal>* literals) {
347 if (literals->empty()) return -1;
348 if (!processed.contains(literals->back().Index())) {
349 return std::min<int>(relevant_prefix_size, literals->size());
350 }
351
352 // To get O(n log n) size of suffixes, we will first process the last n/2
353 // literals, we then move all of them first and process the n/2 literals left.
354 // We use the same algorithm recursively. The sum of the suffixes' size S(n)
355 // is thus S(n/2) + n + S(n/2). That gives us the correct complexity. The code
356 // below simulates one step of this algorithm and is made to be "robust" when
357 // from one call to the next, some literals have been removed (but the order
358 // of literals is preserved).
359 int num_processed = 0;
360 int num_not_processed = 0;
361 int target_prefix_size = literals->size() - 1;
362 for (int i = literals->size() - 1; i >= 0; i--) {
363 if (processed.contains((*literals)[i].Index())) {
364 ++num_processed;
365 } else {
366 ++num_not_processed;
367 target_prefix_size = i;
368 }
369 if (num_not_processed >= num_processed) break;
370 }
371 if (num_not_processed == 0) return -1;
372 target_prefix_size = std::min(target_prefix_size, relevant_prefix_size);
373
374 // Once a prefix size has been decided, it is always better to
375 // enqueue the literal already processed first.
376 std::stable_partition(
377 literals->begin() + target_prefix_size, literals->end(),
378 [&processed](Literal l) { return processed.contains(l.Index()); });
379 return target_prefix_size;
380}
381
382int WeightedPick(absl::Span<const double> input, absl::BitGenRef random) {
383 DCHECK(!input.empty());
384
385 double total_weight = 0;
386 for (const double w : input) {
387 total_weight += w;
388 }
389
390 const double weight_point = absl::Uniform(random, 0.0f, total_weight);
391 double total_weight2 = 0;
392 for (int i = 0; i < input.size(); ++i) {
393 total_weight2 += input[i];
394 if (total_weight2 > weight_point) {
395 return i;
396 }
397 }
398 return input.size() - 1;
399}
400
401void IncrementalAverage::Reset(double reset_value) {
402 num_records_ = 0;
403 average_ = reset_value;
404}
405
406void IncrementalAverage::AddData(double new_record) {
407 num_records_++;
408 average_ += (new_record - average_) / num_records_;
409}
410
411void ExponentialMovingAverage::AddData(double new_record) {
412 num_records_++;
413 average_ = (num_records_ == 1)
414 ? new_record
415 : (new_record + decaying_factor_ * (average_ - new_record));
416}
417
418void Percentile::AddRecord(double record) {
419 records_.push_front(record);
420 if (records_.size() > record_limit_) {
421 records_.pop_back();
422 }
423}
424
425double Percentile::GetPercentile(double percent) {
426 CHECK_GT(records_.size(), 0);
427 CHECK_LE(percent, 100.0);
428 CHECK_GE(percent, 0.0);
429
430 const int num_records = records_.size();
431 const double percentile_rank =
432 static_cast<double>(num_records) * percent / 100.0 - 0.5;
433 if (percentile_rank <= 0) {
434 return *absl::c_min_element(records_);
435 } else if (percentile_rank >= num_records - 1) {
436 return *absl::c_max_element(records_);
437 }
438 std::vector<double> sorted_records;
439 sorted_records.assign(records_.begin(), records_.end());
440 // Interpolate.
441 DCHECK_GE(num_records, 2);
442 DCHECK_LT(percentile_rank, num_records - 1);
443 const int lower_rank = static_cast<int>(std::floor(percentile_rank));
444 DCHECK_LT(lower_rank, num_records - 1);
445 auto upper_it = sorted_records.begin() + lower_rank + 1;
446 // Ensure that sorted_records[lower_rank + 1] is in the correct place as if
447 // records were actually sorted, the next closest is then the largest element
448 // to the left of this element.
449 absl::c_nth_element(sorted_records, upper_it);
450 auto lower_it = std::max_element(sorted_records.begin(), upper_it);
451 return *lower_it + (percentile_rank - lower_rank) * (*upper_it - *lower_it);
452}
453
454void CompressTuples(absl::Span<const int64_t> domain_sizes,
455 std::vector<std::vector<int64_t>>* tuples) {
456 if (tuples->empty()) return;
457
458 // Remove duplicates if any.
460
461 const int num_vars = (*tuples)[0].size();
462
463 std::vector<int> to_remove;
464 std::vector<int64_t> tuple_minus_var_i(num_vars - 1);
465 for (int i = 0; i < num_vars; ++i) {
466 const int domain_size = domain_sizes[i];
467 if (domain_size == 1) continue;
468 absl::flat_hash_map<const std::vector<int64_t>, std::vector<int>>
469 masked_tuples_to_indices;
470 for (int t = 0; t < tuples->size(); ++t) {
471 int out = 0;
472 for (int j = 0; j < num_vars; ++j) {
473 if (i == j) continue;
474 tuple_minus_var_i[out++] = (*tuples)[t][j];
475 }
476 masked_tuples_to_indices[tuple_minus_var_i].push_back(t);
477 }
478 to_remove.clear();
479 for (const auto& it : masked_tuples_to_indices) {
480 if (it.second.size() != domain_size) continue;
481 (*tuples)[it.second.front()][i] = kTableAnyValue;
482 to_remove.insert(to_remove.end(), it.second.begin() + 1, it.second.end());
483 }
484 std::sort(to_remove.begin(), to_remove.end(), std::greater<int>());
485 for (const int t : to_remove) {
486 (*tuples)[t] = tuples->back();
487 tuples->pop_back();
488 }
489 }
490}
491
493 DCHECK_GE(bound, 0);
494 gcd_ = 0;
495 sums_ = {0};
496 expanded_sums_.clear();
497 current_max_ = 0;
498 bound_ = bound;
499}
500
502 if (value == 0) return;
503 if (value > bound_) return;
504 gcd_ = std::gcd(gcd_, value);
505 AddChoicesInternal({value});
506}
507
508void MaxBoundedSubsetSum::AddChoices(absl::Span<const int64_t> choices) {
509 if (DEBUG_MODE) {
510 for (const int64_t c : choices) {
511 DCHECK_GE(c, 0);
512 }
513 }
514
515 // The max is already reachable or we aborted.
516 if (current_max_ == bound_) return;
517
518 // Filter out zero and values greater than bound_.
519 filtered_values_.clear();
520 for (const int64_t c : choices) {
521 if (c == 0 || c > bound_) continue;
522 filtered_values_.push_back(c);
523 gcd_ = std::gcd(gcd_, c);
524 }
525 if (filtered_values_.empty()) return;
526
527 // So we can abort early in the AddChoicesInternal() inner loops.
528 std::sort(filtered_values_.begin(), filtered_values_.end());
529 AddChoicesInternal(filtered_values_);
530}
531
532void MaxBoundedSubsetSum::AddMultiples(int64_t coeff, int64_t max_value) {
533 DCHECK_GE(coeff, 0);
534 DCHECK_GE(max_value, 0);
535
536 if (coeff == 0 || max_value == 0) return;
537 if (coeff > bound_) return;
538 if (current_max_ == bound_) return;
539 gcd_ = std::gcd(gcd_, coeff);
540
541 const int64_t num_values = std::min(max_value, FloorOfRatio(bound_, coeff));
542 if (num_values > 10) {
543 // We only keep GCD in this case.
544 sums_.clear();
545 expanded_sums_.clear();
546 current_max_ = FloorOfRatio(bound_, gcd_) * gcd_;
547 return;
548 }
549
550 filtered_values_.clear();
551 for (int multiple = 1; multiple <= num_values; ++multiple) {
552 const int64_t v = multiple * coeff;
553 if (v == bound_) {
554 current_max_ = bound_;
555 return;
556 }
557 filtered_values_.push_back(v);
558 }
559 AddChoicesInternal(filtered_values_);
560}
561
562void MaxBoundedSubsetSum::AddChoicesInternal(absl::Span<const int64_t> values) {
563 // Mode 1: vector of all possible sums (with duplicates).
564 if (!sums_.empty() && sums_.size() <= max_complexity_per_add_) {
565 const int old_size = sums_.size();
566 for (int i = 0; i < old_size; ++i) {
567 for (const int64_t value : values) {
568 const int64_t s = sums_[i] + value;
569 if (s > bound_) break;
570
571 sums_.push_back(s);
572 current_max_ = std::max(current_max_, s);
573 if (current_max_ == bound_) return; // Abort
574 }
575 }
576 return;
577 }
578
579 // Mode 2: bitset of all possible sums.
580 if (bound_ <= max_complexity_per_add_) {
581 if (!sums_.empty()) {
582 expanded_sums_.assign(bound_ + 1, false);
583 for (const int64_t s : sums_) {
584 expanded_sums_[s] = true;
585 }
586 sums_.clear();
587 }
588
589 // The reverse order is important to not add the current value twice.
590 if (!expanded_sums_.empty()) {
591 for (int64_t i = bound_ - 1; i >= 0; --i) {
592 if (!expanded_sums_[i]) continue;
593 for (const int64_t value : values) {
594 if (i + value > bound_) break;
595
596 expanded_sums_[i + value] = true;
597 current_max_ = std::max(current_max_, i + value);
598 if (current_max_ == bound_) return; // Abort
599 }
600 }
601 return;
602 }
603 }
604
605 // Fall back to gcd_.
606 DCHECK_NE(gcd_, 0);
607 if (gcd_ == 1) {
608 current_max_ = bound_;
609 } else {
610 current_max_ = FloorOfRatio(bound_, gcd_) * gcd_;
611 }
612}
613
614int64_t MaxBoundedSubsetSum::MaxIfAdded(int64_t candidate) const {
615 if (candidate > bound_ || current_max_ == bound_) return current_max_;
616
617 int64_t current_max = current_max_;
618 // Mode 1: vector of all possible sums (with duplicates).
619 if (!sums_.empty()) {
620 for (const int64_t v : sums_) {
621 if (v + candidate > bound_) continue;
622 if (v + candidate > current_max) {
623 current_max = v + candidate;
624 if (current_max == bound_) return current_max;
625 }
626 }
627 return current_max;
628 }
629
630 // Mode 2: bitset of all possible sums.
631 if (!expanded_sums_.empty()) {
632 const int64_t min_useful = std::max<int64_t>(0, current_max_ - candidate);
633 const int64_t max_useful = bound_ - candidate;
634 for (int64_t v = max_useful; v >= min_useful; --v) {
635 if (expanded_sums_[v]) return v + candidate;
636 }
637 }
638 return current_max_;
639}
640
642 const std::vector<Domain>& domains, const std::vector<int64_t>& coeffs,
643 const std::vector<int64_t>& costs, const Domain& rhs) {
644 const int num_vars = domains.size();
645 if (num_vars == 0) return {};
646
647 int64_t min_activity = 0;
648 int64_t max_domain_size = 0;
649 for (int i = 0; i < num_vars; ++i) {
650 max_domain_size = std::max(max_domain_size, domains[i].Size());
651 if (coeffs[i] > 0) {
652 min_activity += coeffs[i] * domains[i].Min();
653 } else {
654 min_activity += coeffs[i] * domains[i].Max();
655 }
656 }
657
658 // The complexity of our DP will depends on the number of "activity" values
659 // that need to be considered.
660 //
661 // TODO(user): We can also solve efficiently if max_activity - rhs.Min() is
662 // small. Implement.
663 const int64_t num_values = rhs.Max() - min_activity + 1;
664 if (num_values < 0) {
665 // Problem is clearly infeasible, we can report the result right away.
666 Result result;
667 result.solved = true;
668 result.infeasible = true;
669 return result;
670 }
671
672 // Abort if complexity too large.
673 const int64_t max_work_per_variable = std::min(num_values, max_domain_size);
674 if (rhs.Max() - min_activity > 1e6) return {};
675 if (num_vars * num_values * max_work_per_variable > 1e8) return {};
676
677 // Canonicalize to positive coeffs and non-negative variables.
678 domains_.clear();
679 coeffs_.clear();
680 costs_.clear();
681 for (int i = 0; i < num_vars; ++i) {
682 if (coeffs[i] > 0) {
683 domains_.push_back(domains[i].AdditionWith(Domain(-domains[i].Min())));
684 coeffs_.push_back(coeffs[i]);
685 costs_.push_back(costs[i]);
686 } else {
687 domains_.push_back(
688 domains[i].Negation().AdditionWith(Domain(domains[i].Max())));
689 coeffs_.push_back(-coeffs[i]);
690 costs_.push_back(-costs[i]);
691 }
692 }
693
694 Result result =
695 InternalSolve(num_values, rhs.AdditionWith(Domain(-min_activity)));
696 if (result.solved && !result.infeasible) {
697 // Transform solution back.
698 for (int i = 0; i < num_vars; ++i) {
699 if (coeffs[i] > 0) {
700 result.solution[i] += domains[i].Min();
701 } else {
702 result.solution[i] = domains[i].Max() - result.solution[i];
703 }
704 }
705 }
706 return result;
707}
708
709BasicKnapsackSolver::Result BasicKnapsackSolver::InternalSolve(
710 int64_t num_values, const Domain& rhs) {
711 const int num_vars = domains_.size();
712
713 // The set of DP states that we will fill.
714 var_activity_states_.assign(num_vars, std::vector<State>(num_values));
715
716 // Initialize with first variable.
717 for (const int64_t v : domains_[0].Values()) {
718 const int64_t value = v * coeffs_[0];
719 CHECK_GE(value, 0);
720 if (value >= num_values) break;
721 var_activity_states_[0][value].cost = v * costs_[0];
722 var_activity_states_[0][value].value = v;
723 }
724
725 // Fill rest of the DP states.
726 for (int i = 1; i < num_vars; ++i) {
727 const std::vector<State>& prev = var_activity_states_[i - 1];
728 std::vector<State>& current = var_activity_states_[i];
729 for (int prev_value = 0; prev_value < num_values; ++prev_value) {
730 if (prev[prev_value].cost == std::numeric_limits<int64_t>::max()) {
731 continue;
732 }
733 for (const int64_t v : domains_[i].Values()) {
734 const int64_t value = prev_value + v * coeffs_[i];
735 CHECK_GE(value, 0);
736 if (value >= num_values) break;
737 const int64_t new_cost = prev[prev_value].cost + v * costs_[i];
738 if (new_cost < current[value].cost) {
739 current[value].cost = new_cost;
740 current[value].value = v;
741 }
742 }
743 }
744 }
745
746 Result result;
747 result.solved = true;
748
749 int64_t best_cost = std::numeric_limits<int64_t>::max();
750 int64_t best_activity;
751 for (int v = 0; v < num_values; ++v) {
752 // TODO(user): optimize this?
753 if (!rhs.Contains(v)) continue;
754 if (var_activity_states_.back()[v].cost < best_cost) {
755 best_cost = var_activity_states_.back()[v].cost;
756 best_activity = v;
757 }
758 }
759
760 if (best_cost == std::numeric_limits<int64_t>::max()) {
761 result.infeasible = true;
762 return result;
763 }
764
765 // Recover the values.
766 result.solution.resize(num_vars);
767 int64_t current_activity = best_activity;
768 for (int i = num_vars - 1; i >= 0; --i) {
769 const int64_t var_value = var_activity_states_[i][current_activity].value;
770 result.solution[i] = var_value;
771 current_activity -= coeffs_[i] * var_value;
772 }
773
774 return result;
775}
776
777namespace {
778
779// We will call FullyCompressTuplesRecursive() for a set of prefixes of the
780// original tuples, each having the same suffix (in reversed_suffix).
781//
782// For such set, we will compress it on the last variable of the prefixes. We
783// will then for each unique compressed set of value of that variable, call
784// a new FullyCompressTuplesRecursive() on the corresponding subset.
785void FullyCompressTuplesRecursive(
786 absl::Span<const int64_t> domain_sizes,
787 absl::Span<std::vector<int64_t>> tuples,
788 std::vector<absl::InlinedVector<int64_t, 2>>* reversed_suffix,
789 std::vector<std::vector<absl::InlinedVector<int64_t, 2>>>* output) {
790 struct TempData {
791 absl::InlinedVector<int64_t, 2> values;
792 int index;
793
794 bool operator<(const TempData& other) const {
795 return values < other.values;
796 }
797 };
798 std::vector<TempData> temp_data;
799
800 CHECK(!tuples.empty());
801 CHECK(!tuples[0].empty());
802 const int64_t domain_size = domain_sizes[tuples[0].size() - 1];
803
804 // Sort tuples and regroup common prefix in temp_data.
805 std::sort(tuples.begin(), tuples.end());
806 for (int i = 0; i < tuples.size();) {
807 const int start = i;
808 temp_data.push_back({{tuples[start].back()}, start});
809 tuples[start].pop_back();
810 for (++i; i < tuples.size(); ++i) {
811 const int64_t v = tuples[i].back();
812 tuples[i].pop_back();
813 if (tuples[i] == tuples[start]) {
814 temp_data.back().values.push_back(v);
815 } else {
816 tuples[i].push_back(v);
817 break;
818 }
819 }
820
821 // If one of the value is the special value kTableAnyValue, we convert
822 // it to the "empty means any" format.
823 for (const int64_t v : temp_data.back().values) {
824 if (v == kTableAnyValue) {
825 temp_data.back().values.clear();
826 break;
827 }
828 }
829 gtl::STLSortAndRemoveDuplicates(&temp_data.back().values);
830
831 // If values cover the whole domain, we clear the vector. This allows to
832 // use less space and avoid creating uneeded clauses.
833 if (temp_data.back().values.size() == domain_size) {
834 temp_data.back().values.clear();
835 }
836 }
837
838 if (temp_data.size() == 1) {
839 output->push_back({});
840 for (const int64_t v : tuples[temp_data[0].index]) {
841 if (v == kTableAnyValue) {
842 output->back().push_back({});
843 } else {
844 output->back().push_back({v});
845 }
846 }
847 output->back().push_back(temp_data[0].values);
848 for (int i = reversed_suffix->size(); --i >= 0;) {
849 output->back().push_back((*reversed_suffix)[i]);
850 }
851 return;
852 }
853
854 // Sort temp_data and make recursive call for all tuples that share the
855 // same suffix.
856 std::sort(temp_data.begin(), temp_data.end());
857 std::vector<std::vector<int64_t>> temp_tuples;
858 for (int i = 0; i < temp_data.size();) {
859 reversed_suffix->push_back(temp_data[i].values);
860 const int start = i;
861 temp_tuples.clear();
862 for (; i < temp_data.size(); i++) {
863 if (temp_data[start].values != temp_data[i].values) break;
864 temp_tuples.push_back(tuples[temp_data[i].index]);
865 }
866 FullyCompressTuplesRecursive(domain_sizes, absl::MakeSpan(temp_tuples),
867 reversed_suffix, output);
868 reversed_suffix->pop_back();
869 }
870}
871
872} // namespace
873
874// TODO(user): We can probably reuse the tuples memory always and never create
875// new one. We should also be able to code an iterative version of this. Note
876// however that the recursion level is bounded by the number of coluns which
877// should be small.
878std::vector<std::vector<absl::InlinedVector<int64_t, 2>>> FullyCompressTuples(
879 absl::Span<const int64_t> domain_sizes,
880 std::vector<std::vector<int64_t>>* tuples) {
881 std::vector<absl::InlinedVector<int64_t, 2>> reversed_suffix;
882 std::vector<std::vector<absl::InlinedVector<int64_t, 2>>> output;
883 FullyCompressTuplesRecursive(domain_sizes, absl::MakeSpan(*tuples),
884 &reversed_suffix, &output);
885 return output;
886}
887
888namespace {
889
890class CliqueDecomposition {
891 public:
892 CliqueDecomposition(const std::vector<std::vector<int>>& graph,
893 absl::BitGenRef random, std::vector<int>* buffer)
894 : graph_(graph), random_(random), buffer_(buffer) {
895 const int n = graph.size();
896 permutation_.resize(n);
897 buffer_->resize(n);
898
899 // TODO(user): Start by sorting by decreasing size of adjacency list?
900 for (int i = 0; i < n; ++i) permutation_[i] = i;
901 }
902
903 // This works in O(m). All possible decomposition are reachable, depending on
904 // the initial permutation.
905 //
906 // TODO(user): It can be made faster within the same complexity though.
907 void DecomposeGreedily() {
908 decomposition_.clear();
909 candidates_.clear();
910
911 const int n = graph_.size();
912 taken_.assign(n, false);
913 temp_.assign(n, false);
914
915 int buffer_index = 0;
916 for (const int i : permutation_) {
917 if (taken_[i]) continue;
918
919 // Save start of amo and output i.
920 const int start = buffer_index;
921 taken_[i] = true;
922 (*buffer_)[buffer_index++] = i;
923
924 candidates_.clear();
925 for (const int c : graph_[i]) {
926 if (!taken_[c]) candidates_.push_back(c);
927 }
928 while (!candidates_.empty()) {
929 // Extend at most one with lowest possible permutation index.
930 int next = candidates_.front();
931 for (const int n : candidates_) {
932 if (permutation_[n] < permutation_[next]) next = n;
933 }
934
935 // Add to current amo.
936 taken_[next] = true;
937 (*buffer_)[buffer_index++] = next;
938
939 // Filter candidates.
940 // TODO(user): With a sorted graph_, same complexity, but likely faster.
941 for (const int head : graph_[next]) temp_[head] = true;
942 int new_size = 0;
943 for (const int c : candidates_) {
944 if (taken_[c]) continue;
945 if (!temp_[c]) continue;
946 candidates_[new_size++] = c;
947 }
948 candidates_.resize(new_size);
949 for (const int head : graph_[next]) temp_[head] = false;
950 }
951
952 // Output amo.
953 decomposition_.push_back(
954 absl::MakeSpan(buffer_->data() + start, buffer_index - start));
955 }
956 DCHECK_EQ(buffer_index, n);
957 }
958
959 // We follow the heuristic in the paper: "Partitioning Networks into Cliques:
960 // A Randomized Heuristic Approach", David Chaluppa, 2014.
961 //
962 // Note that by keeping the local order of each clique, we cannot make the
963 // order "worse". And each new call to DecomposeGreedily() can only reduce
964 // the number of clique in the cover.
965 void ChangeOrder() {
966 if (absl::Bernoulli(random_, 0.5)) {
967 std::reverse(decomposition_.begin(), decomposition_.end());
968 } else {
969 std::shuffle(decomposition_.begin(), decomposition_.end(), random_);
970 }
971
972 int out_index = 0;
973 for (const absl::Span<const int> clique : decomposition_) {
974 for (const int i : clique) {
975 permutation_[out_index++] = i;
976 }
977 }
978 }
979
980 const std::vector<absl::Span<int>>& decomposition() const {
981 return decomposition_;
982 }
983
984 private:
985 const std::vector<std::vector<int>>& graph_;
986 absl::BitGenRef random_;
987 std::vector<int>* buffer_;
988
989 std::vector<absl::Span<int>> decomposition_;
990 std::vector<int> candidates_;
991 std::vector<int> permutation_;
992 std::vector<bool> taken_;
993 std::vector<bool> temp_;
994};
995
996} // namespace
997
998std::vector<absl::Span<int>> AtMostOneDecomposition(
999 const std::vector<std::vector<int>>& graph, absl::BitGenRef random,
1000 std::vector<int>* buffer) {
1001 CliqueDecomposition decomposer(graph, random, buffer);
1002 for (int pass = 0; pass < 10; ++pass) {
1003 decomposer.DecomposeGreedily();
1004 if (decomposer.decomposition().size() == 1) break;
1005 decomposer.ChangeOrder();
1006 }
1007 return decomposer.decomposition();
1008}
1009
1010} // namespace sat
1011} // namespace operations_research
IntegerValue size
bool Contains(int64_t value) const
Domain AdditionWith(const Domain &domain) const
static IntegralType CeilOfRatio(IntegralType numerator, IntegralType denominator)
Definition mathutil.h:40
static IntegralType FloorOfRatio(IntegralType numerator, IntegralType denominator)
Definition mathutil.h:54
Result Solve(const std::vector< Domain > &domains, const std::vector< int64_t > &coeffs, const std::vector< int64_t > &costs, const Domain &rhs)
Definition util.cc:641
void Reset(double reset_value)
Sets the number of records to 0 and average to 'reset_value'.
Definition util.cc:401
void AddChoices(absl::Span< const int64_t > choices)
Definition util.cc:508
int64_t MaxIfAdded(int64_t candidate) const
Returns the updated max if value was added to the subset-sum.
Definition util.cc:614
void Add(int64_t value)
Add a value to the base set for which subset sums will be taken.
Definition util.cc:501
void AddMultiples(int64_t coeff, int64_t max_value)
Adds [0, coeff, 2 * coeff, ... max_value * coeff].
Definition util.cc:532
void AddRecord(double record)
Definition util.cc:418
double GetPercentile(double percent)
Definition util.cc:425
int64_t a
Definition table.cc:44
Block * next
SatParameters parameters
GraphType graph
int64_t value
int index
const bool DEBUG_MODE
Definition macros.h:24
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:58
constexpr int64_t kTableAnyValue
Definition util.h:601
void CompressTuples(absl::Span< const int64_t > domain_sizes, std::vector< std::vector< int64_t > > *tuples)
Definition util.cc:454
void RandomizeDecisionHeuristic(absl::BitGenRef random, SatParameters *parameters)
Randomizes the decision heuristic of the given SatParameters.
Definition util.cc:106
int64_t ProductWithModularInverse(int64_t coeff, int64_t mod, int64_t rhs)
Definition util.cc:187
bool SolveDiophantineEquationOfSizeTwo(int64_t &a, int64_t &b, int64_t &cte, int64_t &x0, int64_t &y0)
Definition util.cc:209
std::vector< std::vector< absl::InlinedVector< int64_t, 2 > > > FullyCompressTuples(absl::Span< const int64_t > domain_sizes, std::vector< std::vector< int64_t > > *tuples)
Definition util.cc:878
int64_t FloorSquareRoot(int64_t a)
The argument must be non-negative.
Definition util.cc:256
int64_t CeilSquareRoot(int64_t a)
Definition util.cc:265
int64_t ModularInverse(int64_t x, int64_t m)
Definition util.cc:149
IntType FloorOfRatio(IntType numerator, IntType denominator)
Definition util.h:734
int WeightedPick(absl::Span< const double > input, absl::BitGenRef random)
Definition util.cc:382
std::string FormatCounter(int64_t num)
Prints a positive number with separators for easier reading (ex: 1'348'065).
Definition util.cc:49
int64_t ClosestMultiple(int64_t value, int64_t base)
Definition util.cc:273
int64_t PositiveMod(int64_t x, int64_t m)
Just returns x % m but with a result always in [0, m).
Definition util.cc:182
bool LinearInequalityCanBeReducedWithClosestMultiple(int64_t base, absl::Span< const int64_t > coeffs, absl::Span< const int64_t > lbs, absl::Span< const int64_t > ubs, int64_t rhs, int64_t *new_rhs)
Definition util.cc:280
int MoveOneUnprocessedLiteralLast(const absl::btree_set< LiteralIndex > &processed, int relevant_prefix_size, std::vector< Literal > *literals)
Definition util.cc:344
std::vector< absl::Span< int > > AtMostOneDecomposition(const std::vector< std::vector< int > > &graph, absl::BitGenRef random, std::vector< int > *buffer)
Definition util.cc:998
std::string FormatTable(std::vector< std::vector< std::string > > &table, int spacing)
Definition util.cc:77
In SWIG mode, we don't want anything besides these top-level includes.
int64_t CapProd(int64_t x, int64_t y)
trees with all degrees equal w the current value of w
int line
static int input(yyscan_t yyscanner)
const Variable x
Definition qp_tests.cc:127
int64_t bound
int head
int64_t start