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