Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
presolve_util.cc
Go to the documentation of this file.
1// Copyright 2010-2025 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
15
16#include <algorithm>
17#include <array>
18#include <cstdint>
19#include <cstdlib>
20#include <limits>
21#include <tuple>
22#include <utility>
23#include <vector>
24
25#include "absl/container/flat_hash_map.h"
26#include "absl/container/flat_hash_set.h"
27#include "absl/log/check.h"
28#include "absl/log/log.h"
29#include "absl/random/distributions.h"
30#include "absl/types/span.h"
34#include "ortools/sat/util.h"
35#include "ortools/util/bitset.h"
39
40namespace operations_research {
41namespace sat {
42
43void DomainDeductions::AddDeduction(int literal_ref, int var, Domain domain) {
44 CHECK_GE(var, 0);
45 const Index index = IndexFromLiteral(literal_ref);
46 if (index >= something_changed_.size()) {
47 something_changed_.Resize(index + 1);
48 enforcement_to_vars_.resize(index.value() + 1);
49 }
50 if (var >= tmp_num_occurrences_.size()) {
51 tmp_num_occurrences_.resize(var + 1, 0);
52 }
53 const auto insert = deductions_.insert({{index, var}, domain});
54 if (insert.second) {
55 // New element.
56 something_changed_.Set(index);
57 enforcement_to_vars_[index].push_back(var);
58 } else {
59 // Existing element.
60 const Domain& old_domain = insert.first->second;
61 if (!old_domain.IsIncludedIn(domain)) {
62 insert.first->second = domain.IntersectionWith(old_domain);
63 something_changed_.Set(index);
64 }
65 }
66}
67
68Domain DomainDeductions::ImpliedDomain(int literal_ref, int var) const {
69 CHECK_GE(var, 0);
70 const Index index = IndexFromLiteral(literal_ref);
71 const auto it = deductions_.find({index, var});
72 if (it == deductions_.end()) return Domain::AllValues();
73 return it->second;
74}
75
76std::vector<std::pair<int, Domain>> DomainDeductions::ProcessClause(
77 absl::Span<const int> clause) {
78 std::vector<std::pair<int, Domain>> result;
79
80 // We only need to process this clause if something changed since last time.
81 bool abort = true;
82 for (const int ref : clause) {
83 const Index index = IndexFromLiteral(ref);
84 if (index >= something_changed_.size()) return result;
85 if (something_changed_[index]) {
86 abort = false;
87 }
88 }
89 if (abort) return result;
90
91 // Count for each variable, how many times it appears in the deductions lists.
92 std::vector<int> to_process;
93 std::vector<int> to_clean;
94 for (const int ref : clause) {
95 const Index index = IndexFromLiteral(ref);
96 for (const int var : enforcement_to_vars_[index]) {
97 if (tmp_num_occurrences_[var] == 0) {
98 to_clean.push_back(var);
99 }
100 tmp_num_occurrences_[var]++;
101 if (tmp_num_occurrences_[var] == clause.size()) {
102 to_process.push_back(var);
103 }
104 }
105 }
106
107 // Clear the counts.
108 for (const int var : to_clean) {
109 tmp_num_occurrences_[var] = 0;
110 }
111
112 // Compute the domain unions.
113 std::vector<Domain> domains(to_process.size());
114 for (const int ref : clause) {
115 const Index index = IndexFromLiteral(ref);
116 for (int i = 0; i < to_process.size(); ++i) {
117 domains[i] = domains[i].UnionWith(deductions_.at({index, to_process[i]}));
118 }
119 }
120
121 for (int i = 0; i < to_process.size(); ++i) {
122 result.push_back({to_process[i], std::move(domains[i])});
123 }
124 return result;
125}
126
127namespace {
128
129// Helper method for variable substitution.
130// Sorts and merges the terms in 'terms'.
131// Returns false on overflow.
132bool SortAndMergeTerms(std::vector<std::pair<int, int64_t>>* terms) {
133 std::sort(terms->begin(), terms->end());
134
135 int new_size = 0;
136 int current_var = 0;
137 int64_t current_coeff = 0;
138 for (const auto& entry : *terms) {
139 DCHECK(RefIsPositive(entry.first));
140 if (entry.first == current_var) {
141 current_coeff = CapAdd(current_coeff, entry.second);
142 if (AtMinOrMaxInt64(current_coeff)) return false;
143 } else {
144 if (current_coeff != 0) {
145 (*terms)[new_size++] = {current_var, current_coeff};
146 }
147 current_var = entry.first;
148 current_coeff = entry.second;
149 }
150 }
151 if (current_coeff != 0) {
152 (*terms)[new_size++] = {current_var, current_coeff};
153 }
154 terms->resize(new_size);
155 return true;
156}
157
158} // namespace
159
160bool AddLinearConstraintMultiple(int64_t factor, const ConstraintProto& to_add,
161 ConstraintProto* to_modify) {
162 if (factor == 0) return true;
163 DCHECK_EQ(to_add.constraint_case(), ConstraintProto::kLinear);
164 DCHECK_EQ(to_modify->constraint_case(), ConstraintProto::kLinear);
165
166 // Copy to_modify terms.
167 std::vector<std::pair<int, int64_t>> terms;
168 LinearConstraintProto* out = to_modify->mutable_linear();
169 const int initial_size = out->vars().size();
170 for (int i = 0; i < initial_size; ++i) {
171 const int var = out->vars(i);
172 const int64_t coeff = out->coeffs(i);
173 if (!RefIsPositive(var)) return false;
174 terms.push_back({var, coeff});
175 }
176
177 // Add factor * to_add and check first kind of overflow.
178 const int to_add_size = to_add.linear().vars().size();
179 for (int i = 0; i < to_add_size; ++i) {
180 const int var = to_add.linear().vars(i);
181 const int64_t coeff = to_add.linear().coeffs(i);
182 if (!RefIsPositive(var)) return false;
183 terms.push_back({var, CapProd(coeff, factor)});
184 if (AtMinOrMaxInt64(terms.back().second)) return false;
185 }
186
187 // Merge terms, return false if we get an overflow here.
188 if (!SortAndMergeTerms(&terms)) return false;
189
190 // Copy terms.
191 out->clear_vars();
192 out->clear_coeffs();
193 for (const auto [var, coeff] : terms) {
194 out->add_vars(var);
195 out->add_coeffs(coeff);
196 }
197
198 // Write new rhs. We want to be exact during the multiplication. Note that in
199 // practice this domain is fixed, so this will always be the case.
200 bool exact = false;
201 Domain offset = ReadDomainFromProto(to_add.linear());
202 offset = offset.MultiplicationBy(factor, &exact);
203 CHECK(exact);
204
205 const Domain rhs = ReadDomainFromProto(*out);
206 FillDomainInProto(rhs.AdditionWith(offset), out);
207 return true;
208}
209
210bool SubstituteVariable(int var, int64_t var_coeff_in_definition,
211 const ConstraintProto& definition,
212 ConstraintProto* ct) {
213 CHECK(RefIsPositive(var));
214
215 // Get the coefficient of var in the constraint.
216 // We assume positive reference here (it should always be the case now).
217 // If we don't find var, we abort.
218 int64_t var_coeff = 0;
219 const int initial_ct_size = ct->linear().vars().size();
220 for (int i = 0; i < initial_ct_size; ++i) {
221 const int ref = ct->linear().vars(i);
222 if (!RefIsPositive(ref)) return false;
223 if (ref == var) {
224 // If var appear multiple time, we add all its coefficients.
225 var_coeff += ct->linear().coeffs(i);
226 }
227 }
228 if (var_coeff == 0) return false;
229
230 CHECK_EQ(std::abs(var_coeff_in_definition), 1);
231 const int64_t factor = var_coeff_in_definition > 0 ? -var_coeff : var_coeff;
232 return AddLinearConstraintMultiple(factor, definition, ct);
233}
234
236 num_at_most_ones_ = 0;
237 amo_indices_.clear();
238}
239
240void ActivityBoundHelper::AddAtMostOne(absl::Span<const int> amo) {
241 int num_skipped = 0;
242 const int complexity_limit = 50;
243 for (const int literal : amo) {
244 const Index i = IndexFromLiteral(literal);
245 if (i >= amo_indices_.size()) amo_indices_.resize(i + 1);
246 if (amo_indices_[i].size() >= complexity_limit) ++num_skipped;
247 }
248 if (num_skipped + 1 >= amo.size()) return;
249
250 // Add it.
251 const int unique_index = num_at_most_ones_++;
252 for (const int literal : amo) {
253 const Index i = IndexFromLiteral(literal);
254 if (amo_indices_[i].size() < complexity_limit) {
255 amo_indices_[i].push_back(unique_index);
256 }
257 }
258}
259
260// TODO(user): Add long ones first, or at least the ones of size 2 after.
262 for (const ConstraintProto& ct : proto.constraints()) {
263 const auto type = ct.constraint_case();
264 if (type == ConstraintProto::kAtMostOne) {
265 AddAtMostOne(ct.at_most_one().literals());
266 } else if (type == ConstraintProto::kExactlyOne) {
267 AddAtMostOne(ct.exactly_one().literals());
268 } else if (type == ConstraintProto::kBoolAnd) {
269 if (ct.enforcement_literal().size() == 1) {
270 const int a = ct.enforcement_literal(0);
271 for (const int b : ct.bool_and().literals()) {
272 // a => b same as amo(a, not(b)).
274 }
275 }
276 }
277 }
278}
279
280int64_t ActivityBoundHelper::ComputeActivity(
281 bool compute_min, absl::Span<const std::pair<int, int64_t>> terms,
282 std::vector<std::array<int64_t, 2>>* conditional) {
283 tmp_terms_for_compute_activity_.clear();
284 tmp_terms_for_compute_activity_.reserve(terms.size());
285 int64_t offset = 0;
286 for (auto [lit, coeff] : terms) {
287 if (compute_min) coeff = -coeff; // Negate.
288 if (coeff >= 0) {
289 tmp_terms_for_compute_activity_.push_back({lit, coeff});
290 } else {
291 // l is the same as 1 - (1 - l)
292 tmp_terms_for_compute_activity_.push_back({NegatedRef(lit), -coeff});
293 offset += coeff;
294 }
295 }
296 const int64_t internal_result =
297 ComputeMaxActivityInternal(tmp_terms_for_compute_activity_, conditional);
298
299 // Correct everything.
300 if (conditional != nullptr) {
301 const int num_terms = terms.size();
302 for (int i = 0; i < num_terms; ++i) {
303 if (tmp_terms_for_compute_activity_[i].first != terms[i].first) {
304 // The true/false meaning is swapped
305 std::swap((*conditional)[i][0], (*conditional)[i][1]);
306 }
307 (*conditional)[i][0] += offset;
308 (*conditional)[i][1] += offset;
309 if (compute_min) {
310 (*conditional)[i][0] = -(*conditional)[i][0];
311 (*conditional)[i][1] = -(*conditional)[i][1];
312 }
313 }
314 }
315 if (compute_min) return -(offset + internal_result);
316 return offset + internal_result;
317}
318
319// Use trivial heuristic for now:
320// - Sort by decreasing coeff.
321// - If belong to a chosen part, use it.
322// - If not, choose biggest part left. TODO(user): compute sum of coeff in part?
323void ActivityBoundHelper::PartitionIntoAmo(
324 absl::Span<const std::pair<int, int64_t>> terms) {
325 auto amo_sums = amo_sums_.ClearedView(num_at_most_ones_);
326
327 const int num_terms = terms.size();
328 to_sort_.clear();
329 to_sort_.reserve(num_terms);
330 for (int i = 0; i < num_terms; ++i) {
331 const Index index = IndexFromLiteral(terms[i].first);
332 const int64_t coeff = terms[i].second;
333 DCHECK_GE(coeff, 0);
334 if (index < amo_indices_.size()) {
335 for (const int a : amo_indices_[index]) {
336 amo_sums[a] += coeff;
337 }
338 }
339 to_sort_.push_back(
340 TermWithIndex{.coeff = coeff, .index = index, .span_index = i});
341 }
342 std::sort(to_sort_.begin(), to_sort_.end(),
343 [](const TermWithIndex& a, const TermWithIndex& b) {
344 // We take into account the index to make the result
345 // deterministic.
346 return std::tie(a.coeff, a.index) > std::tie(b.coeff, b.index);
347 });
348
349 int num_parts = 0;
350 partition_.resize(num_terms);
351 for (const TermWithIndex& term : to_sort_) {
352 const int original_i = term.span_index;
353 const Index index = term.index;
354 const int64_t coeff = term.coeff;
355 int best = -1;
356 int64_t best_sum = 0;
357 bool done = false;
358 if (index < amo_indices_.size()) {
359 for (const int a : amo_indices_[index]) {
360 // Tricky/Optim: we use negative amo_sums to indicate if this amo was
361 // already used and its dense index (we use -1 - index).
362 const int64_t sum_left = amo_sums[a];
363 if (sum_left < 0) {
364 partition_[original_i] = -sum_left - 1;
365 done = true;
366 break;
367 }
368
369 amo_sums[a] -= coeff;
370 if (sum_left > best_sum) {
371 best_sum = sum_left;
372 best = a;
373 }
374 }
375 }
376 if (done) continue;
377
378 // New element.
379 if (best == -1) {
380 partition_[original_i] = num_parts++;
381 } else {
382 amo_sums[best] = -num_parts - 1; // "dense indexing"
383 partition_[original_i] = num_parts;
384 ++num_parts;
385 }
386 }
387 for (const int p : partition_) CHECK_LT(p, num_parts);
388 CHECK_LE(num_parts, num_terms);
389}
390
391// Similar algo as above for this simpler case.
392std::vector<absl::Span<const int>>
393ActivityBoundHelper::PartitionLiteralsIntoAmo(absl::Span<const int> literals) {
394 auto amo_sums = amo_sums_.ClearedView(num_at_most_ones_);
395 for (const int ref : literals) {
396 const Index index = IndexFromLiteral(ref);
397 if (index < amo_indices_.size()) {
398 for (const int a : amo_indices_[index]) {
399 amo_sums[a] += 1;
400 }
401 }
402 }
403
404 int num_parts = 0;
405 const int num_literals = literals.size();
406 partition_.resize(num_literals);
407 for (int i = 0; i < literals.size(); ++i) {
408 const Index index = IndexFromLiteral(literals[i]);
409 int best = -1;
410 int64_t best_sum = 0;
411 bool done = false;
412 if (index < amo_indices_.size()) {
413 for (const int a : amo_indices_[index]) {
414 const int64_t sum_left = amo_sums[a];
415
416 // Tricky/Optim: we use negative amo_sums to indicate if this amo was
417 // already used and its dense index (we use -1 - index).
418 if (sum_left < 0) {
419 partition_[i] = -sum_left - 1;
420 done = true;
421 break;
422 }
423
424 amo_sums[a] -= 1;
425 if (sum_left > best_sum) {
426 best_sum = sum_left;
427 best = a;
428 }
429 }
430 }
431 if (done) continue;
432
433 // New element.
434 if (best != -1) {
435 amo_sums[best] = -num_parts - 1;
436 }
437 partition_[i] = num_parts++;
438 }
439
440 // We have the partition, lets construct the spans now.
441 part_to_literals_.ResetFromFlatMapping(partition_, literals);
442 DCHECK_EQ(part_to_literals_.size(), num_parts);
443 return part_to_literals_.AsVectorOfSpan();
444}
445
446bool ActivityBoundHelper::IsAmo(absl::Span<const int> literals) {
447 auto amo_sums = amo_sums_.ClearedView(num_at_most_ones_);
448 for (int i = 0; i < literals.size(); ++i) {
449 bool has_max_size = false;
450 const Index index = IndexFromLiteral(literals[i]);
451 if (index >= amo_indices_.size()) return false;
452 for (const int a : amo_indices_[index]) {
453 if (amo_sums[a]++ == i) has_max_size = true;
454 }
455 if (!has_max_size) return false;
456 }
457 return true;
458}
459
460int64_t ActivityBoundHelper::ComputeMaxActivityInternal(
461 absl::Span<const std::pair<int, int64_t>> terms,
462 std::vector<std::array<int64_t, 2>>* conditional) {
463 PartitionIntoAmo(terms);
464
465 // Compute the max coefficient in each partition.
466 const int num_terms = terms.size();
467 max_by_partition_.assign(num_terms, 0);
468 second_max_by_partition_.assign(num_terms, 0);
469 for (int i = 0; i < num_terms; ++i) {
470 const int p = partition_[i];
471 const int64_t coeff = terms[i].second;
472 if (coeff >= max_by_partition_[p]) {
473 second_max_by_partition_[p] = max_by_partition_[p];
474 max_by_partition_[p] = coeff;
475 } else if (coeff > second_max_by_partition_[p]) {
476 second_max_by_partition_[p] = coeff;
477 }
478 }
479
480 // Once we have this, we can compute bound.
481 int64_t max_activity = 0;
482 for (int p = 0; p < partition_.size(); ++p) {
483 max_activity += max_by_partition_[p];
484 }
485 if (conditional != nullptr) {
486 conditional->resize(num_terms);
487 for (int i = 0; i < num_terms; ++i) {
488 const int64_t coeff = terms[i].second;
489 const int p = partition_[i];
490 const int64_t max_used = max_by_partition_[p];
491
492 // We have two cases depending if coeff was the maximum in its part or
493 // not.
494 if (coeff == max_used) {
495 // Use the second max.
496 (*conditional)[i][0] =
497 max_activity - max_used + second_max_by_partition_[p];
498 (*conditional)[i][1] = max_activity;
499 } else {
500 // The max is still there, no change at 0 but change for 1.
501 (*conditional)[i][0] = max_activity;
502 (*conditional)[i][1] = max_activity - max_used + coeff;
503 }
504 }
505 }
506 return max_activity;
507}
508
509bool ActivityBoundHelper::AppearInTriggeredAmo(int literal) const {
510 const Index index = IndexFromLiteral(literal);
511 if (index >= amo_indices_.size()) return false;
512 for (const int amo : amo_indices_[index]) {
513 if (triggered_amo_.contains(amo)) return true;
514 }
515 return false;
516}
517
519 absl::Span<const int> refs, ConstraintProto* ct,
520 absl::flat_hash_set<int>* literals_at_true) {
521 if (ct->enforcement_literal().empty()) return true;
522
523 literals_at_true->clear();
524 triggered_amo_.clear();
525 int new_size = 0;
526 for (int i = 0; i < ct->enforcement_literal().size(); ++i) {
527 const int ref = ct->enforcement_literal(i);
528 if (literals_at_true->contains(ref)) continue; // Duplicate.
529 if (literals_at_true->contains(NegatedRef(ref))) return false; // False.
530 literals_at_true->insert(ref);
531
532 // If a previous enforcement literal implies this one, we can skip it.
533 //
534 // Tricky: We need to do that before appending the amo containing ref in
535 // case an amo contains both ref and not(ref).
536 // TODO(user): Ideally these amo should not be added to this class.
537 if (AppearInTriggeredAmo(NegatedRef(ref))) continue;
538
539 const Index index = IndexFromLiteral(ref);
540 if (index < amo_indices_.size()) {
541 for (const int a : amo_indices_[index]) {
542 // If some other literal is at one in this amo, literal must be false,
543 // and so the constraint cannot be enforced.
544 const auto [_, inserted] = triggered_amo_.insert(a);
545 if (!inserted) return false;
546 }
547 }
548
549 // Keep this enforcement.
550 ct->set_enforcement_literal(new_size++, ref);
551 }
552 ct->mutable_enforcement_literal()->Truncate(new_size);
553
554 for (const int ref : refs) {
555 // Skip already fixed.
556 if (literals_at_true->contains(ref)) continue;
557 if (literals_at_true->contains(NegatedRef(ref))) continue;
558 for (const int to_test : {ref, NegatedRef(ref)}) {
559 const Index index = IndexFromLiteral(to_test);
560 if (index < amo_indices_.size()) {
561 for (const int a : amo_indices_[index]) {
562 if (triggered_amo_.contains(a)) {
563 // If some other literal is at one in this amo,
564 // literal must be false.
565 if (literals_at_true->contains(to_test)) return false;
566 literals_at_true->insert(NegatedRef(to_test));
567 break;
568 }
569 }
570 }
571 }
572 }
573
574 return true;
575}
576
578 absl::Span<const std::pair<int, int64_t>> boolean_terms,
579 const Domain& other_terms, const Domain& rhs, ConstraintProto* ct) {
580 if (boolean_terms.empty()) return 0;
581 tmp_set_.clear();
582 triggered_amo_.clear();
583 tmp_boolean_terms_in_some_amo_.clear();
584 tmp_boolean_terms_in_some_amo_.reserve(boolean_terms.size());
585 int num_enforcement_to_check = 0;
586 for (const int enf_lit : ct->enforcement_literal()) {
587 const Index negated_index = IndexFromLiteral(NegatedRef(enf_lit));
588 if (negated_index >= amo_indices_.size()) continue;
589 if (amo_indices_[negated_index].empty()) continue;
590 triggered_amo_.insert(amo_indices_[negated_index].begin(),
591 amo_indices_[negated_index].end());
592 ++num_enforcement_to_check;
593 }
594 int non_amo_min_activity = 0;
595 int non_amo_max_activity = 0;
596 auto log_work = [&]() {
597 VLOG(1) << "RemoveEnforcementThatMakesConstraintTrivial: "
598 "aborting because too expensive: "
599 << num_enforcement_to_check << " " << boolean_terms.size();
600 return 0;
601 };
602 static const int kMaxWork = 1e9;
603 int work = 0;
604 for (int i = 0; i < boolean_terms.size(); ++i) {
605 const int ref = boolean_terms[i].first;
606 const int64_t coeff = boolean_terms[i].second;
607 if (AppearInTriggeredAmo(ref) || AppearInTriggeredAmo(NegatedRef(ref))) {
608 tmp_boolean_terms_in_some_amo_.push_back(i);
609 } else {
610 if (coeff > 0) {
611 non_amo_max_activity += coeff;
612 } else {
613 non_amo_min_activity += coeff;
614 }
615 }
616 work += NumAmoForVariable(ref);
617 if (work > kMaxWork) return log_work();
618 }
619
620 for (const int enf_lit : ct->enforcement_literal()) {
621 const Index negated_index = IndexFromLiteral(NegatedRef(enf_lit));
622 if (negated_index >= amo_indices_.size()) continue;
623 if (amo_indices_[negated_index].empty()) continue;
624
625 triggered_amo_.clear();
626 triggered_amo_.insert(amo_indices_[negated_index].begin(),
627 amo_indices_[negated_index].end());
628
629 // Compute min_max activity when enf_lit is false.
630 int64_t min_activity = non_amo_min_activity;
631 int64_t max_activity = non_amo_max_activity;
632 for (const int i : tmp_boolean_terms_in_some_amo_) {
633 const int ref = boolean_terms[i].first;
634 const int64_t coeff = boolean_terms[i].second;
635 // This is not supposed to happen after PresolveEnforcement(), so we
636 // just abort in this case.
637 if (ref == enf_lit || ref == NegatedRef(enf_lit)) break;
638
639 const bool is_true = AppearInTriggeredAmo(NegatedRef(ref));
640 const bool is_false = AppearInTriggeredAmo(ref);
641 work += NumAmoForVariable(ref);
642 if (work > kMaxWork) return log_work();
643
644 // Similarly, this is not supposed to happen after PresolveEnforcement().
645 if (is_true && is_false) break;
646
647 if (is_false) continue;
648 if (is_true) {
649 min_activity += coeff;
650 max_activity += coeff;
651 continue;
652 }
653 if (coeff > 0) {
654 max_activity += coeff;
655 } else {
656 min_activity += coeff;
657 }
658 }
659
660 if (Domain(min_activity, max_activity)
661 .AdditionWith(other_terms)
662 .IsIncludedIn(rhs)) {
663 tmp_set_.insert(enf_lit);
664 }
665 }
666
667 if (!tmp_set_.empty()) {
668 int new_size = 0;
669 for (const int ref : ct->enforcement_literal()) {
670 if (tmp_set_.contains(ref)) continue;
671 ct->set_enforcement_literal(new_size++, ref);
672 }
673 const int old_size = ct->enforcement_literal().size();
674 ct->mutable_enforcement_literal()->Truncate(new_size);
675 return old_size - new_size;
676 }
677 return 0;
678}
679
681 absl::Span<const int> clause) {
682 uint64_t hash = 0;
683 for (const int ref : clause) {
684 const Index index = IndexFromLiteral(ref);
685 while (index >= literal_to_hash_.size()) {
686 // We use random value for a literal hash.
687 literal_to_hash_.push_back(absl::Uniform<uint64_t>(random_));
688 }
689 hash ^= literal_to_hash_[index];
690 }
691
692 if (c >= clause_to_hash_.size()) clause_to_hash_.resize(c + 1, 0);
693 clause_to_hash_[c] = hash;
694}
695
697 absl::Span<const int> literals) {
698 uint64_t hash = 0;
699 for (const int ref : literals) {
700 const Index index = IndexFromLiteral(NegatedRef(ref));
701 while (index >= literal_to_hash_.size()) {
702 // We use random value for a literal hash.
703 literal_to_hash_.push_back(absl::Uniform<uint64_t>(random_));
704 }
705 hash ^= literal_to_hash_[index];
706 }
707 return hash;
708}
709
711 const LinearConstraintProto& lin2, int* var1,
712 int64_t* coeff1, int* var2, int64_t* coeff2) {
713 const int size = lin1.vars().size();
714 CHECK_EQ(size, lin2.vars().size());
715 *coeff1 = 0;
716 *coeff2 = 0;
717 int i = 0;
718 int j = 0;
719 while (i < size || j < size) {
720 // Note that we can't have both undefined or the loop would have exited.
721 const int v1 = i < size ? lin1.vars(i) : std::numeric_limits<int>::max();
722 const int v2 = j < size ? lin2.vars(j) : std::numeric_limits<int>::max();
723
724 // Same term, continue.
725 if (v1 == v2 && lin1.coeffs(i) == lin2.coeffs(j)) {
726 ++i;
727 ++j;
728 continue;
729 }
730
731 // We have a diff.
732 // term i not in lin2.
733 if (v1 < v2) {
734 if (*coeff1 != 0) return false; // Returns if second diff.
735 *var1 = v1;
736 *coeff1 = lin1.coeffs(i);
737 ++i;
738 continue;
739 }
740
741 // term j not in lin1.
742 if (v1 > v2) {
743 if (*coeff2 != 0) return false; // Returns if second diff.
744 *var2 = v2;
745 *coeff2 = lin2.coeffs(j);
746 ++j;
747 continue;
748 }
749
750 // Coeff differ. Returns if we had a diff previously.
751 if (*coeff1 != 0 || *coeff2 != 0) return false;
752 *var1 = v1;
753 *var2 = v2;
754 *coeff1 = lin1.coeffs(i);
755 *coeff2 = lin2.coeffs(j);
756 ++i;
757 ++j;
758 }
759
760 return *coeff1 != 0 && *coeff2 != 0;
761}
762
763} // namespace sat
764} // namespace operations_research
Domain MultiplicationBy(int64_t coeff, bool *exact=nullptr) const
Domain IntersectionWith(const Domain &domain) const
Domain AdditionWith(const Domain &domain) const
bool IsIncludedIn(const Domain &domain) const
void AddAllAtMostOnes(const CpModelProto &proto)
void AddAtMostOne(absl::Span< const int > amo)
int RemoveEnforcementThatMakesConstraintTrivial(absl::Span< const std::pair< int, int64_t > > boolean_terms, const Domain &other_terms, const Domain &rhs, ConstraintProto *ct)
std::vector< absl::Span< const int > > PartitionLiteralsIntoAmo(absl::Span< const int > literals)
bool PresolveEnforcement(absl::Span< const int > refs, ConstraintProto *ct, absl::flat_hash_set< int > *literals_at_true)
bool IsAmo(absl::Span< const int > literals)
void RegisterClause(int c, absl::Span< const int > clause)
uint64_t HashOfNegatedLiterals(absl::Span< const int > literals)
::int32_t enforcement_literal(int index) const
void set_enforcement_literal(int index, ::int32_t value)
const ::operations_research::sat::LinearConstraintProto & linear() const
::operations_research::sat::LinearConstraintProto *PROTOBUF_NONNULL mutable_linear()
::google::protobuf::RepeatedField<::int32_t > *PROTOBUF_NONNULL mutable_enforcement_literal()
const ::operations_research::sat::ConstraintProto & constraints(int index) const
std::vector< std::pair< int, Domain > > ProcessClause(absl::Span< const int > clause)
void AddDeduction(int literal_ref, int var, Domain domain)
Domain ImpliedDomain(int literal_ref, int var) const
bool FindSingleLinearDifference(const LinearConstraintProto &lin1, const LinearConstraintProto &lin2, int *var1, int64_t *coeff1, int *var2, int64_t *coeff2)
bool SubstituteVariable(int var, int64_t var_coeff_in_definition, const ConstraintProto &definition, ConstraintProto *ct)
bool AddLinearConstraintMultiple(int64_t factor, const ConstraintProto &to_add, ConstraintProto *to_modify)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
OR-Tools root namespace.
bool AtMinOrMaxInt64(int64_t x)
int64_t CapAdd(int64_t x, int64_t y)
ClosedInterval::Iterator end(ClosedInterval interval)
int64_t CapProd(int64_t x, int64_t y)
ClosedInterval::Iterator begin(ClosedInterval interval)