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