Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
presolve_context.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 <cstdint>
18#include <cstdlib>
19#include <limits>
20#include <numeric>
21#include <optional>
22#include <string>
23#include <tuple>
24#include <utility>
25#include <vector>
26
27#include "absl/base/attributes.h"
28#include "absl/container/btree_map.h"
29#include "absl/container/flat_hash_map.h"
30#include "absl/container/flat_hash_set.h"
31#include "absl/flags/flag.h"
32#include "absl/log/check.h"
33#include "absl/meta/type_traits.h"
34#include "absl/numeric/int128.h"
35#include "absl/strings/str_cat.h"
36#include "absl/strings/string_view.h"
37#include "absl/types/span.h"
40#include "ortools/sat/cp_model.pb.h"
45#include "ortools/sat/integer.h"
47#include "ortools/sat/model.h"
48#include "ortools/sat/sat_parameters.pb.h"
50#include "ortools/sat/util.h"
52#include "ortools/util/bitset.h"
57
58ABSL_FLAG(bool, cp_model_debug_postsolve, false,
59 "DEBUG ONLY. When set to true, the mapping_model.proto will contains "
60 "file:line of the code that created that constraint. This is helpful "
61 "for debugging postsolve");
62
63namespace operations_research {
64namespace sat {
65
67 return context->GetLiteralRepresentative(ref_);
68}
69
70int SavedVariable::Get() const { return ref_; }
71
72void PresolveContext::ClearStats() { stats_by_rule_name_.clear(); }
73
75 IntegerVariableProto* const var = working_model->add_variables();
76 FillDomainInProto(domain, var);
78 return working_model->variables_size() - 1;
79}
80
82 const Domain& domain, absl::Span<const std::pair<int, int64_t>> definition,
83 bool append_constraint_to_mapping_model) {
84 if (domain.Size() == 1) {
85 UpdateRuleStats("TODO new_var_definition : use boolean equation");
86 }
87
88 const int new_var = NewIntVar(domain);
89
90 // Create new linear constraint new_var = definition.
91 // TODO(user): When we encounter overflow (rare), we still create a variable.
92 auto* new_linear =
93 append_constraint_to_mapping_model
94 ? NewMappingConstraint(__FILE__, __LINE__)->mutable_linear()
95 : working_model->add_constraints()->mutable_linear();
96 for (const auto [var, coeff] : definition) {
97 new_linear->add_vars(var);
98 new_linear->add_coeffs(coeff);
99 }
100 new_linear->add_vars(new_var);
101 new_linear->add_coeffs(-1);
102 new_linear->add_domain(0);
103 new_linear->add_domain(0);
104 if (PossibleIntegerOverflow(*working_model, new_linear->vars(),
105 new_linear->coeffs())) {
106 UpdateRuleStats("TODO new_var_definition : possible overflow.");
107 if (append_constraint_to_mapping_model) {
108 mapping_model->mutable_constraints()->RemoveLast();
109 } else {
110 working_model->mutable_constraints()->RemoveLast();
111 }
112 return -1;
113 }
114 if (!append_constraint_to_mapping_model) {
116 }
117
118 solution_crush_.SetVarToLinearExpression(new_var, definition);
119 return new_var;
120}
121
122int PresolveContext::NewBoolVar(absl::string_view source) {
123 UpdateRuleStats(absl::StrCat("new_bool: ", source));
124 return NewIntVar(Domain(0, 1));
125}
126
127int PresolveContext::NewBoolVarWithClause(absl::Span<const int> clause) {
128 const int new_var = NewBoolVar("with clause");
129 solution_crush_.SetVarToClause(new_var, clause);
130 return new_var;
131}
132
134 absl::Span<const int> conjunction) {
135 const int new_var = NewBoolVar("with conjunction");
136 solution_crush_.SetVarToConjunction(new_var, conjunction);
137 return new_var;
138}
139
141 if (!true_literal_is_defined_) {
142 true_literal_is_defined_ = true;
143 true_literal_ = NewIntVar(Domain(1));
144 solution_crush_.SetVarToConjunction(true_literal_, {});
145 }
146 return true_literal_;
147}
148
150
151// a => b.
153 if (a == b) return;
154 ConstraintProto* const ct = working_model->add_constraints();
155 ct->add_enforcement_literal(a);
156 ct->mutable_bool_and()->add_literals(b);
157}
158
159// b => x in [lb, ub].
160void PresolveContext::AddImplyInDomain(int b, int x, const Domain& domain) {
161 ConstraintProto* const imply = working_model->add_constraints();
162
163 // Doing it like this seems to use slightly less memory.
164 // TODO(user): Find the best way to create such small proto.
165 imply->mutable_enforcement_literal()->Resize(1, b);
166 LinearConstraintProto* mutable_linear = imply->mutable_linear();
167 mutable_linear->mutable_vars()->Resize(1, x);
168 mutable_linear->mutable_coeffs()->Resize(1, 1);
169 FillDomainInProto(domain, mutable_linear);
170}
171
172void PresolveContext::AddImplyInDomain(int b, const LinearExpressionProto& expr,
173 const Domain& domain) {
174 ConstraintProto* const imply = working_model->add_constraints();
175
176 imply->mutable_enforcement_literal()->Resize(1, b);
177 LinearConstraintProto* mutable_linear = imply->mutable_linear();
178 FillDomainInProto(domain, mutable_linear);
179 AddLinearExpressionToLinearConstraint(expr, 1, imply->mutable_linear());
180}
181
183 return domains_[PositiveRef(ref)].IsEmpty();
184}
185
186bool PresolveContext::IsFixed(int ref) const {
187 DCHECK_LT(PositiveRef(ref), domains_.size());
188 DCHECK(!DomainIsEmpty(ref));
189 return domains_[PositiveRef(ref)].IsFixed();
190}
191
193 const int var = PositiveRef(ref);
194 return domains_[var].Min() >= 0 && domains_[var].Max() <= 1;
195}
196
198 DCHECK(CanBeUsedAsLiteral(lit));
199 if (RefIsPositive(lit)) {
200 return domains_[lit].Min() == 1;
201 } else {
202 return domains_[PositiveRef(lit)].Max() == 0;
203 }
204}
205
207 DCHECK(CanBeUsedAsLiteral(lit));
208 if (RefIsPositive(lit)) {
209 return domains_[lit].Max() == 0;
210 } else {
211 return domains_[PositiveRef(lit)].Min() == 1;
212 }
213}
214
215int64_t PresolveContext::MinOf(int ref) const {
216 DCHECK(!DomainIsEmpty(ref));
217 return RefIsPositive(ref) ? domains_[PositiveRef(ref)].Min()
218 : -domains_[PositiveRef(ref)].Max();
219}
220
221int64_t PresolveContext::MaxOf(int ref) const {
222 DCHECK(!DomainIsEmpty(ref));
223 return RefIsPositive(ref) ? domains_[PositiveRef(ref)].Max()
224 : -domains_[PositiveRef(ref)].Min();
225}
226
227int64_t PresolveContext::FixedValue(int ref) const {
228 DCHECK(!DomainIsEmpty(ref));
229 CHECK(IsFixed(ref));
230 return RefIsPositive(ref) ? domains_[PositiveRef(ref)].Min()
231 : -domains_[PositiveRef(ref)].Min();
232}
233
234int64_t PresolveContext::MinOf(const LinearExpressionProto& expr) const {
235 int64_t result = expr.offset();
236 for (int i = 0; i < expr.vars_size(); ++i) {
237 const int64_t coeff = expr.coeffs(i);
238 if (coeff > 0) {
239 result += coeff * MinOf(expr.vars(i));
240 } else {
241 result += coeff * MaxOf(expr.vars(i));
242 }
243 }
244 return result;
245}
246
247int64_t PresolveContext::MaxOf(const LinearExpressionProto& expr) const {
248 int64_t result = expr.offset();
249 for (int i = 0; i < expr.vars_size(); ++i) {
250 const int64_t coeff = expr.coeffs(i);
251 if (coeff > 0) {
252 result += coeff * MaxOf(expr.vars(i));
253 } else {
254 result += coeff * MinOf(expr.vars(i));
255 }
256 }
257 return result;
258}
259
260bool PresolveContext::IsFixed(const LinearExpressionProto& expr) const {
261 for (int i = 0; i < expr.vars_size(); ++i) {
262 if (expr.coeffs(i) != 0 && !IsFixed(expr.vars(i))) return false;
263 }
264 return true;
265}
266
267int64_t PresolveContext::FixedValue(const LinearExpressionProto& expr) const {
268 int64_t result = expr.offset();
269 for (int i = 0; i < expr.vars_size(); ++i) {
270 if (expr.coeffs(i) == 0) continue;
271 result += expr.coeffs(i) * FixedValue(expr.vars(i));
272 }
273 return result;
274}
275
277 const LinearExpressionProto& expr) const {
278 int64_t result = expr.offset();
279 for (int i = 0; i < expr.vars_size(); ++i) {
280 if (expr.coeffs(i) == 0) continue;
281 const Domain& domain = domains_[expr.vars(i)];
282 if (!domain.IsFixed()) return std::nullopt;
283 result += expr.coeffs(i) * domain.Min();
284 }
285 return result;
286}
287
289 const LinearExpressionProto& expr) const {
290 Domain result(expr.offset());
291 for (int i = 0; i < expr.vars_size(); ++i) {
292 result = result.AdditionWith(
293 DomainOf(expr.vars(i)).MultiplicationBy(expr.coeffs(i)));
294 }
295 return result;
296}
297
299 const LinearExpressionProto& expr) const {
300 if (expr.vars().size() != 1) return false;
301 return CanBeUsedAsLiteral(expr.vars(0));
302}
303
305 const LinearExpressionProto& expr) const {
306 const int ref = expr.vars(0);
307 return RefIsPositive(ref) == (expr.coeffs(0) > 0) ? ref : NegatedRef(ref);
308}
309
311 const LinearExpressionProto& expr) const {
312 return expr.offset() == 0 && expr.vars_size() == 1 && expr.coeffs(0) == 1;
313}
314
315bool PresolveContext::ExpressionIsALiteral(const LinearExpressionProto& expr,
316 int* literal) const {
317 if (expr.vars_size() != 1) return false;
318 const int ref = expr.vars(0);
319 const int var = PositiveRef(ref);
320 if (MinOf(var) < 0 || MaxOf(var) > 1) return false;
321
322 if (expr.offset() == 0 && expr.coeffs(0) == 1 && RefIsPositive(ref)) {
323 if (literal != nullptr) *literal = ref;
324 return true;
325 }
326 if (expr.offset() == 1 && expr.coeffs(0) == -1 && RefIsPositive(ref)) {
327 if (literal != nullptr) *literal = NegatedRef(ref);
328 return true;
329 }
330 if (expr.offset() == 1 && expr.coeffs(0) == 1 && !RefIsPositive(ref)) {
331 if (literal != nullptr) *literal = ref;
332 return true;
333 }
334 return false;
335}
336
337// Note that we only support converted intervals.
339 const ConstraintProto& proto = working_model->constraints(ct_ref);
340 if (!proto.enforcement_literal().empty()) return false;
341 if (!IsFixed(proto.interval().start())) return false;
342 if (!IsFixed(proto.interval().size())) return false;
343 if (!IsFixed(proto.interval().end())) return false;
344 return true;
345}
346
347std::string PresolveContext::IntervalDebugString(int ct_ref) const {
348 if (IntervalIsConstant(ct_ref)) {
349 return absl::StrCat("interval_", ct_ref, "(", StartMin(ct_ref), "..",
350 EndMax(ct_ref), ")");
351 } else if (ConstraintIsOptional(ct_ref)) {
352 const int literal =
353 working_model->constraints(ct_ref).enforcement_literal(0);
354 if (SizeMin(ct_ref) == SizeMax(ct_ref)) {
355 return absl::StrCat("interval_", ct_ref, "(lit=", literal, ", ",
356 StartMin(ct_ref), " --(", SizeMin(ct_ref), ")--> ",
357 EndMax(ct_ref), ")");
358 } else {
359 return absl::StrCat("interval_", ct_ref, "(lit=", literal, ", ",
360 StartMin(ct_ref), " --(", SizeMin(ct_ref), "..",
361 SizeMax(ct_ref), ")--> ", EndMax(ct_ref), ")");
362 }
363 } else if (SizeMin(ct_ref) == SizeMax(ct_ref)) {
364 return absl::StrCat("interval_", ct_ref, "(", StartMin(ct_ref), " --(",
365 SizeMin(ct_ref), ")--> ", EndMax(ct_ref), ")");
366 } else {
367 return absl::StrCat("interval_", ct_ref, "(", StartMin(ct_ref), " --(",
368 SizeMin(ct_ref), "..", SizeMax(ct_ref), ")--> ",
369 EndMax(ct_ref), ")");
370 }
371}
372
373int64_t PresolveContext::StartMin(int ct_ref) const {
374 const IntervalConstraintProto& interval =
375 working_model->constraints(ct_ref).interval();
376 return MinOf(interval.start());
377}
378
379int64_t PresolveContext::StartMax(int ct_ref) const {
380 const IntervalConstraintProto& interval =
381 working_model->constraints(ct_ref).interval();
382 return MaxOf(interval.start());
383}
384
385int64_t PresolveContext::EndMin(int ct_ref) const {
386 const IntervalConstraintProto& interval =
387 working_model->constraints(ct_ref).interval();
388 return MinOf(interval.end());
389}
390
391int64_t PresolveContext::EndMax(int ct_ref) const {
392 const IntervalConstraintProto& interval =
393 working_model->constraints(ct_ref).interval();
394 return MaxOf(interval.end());
395}
396
397int64_t PresolveContext::SizeMin(int ct_ref) const {
398 const IntervalConstraintProto& interval =
399 working_model->constraints(ct_ref).interval();
400 return MinOf(interval.size());
401}
402
403int64_t PresolveContext::SizeMax(int ct_ref) const {
404 const IntervalConstraintProto& interval =
405 working_model->constraints(ct_ref).interval();
406 return MaxOf(interval.size());
407}
408
409// Tricky: If this variable is equivalent to another one (but not the
410// representative) and appear in just one constraint, then this constraint must
411// be the affine defining one. And in this case the code using this function
412// should do the proper stuff.
414 if (!ConstraintVariableGraphIsUpToDate()) return false;
415 const int var = PositiveRef(ref);
416 return var_to_constraints_[var].size() == 1;
417}
418
420 return !params_.keep_all_feasible_solutions_in_presolve() &&
421 VariableIsUnique(ref);
422}
423
425 if (!ConstraintVariableGraphIsUpToDate()) return false;
426 const int var = PositiveRef(ref);
427 return var_to_constraints_[var].size() == 2 &&
428 var_to_constraints_[var].contains(kObjectiveConstraint);
429}
430
431// Tricky: Same remark as for VariableIsUniqueAndRemovable().
432//
433// Also if the objective domain is constraining, we can't have a preferred
434// direction, so we cannot easily remove such variable.
436 if (!ConstraintVariableGraphIsUpToDate()) return false;
437 const int var = PositiveRef(ref);
438 return !params_.keep_all_feasible_solutions_in_presolve() &&
439 !objective_domain_is_constraining_ && VariableWithCostIsUnique(var);
440}
441
442// Here, even if the variable is equivalent to others, if its affine defining
443// constraints where removed, then it is not needed anymore.
445 if (!ConstraintVariableGraphIsUpToDate()) return false;
446 return var_to_constraints_[PositiveRef(ref)].empty();
447}
448
450 removed_variables_.insert(PositiveRef(ref));
451}
452
453// Note(user): I added an indirection and a function for this to be able to
454// display debug information when this return false. This should actually never
455// return false in the cases where it is used.
457 // It is okay to reuse removed fixed variable.
458 if (IsFixed(ref)) return false;
459 if (!removed_variables_.contains(PositiveRef(ref))) return false;
460 if (!var_to_constraints_[PositiveRef(ref)].empty()) {
461 SOLVER_LOG(logger_, "Variable ", PositiveRef(ref),
462 " was removed, yet it appears in some constraints!");
463 SOLVER_LOG(logger_, "affine relation: ",
465 for (const int c : var_to_constraints_[PositiveRef(ref)]) {
466 SOLVER_LOG(logger_, "constraint #", c, " : ",
467 c >= 0
468 ? ProtobufShortDebugString(working_model->constraints(c))
469 : "");
470 }
471 }
472 return true;
473}
474
476 int var) const {
477 CHECK(RefIsPositive(var));
478 if (!ConstraintVariableGraphIsUpToDate()) return false;
479 if (var_to_num_linear1_[var] == 0) return false;
480 return var_to_num_linear1_[var] == var_to_constraints_[var].size() ||
481 (var_to_constraints_[var].contains(kObjectiveConstraint) &&
482 var_to_num_linear1_[var] + 1 == var_to_constraints_[var].size());
483}
484
486 int var) const {
487 if (!ConstraintVariableGraphIsUpToDate()) return false;
488 if (var_to_num_linear1_[var] == 0) return false;
489 CHECK(RefIsPositive(var));
490 return var_to_num_linear1_[var] + 1 == var_to_constraints_[var].size();
491}
492
494 Domain result;
495 if (RefIsPositive(ref)) {
496 result = domains_[ref];
497 } else {
498 result = domains_[PositiveRef(ref)].Negation();
499 }
500 return result;
501}
502
503bool PresolveContext::DomainContains(int ref, int64_t value) const {
504 if (!RefIsPositive(ref)) {
505 return domains_[PositiveRef(ref)].Contains(-value);
506 }
507 return domains_[ref].Contains(value);
508}
509
510bool PresolveContext::DomainContains(const LinearExpressionProto& expr,
511 int64_t value) const {
512 CHECK_LE(expr.vars_size(), 1);
513 if (IsFixed(expr)) {
514 return FixedValue(expr) == value;
515 }
516 if (value > MaxOf(expr)) return false;
517 if (value < MinOf(expr)) return false;
518
519 // We assume expression is validated for overflow initially, and the code
520 // below should be overflow safe.
521 if ((value - expr.offset()) % expr.coeffs(0) != 0) return false;
522 return DomainContains(expr.vars(0), (value - expr.offset()) / expr.coeffs(0));
523}
524
525ABSL_MUST_USE_RESULT bool PresolveContext::IntersectDomainWith(
526 int ref, const Domain& domain, bool* domain_modified) {
527 DCHECK(!DomainIsEmpty(ref));
528 const int var = PositiveRef(ref);
529
530 if (RefIsPositive(ref)) {
531 if (domains_[var].IsIncludedIn(domain)) {
532 return true;
533 }
534 domains_[var] = domains_[var].IntersectionWith(domain);
535 } else {
536 const Domain temp = domain.Negation();
537 if (domains_[var].IsIncludedIn(temp)) {
538 return true;
539 }
540 domains_[var] = domains_[var].IntersectionWith(temp);
541 }
542
543 if (domain_modified != nullptr) {
544 *domain_modified = true;
545 }
546 modified_domains.Set(var);
547 if (domains_[var].IsEmpty()) {
549 absl::StrCat("var #", ref, " as empty domain after intersecting with ",
550 domain.ToString()));
551 }
552
553 solution_crush_.SetOrUpdateVarToDomain(var, domains_[var]);
554
555 // Propagate the domain of the representative right away.
556 // Note that the recursive call should only by one level deep.
558 if (r.representative == var) return true;
560 DomainOf(var)
561 .AdditionWith(Domain(-r.offset))
562 .InverseMultiplicationBy(r.coeff),
563 /*domain_modified=*/nullptr);
564}
565
566ABSL_MUST_USE_RESULT bool PresolveContext::IntersectDomainWith(
567 const LinearExpressionProto& expr, const Domain& domain,
568 bool* domain_modified) {
569 if (expr.vars().empty()) {
570 if (domain.Contains(expr.offset())) {
571 return true;
572 } else {
573 return NotifyThatModelIsUnsat(absl::StrCat(
575 " as empty domain after intersecting with ", domain.ToString()));
576 }
577 }
578 if (expr.vars().size() == 1) { // Affine
579 return IntersectDomainWith(expr.vars(0),
580 domain.AdditionWith(Domain(-expr.offset()))
581 .InverseMultiplicationBy(expr.coeffs(0)),
582 domain_modified);
583 }
584
585 // We don't do anything for longer expression for now.
586 return true;
587}
588
589ABSL_MUST_USE_RESULT bool PresolveContext::SetLiteralToFalse(int lit) {
590 const int var = PositiveRef(lit);
591 const int64_t value = RefIsPositive(lit) ? 0 : 1;
592 return IntersectDomainWith(var, Domain(value));
593}
594
595ABSL_MUST_USE_RESULT bool PresolveContext::SetLiteralToTrue(int lit) {
596 return SetLiteralToFalse(NegatedRef(lit));
597}
598
600 const ConstraintProto& ct = working_model->constraints(index);
601 if (ct.constraint_case() ==
602 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
603 return true;
604 }
605 for (const int literal : ct.enforcement_literal()) {
606 if (LiteralIsFalse(literal)) return true;
607 }
608 return false;
609}
610
612 const ConstraintProto& ct = working_model->constraints(ct_ref);
613 bool contains_one_free_literal = false;
614 for (const int literal : ct.enforcement_literal()) {
615 if (LiteralIsFalse(literal)) return false;
616 if (!LiteralIsTrue(literal)) contains_one_free_literal = true;
617 }
618 return contains_one_free_literal;
619}
620
621void PresolveContext::UpdateRuleStats(const std::string& name, int num_times) {
622 // Hack: we don't want to count TODO rules as this is used to decide if
623 // we loop again.
624 const bool is_todo = name.size() >= 4 && name.substr(0, 4) == "TODO";
625 if (!is_todo) num_presolve_operations += num_times;
626
627 if (logger_->LoggingIsEnabled()) {
628 if (VLOG_IS_ON(1)) {
629 int level = is_todo ? 3 : 2;
630 if (std::abs(num_presolve_operations -
631 params_.debug_max_num_presolve_operations()) <= 100) {
632 level = 1;
633 }
634 VLOG(level) << num_presolve_operations << " : " << name;
635 }
636
637 stats_by_rule_name_[name] += num_times;
638 }
639}
640
641void PresolveContext::UpdateLinear1Usage(const ConstraintProto& ct, int c) {
642 const int old_var = constraint_to_linear1_var_[c];
643 if (old_var >= 0) {
644 var_to_num_linear1_[old_var]--;
645 DCHECK_GE(var_to_num_linear1_[old_var], 0);
646 }
647 if (ct.constraint_case() == ConstraintProto::ConstraintCase::kLinear &&
648 ct.linear().vars().size() == 1) {
649 const int var = PositiveRef(ct.linear().vars(0));
650 constraint_to_linear1_var_[c] = var;
651 var_to_num_linear1_[var]++;
652 } else {
653 constraint_to_linear1_var_[c] = -1;
654 }
655}
656
657void PresolveContext::MaybeResizeIntervalData() {
658 // Lazy allocation so that we only do that if there are some interval.
659 const int num_constraints = constraint_to_vars_.size();
660 if (constraint_to_intervals_.size() != num_constraints) {
661 constraint_to_intervals_.resize(num_constraints);
662 interval_usage_.resize(num_constraints);
663 }
664}
665
666void PresolveContext::AddVariableUsage(int c) {
667 const ConstraintProto& ct = working_model->constraints(c);
668
669 constraint_to_vars_[c] = UsedVariables(ct);
670 for (const int v : constraint_to_vars_[c]) {
671 DCHECK_LT(v, var_to_constraints_.size());
672 DCHECK(!VariableWasRemoved(v));
673 var_to_constraints_[v].insert(c);
674 }
675
676 std::vector<int> used_interval = UsedIntervals(ct);
677 if (!used_interval.empty()) {
678 MaybeResizeIntervalData();
679 constraint_to_intervals_[c].swap(used_interval);
680 for (const int i : constraint_to_intervals_[c]) interval_usage_[i]++;
681 }
682
683 UpdateLinear1Usage(ct, c);
684
685#ifdef CHECK_HINT
686 // Crash if the loaded hint is infeasible for this constraint.
687 // This is helpful to debug a wrong presolve that kill a feasible solution.
688 if (working_model->has_solution_hint() &&
689 solution_crush_.SolutionIsLoaded() &&
691 solution_crush_.GetVarValues())) {
692 LOG(FATAL) << "Hint infeasible for constraint #" << c << " : "
693 << ct.ShortDebugString();
694 }
695#endif
696}
697
698void PresolveContext::EraseFromVarToConstraint(int var, int c) {
699 var_to_constraints_[var].erase(c);
700 if (var_to_constraints_[var].size() <= 3) {
702 }
703}
704
706 if (is_unsat_) return;
707 DCHECK_EQ(constraint_to_vars_.size(), working_model->constraints_size());
708 const ConstraintProto& ct = working_model->constraints(c);
709
710 // We don't optimize the interval usage as this is not super frequent.
711 std::vector<int> used_interval = UsedIntervals(ct);
712 if (c < constraint_to_intervals_.size() || !used_interval.empty()) {
713 MaybeResizeIntervalData();
714 for (const int i : constraint_to_intervals_[c]) interval_usage_[i]--;
715 constraint_to_intervals_[c].swap(used_interval);
716 for (const int i : constraint_to_intervals_[c]) interval_usage_[i]++;
717 }
718
719 // For the variables, we avoid an erase() followed by an insert() for the
720 // variables that didn't change.
721 std::vector<int> new_usage = UsedVariables(ct);
722 const absl::Span<const int> old_usage = constraint_to_vars_[c];
723 const int old_size = old_usage.size();
724 int i = 0;
725 for (const int var : new_usage) {
726 DCHECK(!VariableWasRemoved(var));
727 while (i < old_size && old_usage[i] < var) {
728 EraseFromVarToConstraint(old_usage[i], c);
729 ++i;
730 }
731 if (i < old_size && old_usage[i] == var) {
732 ++i;
733 } else {
734 var_to_constraints_[var].insert(c);
735 }
736 }
737 for (; i < old_size; ++i) {
738 EraseFromVarToConstraint(old_usage[i], c);
739 }
740 constraint_to_vars_[c].swap(new_usage);
741
742 UpdateLinear1Usage(ct, c);
743
744#ifdef CHECK_HINT
745 // Crash if the loaded hint is infeasible for this constraint.
746 // This is helpful to debug a wrong presolve that kill a feasible solution.
747 if (working_model->has_solution_hint() &&
748 solution_crush_.SolutionIsLoaded() &&
750 solution_crush_.GetVarValues())) {
751 LOG(FATAL) << "Hint infeasible for constraint #" << c << " : "
752 << ct.ShortDebugString();
753 }
754#endif
755}
756
758 if (is_unsat_) return true; // We do not care in this case.
759 return constraint_to_vars_.size() == working_model->constraints_size();
760}
761
763 if (is_unsat_) return;
764 const int old_size = constraint_to_vars_.size();
765 const int new_size = working_model->constraints_size();
766 DCHECK_LE(old_size, new_size);
767 constraint_to_vars_.resize(new_size);
768 constraint_to_linear1_var_.resize(new_size, -1);
769 for (int c = old_size; c < new_size; ++c) {
770 AddVariableUsage(c);
771 }
772}
773
775 if (is_unsat_) return false; // We do not care in this case.
776 if (params_.keep_all_feasible_solutions_in_presolve()) return false;
777
778 // We can leave non-optimal stuff around if we reach the time limit.
779 if (time_limit_->LimitReached()) return false;
780
781 for (int var = 0; var < working_model->variables_size(); ++var) {
782 if (VariableIsNotUsedAnymore(var)) continue;
783 if (IsFixed(var)) continue;
784 const auto& constraints = VarToConstraints(var);
785 if (constraints.size() == 1 &&
786 constraints.contains(kAffineRelationConstraint) &&
787 GetAffineRelation(var).representative != var) {
788 return true;
789 }
790 }
791 return false;
792}
793
794// TODO(user): Also test var_to_constraints_ !!
796 if (is_unsat_) return true; // We do not care in this case.
797 if (var_to_constraints_.size() != working_model->variables_size()) {
798 LOG(INFO) << "Wrong var_to_constraints_ size!";
799 return false;
800 }
801 if (constraint_to_vars_.size() != working_model->constraints_size()) {
802 LOG(INFO) << "Wrong constraint_to_vars size!";
803 return false;
804 }
805 std::vector<int> linear1_count(var_to_constraints_.size(), 0);
806 for (int c = 0; c < constraint_to_vars_.size(); ++c) {
807 const ConstraintProto& ct = working_model->constraints(c);
808 if (constraint_to_vars_[c] != UsedVariables(ct)) {
809 LOG(INFO) << "Wrong variables usage for constraint: \n"
811 << " old_size: " << constraint_to_vars_[c].size();
812 return false;
813 }
814 if (ct.constraint_case() == ConstraintProto::kLinear &&
815 ct.linear().vars().size() == 1) {
816 linear1_count[PositiveRef(ct.linear().vars(0))]++;
817 if (constraint_to_linear1_var_[c] != PositiveRef(ct.linear().vars(0))) {
818 LOG(INFO) << "Wrong variables for linear1: \n"
820 << " saved_var: " << constraint_to_linear1_var_[c];
821 return false;
822 }
823 }
824 }
825 int num_in_objective = 0;
826 for (int v = 0; v < var_to_constraints_.size(); ++v) {
827 if (linear1_count[v] != var_to_num_linear1_[v]) {
828 LOG(INFO) << "Variable " << v << " has wrong linear1 count!"
829 << " stored: " << var_to_num_linear1_[v]
830 << " actual: " << linear1_count[v];
831 return false;
832 }
833 if (var_to_constraints_[v].contains(kObjectiveConstraint)) {
834 ++num_in_objective;
835 if (!objective_map_.contains(v)) {
836 LOG(INFO) << "Variable " << v
837 << " is marked as part of the objective but isn't.";
838 return false;
839 }
840 }
841 }
842 if (num_in_objective != objective_map_.size()) {
843 LOG(INFO) << "Not all variables are marked as part of the objective";
844 return false;
845 }
846
847 return true;
848}
849
850// If a Boolean variable (one with domain [0, 1]) appear in this affine
851// equivalence class, then we want its representative to be Boolean. Note that
852// this is always possible because a Boolean variable can never be equal to a
853// multiple of another if std::abs(coeff) is greater than 1 and if it is not
854// fixed to zero. This is important because it allows to simply use the same
855// representative for any referenced literals.
856//
857// Note(user): When both domain contains [0,1] and later the wrong variable
858// become usable as boolean, then we have a bug. Because of that, the code
859// for GetLiteralRepresentative() is not as simple as it should be.
860bool PresolveContext::AddRelation(int x, int y, int64_t c, int64_t o,
861 AffineRelation* repo) {
862 // When the coefficient is larger than one, then if later one variable becomes
863 // Boolean, it must be the representative.
864 if (std::abs(c) != 1) return repo->TryAdd(x, y, c, o);
865
866 CHECK(!VariableWasRemoved(x));
867 CHECK(!VariableWasRemoved(y));
868
869 // To avoid integer overflow, we always want to use the representative with
870 // the smallest domain magnitude. Otherwise we might express a variable in say
871 // [0, 3] as ([x, x + 3] - x) for an arbitrary large x, and substituting
872 // something like this in a linear expression could break our overflow
873 // precondition.
874 //
875 // Note that if either rep_x or rep_y can be used as a literal, then it will
876 // also be the variable with the smallest domain magnitude (1 or 0 if fixed).
877 const int rep_x = repo->Get(x).representative;
878 const int rep_y = repo->Get(y).representative;
879 const int64_t m_x = std::max(std::abs(MinOf(rep_x)), std::abs(MaxOf(rep_x)));
880 const int64_t m_y = std::max(std::abs(MinOf(rep_y)), std::abs(MaxOf(rep_y)));
881 bool allow_rep_x = m_x < m_y;
882 bool allow_rep_y = m_y < m_x;
883 if (m_x == m_y) {
884 // If both magnitude are the same, we prefer a positive domain.
885 // This is important so we don't use [-1, 0] as a representative for [0, 1].
886 allow_rep_x = MinOf(rep_x) >= MinOf(rep_y);
887 allow_rep_y = MinOf(rep_y) >= MinOf(rep_x);
888 }
889 if (allow_rep_x && allow_rep_y) {
890 // If both representative are okay, we force the choice to the variable
891 // with lower index. This is needed because we have two "equivalence"
892 // relations, and we want the same representative in both.
893 if (rep_x < rep_y) {
894 allow_rep_y = false;
895 } else {
896 allow_rep_x = false;
897 }
898 }
899 return repo->TryAdd(x, y, c, o, allow_rep_x, allow_rep_y);
900}
901
903 DCHECK(RefIsPositive(var));
905 if (r.representative == var) return true;
907}
908
909bool PresolveContext::PropagateAffineRelation(int var, int rep, int64_t coeff,
910 int64_t offset) {
911 DCHECK(RefIsPositive(var));
912 DCHECK(RefIsPositive(rep));
913 DCHECK(!DomainIsEmpty(var));
914 DCHECK(!DomainIsEmpty(rep));
915
916 // Propagate domains both ways.
917 // var = coeff * rep + offset
918 if (!IntersectDomainWith(rep, DomainOf(var)
919 .AdditionWith(Domain(-offset))
920 .InverseMultiplicationBy(coeff))) {
921 return false;
922 }
924 var,
925 DomainOf(rep).MultiplicationBy(coeff).AdditionWith(Domain(offset)))) {
926 return false;
927 }
928
929 return true;
930}
931
933 for (auto& ref_map : var_to_constraints_) {
934 ref_map.erase(kAffineRelationConstraint);
935 }
936}
937
939 if (!VariableIsUnique(var)) {
940 return;
941 }
943 if (var == r.representative) {
944 return;
945 }
946 DCHECK(VarToConstraints(var).contains(kAffineRelationConstraint));
948 // Add relation with current representative to the mapping model.
949 ConstraintProto* ct = NewMappingConstraint(__FILE__, __LINE__);
950 auto* arg = ct->mutable_linear();
951 arg->add_vars(var);
952 arg->add_coeffs(1);
953 arg->add_vars(r.representative);
954 arg->add_coeffs(-r.coeff);
955 arg->add_domain(r.offset);
956 arg->add_domain(r.offset);
958}
959
960// We only call that for a non representative variable that is only used in
961// the kAffineRelationConstraint. Such variable can be ignored and should never
962// be seen again in the presolve.
964 const int rep = GetAffineRelation(var).representative;
965
966 CHECK(RefIsPositive(var));
967 CHECK_NE(var, rep);
968 CHECK_EQ(var_to_constraints_[var].size(), 1);
969 CHECK(var_to_constraints_[var].contains(kAffineRelationConstraint));
970 CHECK(var_to_constraints_[rep].contains(kAffineRelationConstraint));
971
972 // We shouldn't reuse this variable again!
974
975 // We do not call EraseFromVarToConstraint() on purpose here since the
976 // variable is removed.
977 var_to_constraints_[var].erase(kAffineRelationConstraint);
978 affine_relations_.IgnoreFromClassSize(var);
979
980 // If the representative is left alone, we can remove it from the special
981 // affine relation constraint too.
982 if (affine_relations_.ClassSize(rep) == 1) {
983 EraseFromVarToConstraint(rep, kAffineRelationConstraint);
984 }
985
986 if (VLOG_IS_ON(2)) {
987 LOG(INFO) << "Removing affine relation: " << AffineRelationDebugString(var);
988 }
989}
990
992 const int var = GetAffineRelation(ref).representative;
993 const int64_t min = MinOf(var);
994 if (min == 0 || IsFixed(var)) return; // Nothing to do.
995
996 const int new_var = NewIntVar(DomainOf(var).AdditionWith(Domain(-min)));
997 CHECK(StoreAffineRelation(var, new_var, 1, min, /*debug_no_recursion=*/true));
998 UpdateRuleStats("variables: canonicalize domain");
1000}
1001
1002bool ScaleFloatingPointObjective(const SatParameters& params,
1003 SolverLogger* logger, CpModelProto* proto) {
1004 DCHECK(proto->has_floating_point_objective());
1005 DCHECK(!proto->has_objective());
1006 const auto& objective = proto->floating_point_objective();
1007 std::vector<std::pair<int, double>> terms;
1008 for (int i = 0; i < objective.vars_size(); ++i) {
1009 DCHECK(RefIsPositive(objective.vars(i)));
1010 terms.push_back({objective.vars(i), objective.coeffs(i)});
1011 }
1012 const double offset = objective.offset();
1013 const bool maximize = objective.maximize();
1014 proto->clear_floating_point_objective();
1015
1016 return ScaleAndSetObjective(params, terms, offset, maximize, proto, logger);
1017}
1018
1020 int64_t mod, int64_t rhs) {
1021 CHECK_NE(mod, 0);
1022 CHECK_NE(coeff, 0);
1023
1024 const int64_t gcd = std::gcd(coeff, mod);
1025 if (gcd != 1) {
1026 if (rhs % gcd != 0) {
1028 absl::StrCat("Infeasible ", coeff, " * X = ", rhs, " % ", mod));
1029 }
1030 coeff /= gcd;
1031 mod /= gcd;
1032 rhs /= gcd;
1033 }
1034
1035 // We just abort in this case as there is no point introducing a new variable.
1036 if (std::abs(mod) == 1) return true;
1037
1038 int var = ref;
1039 if (!RefIsPositive(var)) {
1040 var = NegatedRef(ref);
1041 coeff = -coeff;
1042 rhs = -rhs;
1043 }
1044
1045 // From var * coeff % mod = rhs
1046 // We have var = mod * X + offset.
1047 const int64_t offset = ProductWithModularInverse(coeff, mod, rhs);
1048
1049 // Lets create a new integer variable and add the affine relation.
1050 const Domain new_domain =
1052 if (new_domain.IsEmpty()) {
1054 "Empty domain in CanonicalizeAffineVariable()");
1055 }
1056 if (new_domain.IsFixed()) {
1057 UpdateRuleStats("variables: fixed value due to affine relation");
1058 return IntersectDomainWith(
1059 var, new_domain.ContinuousMultiplicationBy(mod).AdditionWith(
1060 Domain(offset)));
1061 }
1062
1063 // We make sure the new variable has a domain starting at zero to minimize
1064 // future overflow issues. If it end up Boolean, it is also nice to be able to
1065 // use it as such.
1066 //
1067 // A potential problem with this is that it messes up the natural variable
1068 // order chosen by the modeler. We try to correct that when mapping variables
1069 // at the end of the presolve.
1070 const int64_t min_value = new_domain.Min();
1071 const int new_var = NewIntVar(new_domain.AdditionWith(Domain(-min_value)));
1072 if (!working_model->variables(var).name().empty()) {
1073 working_model->mutable_variables(new_var)->set_name(
1074 working_model->variables(var).name());
1075 }
1076 CHECK(StoreAffineRelation(var, new_var, mod, offset + mod * min_value,
1077 /*debug_no_recursion=*/true));
1078 UpdateRuleStats("variables: canonicalize affine domain");
1080 return true;
1081}
1082
1083bool PresolveContext::StoreAffineRelation(int var_x, int var_y, int64_t coeff,
1084 int64_t offset,
1085 bool debug_no_recursion) {
1086 DCHECK(RefIsPositive(var_x));
1087 DCHECK(RefIsPositive(var_y));
1088 DCHECK_NE(coeff, 0);
1089 if (is_unsat_) return false;
1090
1091 // Sets var_y's value to the solution of
1092 // "var_x's value - coeff * var_y's value = offset".
1093 solution_crush_.SetVarToLinearConstraintSolution(1, {var_x, var_y},
1094 {1, -coeff}, offset);
1095
1096 // TODO(user): I am not 100% sure why, but sometimes the representative is
1097 // fixed but that is not propagated to var_x or var_y and this causes issues.
1098 if (!PropagateAffineRelation(var_x)) return false;
1099 if (!PropagateAffineRelation(var_y)) return false;
1100 if (!PropagateAffineRelation(var_x, var_y, coeff, offset)) return false;
1101
1102 if (IsFixed(var_x)) {
1103 const int64_t lhs = DomainOf(var_x).FixedValue() - offset;
1104 if (lhs % std::abs(coeff) != 0) {
1105 return NotifyThatModelIsUnsat();
1106 }
1107 UpdateRuleStats("affine: fixed");
1108 return IntersectDomainWith(var_y, Domain(lhs / coeff));
1109 }
1110
1111 if (IsFixed(var_y)) {
1112 const int64_t value_x = DomainOf(var_y).FixedValue() * coeff + offset;
1113 UpdateRuleStats("affine: fixed");
1114 return IntersectDomainWith(var_x, Domain(value_x));
1115 }
1116
1117 // If both are already in the same class, we need to make sure the relations
1118 // are compatible.
1121 if (rx.representative == ry.representative) {
1122 // x = rx.coeff * rep + rx.offset;
1123 // y = ry.coeff * rep + ry.offset;
1124 // And x == coeff * ry.coeff * rep + (coeff * ry.offset + offset).
1125 //
1126 // So we get the relation a * rep == b with a and b defined here:
1127 const int64_t a = coeff * ry.coeff - rx.coeff;
1128 const int64_t b = coeff * ry.offset + offset - rx.offset;
1129 if (a == 0) {
1130 if (b != 0) return NotifyThatModelIsUnsat();
1131 return true;
1132 }
1133 if (b % a != 0) {
1134 return NotifyThatModelIsUnsat();
1135 }
1136 UpdateRuleStats("affine: unique solution");
1137 const int64_t unique_value = -b / a;
1138 if (!IntersectDomainWith(rx.representative, Domain(unique_value))) {
1139 return false;
1140 }
1141 if (!IntersectDomainWith(var_x,
1142 Domain(unique_value * rx.coeff + rx.offset))) {
1143 return false;
1144 }
1145 if (!IntersectDomainWith(var_y,
1146 Domain(unique_value * ry.coeff + ry.offset))) {
1147 return false;
1148 }
1149 return true;
1150 }
1151
1152 // var_x = coeff * var_y + offset;
1153 // rx.coeff * rep_x + rx.offset =
1154 // coeff * (ry.coeff * rep_y + ry.offset) + offset
1155 //
1156 // We have a * rep_x + b * rep_y == o
1157 int64_t a = rx.coeff;
1158 int64_t b = -coeff * ry.coeff;
1159 int64_t o = coeff * ry.offset + offset - rx.offset;
1160 CHECK_NE(a, 0);
1161 CHECK_NE(b, 0);
1162 {
1163 const int64_t gcd = std::gcd(std::abs(a), std::abs(b));
1164 if (gcd != 1) {
1165 a /= gcd;
1166 b /= gcd;
1167 if (o % gcd != 0) return NotifyThatModelIsUnsat();
1168 o /= gcd;
1169 }
1170 }
1171
1172 // In this (rare) case, we need to canonicalize one of the variable that will
1173 // become the representative for both.
1174 if (std::abs(a) > 1 && std::abs(b) > 1) {
1175 UpdateRuleStats("affine: created common representative");
1176 if (!CanonicalizeAffineVariable(rx.representative, a, std::abs(b),
1177 offset)) {
1178 return false;
1179 }
1180
1181 // Re-add the relation now that a will resolve to a multiple of b.
1182 return StoreAffineRelation(var_x, var_y, coeff, offset,
1183 /*debug_no_recursion=*/true);
1184 }
1185
1186 // Canonicalize from (a * rep_x + b * rep_y = o) to (x = c * y + o).
1187 int x, y;
1188 int64_t c;
1189 bool negate = false;
1190 if (std::abs(a) == 1) {
1191 x = rx.representative;
1192 y = ry.representative;
1193 c = -b;
1194 negate = a < 0;
1195 } else {
1196 CHECK_EQ(std::abs(b), 1);
1197 x = ry.representative;
1198 y = rx.representative;
1199 c = -a;
1200 negate = b < 0;
1201 }
1202 if (negate) {
1203 c = -c;
1204 o = -o;
1205 }
1206 CHECK(RefIsPositive(x));
1207 CHECK(RefIsPositive(y));
1208
1209 // Lets propagate domains first.
1211 y, DomainOf(x).AdditionWith(Domain(-o)).InverseMultiplicationBy(c))) {
1212 return false;
1213 }
1215 x,
1216 DomainOf(y).ContinuousMultiplicationBy(c).AdditionWith(Domain(o)))) {
1217 return false;
1218 }
1219
1220 // To avoid corner cases where replacing x by y in a linear expression
1221 // can cause overflow, we might want to canonicalize y first to avoid
1222 // cases like x = c * [large_value, ...] - large_value.
1223 //
1224 // TODO(user): we can do better for overflow by not always choosing the
1225 // min at zero, do the best things if it becomes needed.
1226 if (std::abs(o) > std::max(std::abs(MinOf(x)), std::abs(MaxOf(x)))) {
1227 // Both these function recursively call StoreAffineRelation() but shouldn't
1228 // be able to cascade (CHECKED).
1229 CHECK(!debug_no_recursion);
1231 return StoreAffineRelation(x, y, c, o, /*debug_no_recursion=*/true);
1232 }
1233
1234 // TODO(user): can we force the rep and remove GetAffineRelation()?
1235 CHECK(AddRelation(x, y, c, o, &affine_relations_));
1236 UpdateRuleStats("affine: new relation");
1237
1238 // Lets propagate again the new relation. We might as well do it as early
1239 // as possible and not all call site do it.
1240 //
1241 // TODO(user): I am not sure this is needed given the propagation above.
1242 if (!PropagateAffineRelation(var_x)) return false;
1243 if (!PropagateAffineRelation(var_y)) return false;
1244
1245 // These maps should only contains representative, so only need to remap
1246 // either x or y.
1247 const int rep = GetAffineRelation(x).representative;
1248
1249 // The domain didn't change, but this notification allows to re-process any
1250 // constraint containing these variables. Note that we do not need to
1251 // retrigger a propagation of the constraint containing a variable whose
1252 // representative didn't change.
1253 if (x != rep) modified_domains.Set(x);
1254 if (y != rep) modified_domains.Set(y);
1255
1256 var_to_constraints_[x].insert(kAffineRelationConstraint);
1257 var_to_constraints_[y].insert(kAffineRelationConstraint);
1258 return true;
1259}
1260
1262 int ref_a, int ref_b) {
1263 if (is_unsat_) return false;
1264
1265 CHECK(!VariableWasRemoved(ref_a));
1266 CHECK(!VariableWasRemoved(ref_b));
1267 CHECK(!DomainOf(ref_a).IsEmpty());
1268 CHECK(!DomainOf(ref_b).IsEmpty());
1269 CHECK(CanBeUsedAsLiteral(ref_a));
1270 CHECK(CanBeUsedAsLiteral(ref_b));
1271
1272 if (ref_a == ref_b) return true;
1273 if (ref_a == NegatedRef(ref_b)) {
1274 is_unsat_ = true;
1275 return false;
1276 }
1277
1278 const int var_a = PositiveRef(ref_a);
1279 const int var_b = PositiveRef(ref_b);
1280 if (RefIsPositive(ref_a) == RefIsPositive(ref_b)) {
1281 // a = b
1282 return StoreAffineRelation(var_a, var_b, /*coeff=*/1, /*offset=*/0);
1283 }
1284 // a = 1 - b
1285 return StoreAffineRelation(var_a, var_b, /*coeff=*/-1, /*offset=*/1);
1286}
1287
1290
1291 CHECK(CanBeUsedAsLiteral(ref));
1293 // Note(user): This can happen is some corner cases where the affine
1294 // relation where added before the variable became usable as Boolean. When
1295 // this is the case, the domain will be of the form [x, x + 1] and should be
1296 // later remapped to a Boolean variable.
1297 return ref;
1298 }
1299
1300 // We made sure that the affine representative can always be used as a
1301 // literal. However, if some variable are fixed, we might not have only
1302 // (coeff=1 offset=0) or (coeff=-1 offset=1) and we might have something like
1303 // (coeff=8 offset=0) which is only valid for both variable at zero...
1304 //
1305 // What is sure is that depending on the value, only one mapping can be valid
1306 // because r.coeff can never be zero.
1307 const bool positive_possible = (r.offset == 0 || r.coeff + r.offset == 1);
1308 const bool negative_possible = (r.offset == 1 || r.coeff + r.offset == 0);
1309 DCHECK_NE(positive_possible, negative_possible);
1310 if (RefIsPositive(ref)) {
1311 return positive_possible ? r.representative : NegatedRef(r.representative);
1312 } else {
1313 return positive_possible ? NegatedRef(r.representative) : r.representative;
1314 }
1315}
1316
1317// This makes sure that the affine relation only uses one of the
1318// representative from the var_equiv_relations_.
1320 AffineRelation::Relation r = affine_relations_.Get(PositiveRef(ref));
1321 if (!RefIsPositive(ref)) {
1322 r.coeff *= -1;
1323 r.offset *= -1;
1324 }
1325 return r;
1326}
1327
1328std::string PresolveContext::RefDebugString(int ref) const {
1329 return absl::StrCat(RefIsPositive(ref) ? "X" : "-X", PositiveRef(ref),
1330 DomainOf(ref).ToString());
1331}
1332
1335 return absl::StrCat(RefDebugString(ref), " = ", r.coeff, " * ",
1336 RefDebugString(r.representative), " + ", r.offset);
1337}
1338
1340 domains_.clear();
1341 modified_domains.ClearAll();
1343 var_to_constraints_.clear();
1344 var_to_num_linear1_.clear();
1345 objective_map_.clear();
1346 DCHECK(!solution_crush_.SolutionIsLoaded());
1347}
1348
1349// Create the internal structure for any new variables in working_model.
1351 const int new_size = working_model->variables().size();
1352 DCHECK_GE(new_size, domains_.size());
1353 if (domains_.size() == new_size) return;
1354
1355 modified_domains.Resize(new_size);
1356 var_with_reduced_small_degree.Resize(new_size);
1357 var_to_constraints_.resize(new_size);
1358 var_to_num_linear1_.resize(new_size);
1359
1360 // We mark the domain as modified so we will look at these new variable during
1361 // our presolve loop.
1362 const int old_size = domains_.size();
1363 domains_.resize(new_size);
1364 for (int i = old_size; i < new_size; ++i) {
1365 modified_domains.Set(i);
1366 domains_[i] = ReadDomainFromProto(working_model->variables(i));
1367 if (domains_[i].IsEmpty()) {
1368 is_unsat_ = true;
1369 return;
1370 }
1371 }
1372
1373 // We resize the hint too even if not loaded.
1374 solution_crush_.Resize(new_size);
1375}
1376
1378 absl::flat_hash_map<int, int64_t> hint_values;
1379 if (working_model->has_solution_hint()) {
1380 const auto hint_proto = working_model->solution_hint();
1381 int num_changes = 0;
1382 for (int i = 0; i < hint_proto.vars().size(); ++i) {
1383 const int var = hint_proto.vars(i);
1384 if (!RefIsPositive(var)) break; // Abort. Shouldn't happen.
1385 const int64_t hint_value = hint_proto.values(i);
1386 const int64_t clamped_hint_value = DomainOf(var).ClosestValue(hint_value);
1387 if (clamped_hint_value != hint_value) {
1388 ++num_changes;
1389 }
1390 hint_values[var] = clamped_hint_value;
1391 }
1392 if (num_changes > 0) {
1393 UpdateRuleStats("hint: moved var hint within its domain.", num_changes);
1394 }
1395 for (int i = 0; i < working_model->variables().size(); ++i) {
1396 if (!hint_values.contains(i) && IsFixed(i)) {
1397 hint_values[i] = FixedValue(i);
1398 }
1399 }
1400 }
1401 solution_crush_.LoadSolution(working_model->variables().size(), hint_values);
1402}
1403
1405 CHECK(RefIsPositive(var));
1406 CHECK_EQ(DomainOf(var).Size(), 2);
1407 const int64_t var_min = MinOf(var);
1408 const int64_t var_max = MaxOf(var);
1409
1410 if (is_unsat_) return;
1411
1412 absl::flat_hash_map<int64_t, SavedLiteral>& var_map = encoding_[var];
1413
1414 // Find encoding for min if present.
1415 auto min_it = var_map.find(var_min);
1416 if (min_it != var_map.end()) {
1417 const int old_var = PositiveRef(min_it->second.Get(this));
1418 if (removed_variables_.contains(old_var)) {
1419 var_map.erase(min_it);
1420 min_it = var_map.end();
1421 }
1422 }
1423
1424 // Find encoding for max if present.
1425 auto max_it = var_map.find(var_max);
1426 if (max_it != var_map.end()) {
1427 const int old_var = PositiveRef(max_it->second.Get(this));
1428 if (removed_variables_.contains(old_var)) {
1429 var_map.erase(max_it);
1430 max_it = var_map.end();
1431 }
1432 }
1433
1434 // Insert missing encoding.
1435 int min_literal;
1436 int max_literal;
1437 if (min_it != var_map.end() && max_it != var_map.end()) {
1438 min_literal = min_it->second.Get(this);
1439 max_literal = max_it->second.Get(this);
1440 if (min_literal != NegatedRef(max_literal)) {
1441 UpdateRuleStats("variables with 2 values: merge encoding literals");
1442 if (!StoreBooleanEqualityRelation(min_literal, NegatedRef(max_literal))) {
1443 return;
1444 }
1445 }
1446 min_literal = GetLiteralRepresentative(min_literal);
1447 max_literal = GetLiteralRepresentative(max_literal);
1448 if (!IsFixed(min_literal)) CHECK_EQ(min_literal, NegatedRef(max_literal));
1449 } else if (min_it != var_map.end() && max_it == var_map.end()) {
1450 UpdateRuleStats("variables with 2 values: register other encoding");
1451 min_literal = min_it->second.Get(this);
1452 max_literal = NegatedRef(min_literal);
1453 var_map[var_max] = SavedLiteral(max_literal);
1454 } else if (min_it == var_map.end() && max_it != var_map.end()) {
1455 UpdateRuleStats("variables with 2 values: register other encoding");
1456 max_literal = max_it->second.Get(this);
1457 min_literal = NegatedRef(max_literal);
1458 var_map[var_min] = SavedLiteral(min_literal);
1459 } else {
1460 UpdateRuleStats("variables with 2 values: create encoding literal");
1461 max_literal = NewBoolVar("var with 2 values");
1462 solution_crush_.MaybeSetLiteralToValueEncoding(max_literal, var, var_max);
1463 min_literal = NegatedRef(max_literal);
1464 var_map[var_min] = SavedLiteral(min_literal);
1465 var_map[var_max] = SavedLiteral(max_literal);
1466 }
1467
1468 if (IsFixed(min_literal) || IsFixed(max_literal)) {
1469 CHECK(IsFixed(min_literal));
1470 CHECK(IsFixed(max_literal));
1471 UpdateRuleStats("variables with 2 values: fixed encoding");
1472 if (LiteralIsTrue(min_literal)) {
1473 return static_cast<void>(IntersectDomainWith(var, Domain(var_min)));
1474 } else {
1475 return static_cast<void>(IntersectDomainWith(var, Domain(var_max)));
1476 }
1477 }
1478
1479 // Add affine relation.
1480 if (GetAffineRelation(var).representative != PositiveRef(min_literal)) {
1481 UpdateRuleStats("variables with 2 values: new affine relation");
1482 if (RefIsPositive(max_literal)) {
1483 (void)StoreAffineRelation(var, PositiveRef(max_literal),
1484 var_max - var_min, var_min);
1485 } else {
1486 (void)StoreAffineRelation(var, PositiveRef(max_literal),
1487 var_min - var_max, var_max);
1488 }
1489 }
1490}
1491
1492bool PresolveContext::InsertVarValueEncodingInternal(int literal, int var,
1493 int64_t value,
1494 bool add_constraints) {
1495 DCHECK(RefIsPositive(var));
1496 DCHECK(!VariableWasRemoved(literal));
1497 DCHECK(!VariableWasRemoved(var));
1498 if (is_unsat_) return false;
1499 absl::flat_hash_map<int64_t, SavedLiteral>& var_map = encoding_[var];
1500
1501 // The code below is not 100% correct if this is not the case.
1502 if (!DomainOf(var).Contains(value)) {
1503 return SetLiteralToFalse(literal);
1504 }
1505 if (DomainOf(var).IsFixed()) {
1506 return SetLiteralToTrue(literal);
1507 }
1508 if (LiteralIsTrue(literal)) {
1509 return IntersectDomainWith(var, Domain(value));
1510 }
1511 if (LiteralIsFalse(literal)) {
1512 return IntersectDomainWith(var, Domain(value).Complement());
1513 }
1514
1515 // If an encoding already exist, make the two Boolean equals.
1516 const auto [it, inserted] =
1517 var_map.insert(std::make_pair(value, SavedLiteral(literal)));
1518 if (!inserted) {
1519 const int previous_literal = it->second.Get(this);
1520
1521 // Ticky and rare: I have only observed this on the LNS of
1522 // radiation_m18_12_05_sat.fzn. The value was encoded, but maybe we never
1523 // used the involved variables / constraints, so it was removed (with the
1524 // encoding constraints) from the model already! We have to be careful.
1525 if (VariableWasRemoved(previous_literal)) {
1526 it->second = SavedLiteral(literal);
1527 } else {
1528 if (literal != previous_literal) {
1530 "variables: merge equivalent var value encoding literals");
1531 if (!StoreBooleanEqualityRelation(literal, previous_literal)) {
1532 return false;
1533 }
1534 }
1535 }
1536 return true;
1537 }
1538
1539 if (DomainOf(var).Size() == 2) {
1540 if (!CanBeUsedAsLiteral(var)) {
1541 // TODO(user): There is a bug here if the var == value was not in the
1542 // domain, it will just be ignored.
1544 if (is_unsat_) return false;
1545
1546 if (IsFixed(var)) {
1547 if (FixedValue(var) == value) {
1548 return SetLiteralToTrue(literal);
1549 } else {
1550 return SetLiteralToFalse(literal);
1551 }
1552 }
1553
1554 // We should have a Boolean now.
1555 CanonicalizeEncoding(&var, &value);
1556 }
1557
1558 CHECK(CanBeUsedAsLiteral(var));
1559 if (value == 0) {
1560 if (!StoreBooleanEqualityRelation(literal, NegatedRef(var))) {
1561 return false;
1562 }
1563 } else {
1564 CHECK_EQ(value, 1);
1565 if (!StoreBooleanEqualityRelation(literal, var)) return false;
1566 }
1567 } else if (add_constraints) {
1568 UpdateRuleStats("variables: add encoding constraint");
1569 AddImplyInDomain(literal, var, Domain(value));
1570 AddImplyInDomain(NegatedRef(literal), var, Domain(value).Complement());
1571 }
1572
1573 // The canonicalization might have proven UNSAT.
1574 return !ModelIsUnsat();
1575}
1576
1577bool PresolveContext::InsertHalfVarValueEncoding(int literal, int var,
1578 int64_t value, bool imply_eq) {
1579 if (is_unsat_) return false;
1580 DCHECK(RefIsPositive(var));
1581
1582 // Creates the linking sets on demand.
1583 // Insert the enforcement literal in the half encoding map.
1584 auto& direct_set = imply_eq ? eq_half_encoding_ : neq_half_encoding_;
1585 auto insert_result = direct_set.insert({{literal, var}, value});
1586 if (!insert_result.second) {
1587 if (insert_result.first->second != value && imply_eq) {
1588 UpdateRuleStats("variables: detect half reified incompatible value");
1589 return SetLiteralToFalse(literal);
1590 }
1591 return false; // Already there.
1592 }
1593 if (imply_eq) {
1594 // We are adding b => x=v. Check if we already have ~b => x=u.
1595 auto negated_encoding = direct_set.find({NegatedRef(literal), var});
1596 if (negated_encoding != direct_set.end()) {
1597 if (negated_encoding->second == value) {
1599 "variables: both boolean and its negation imply same equality");
1600 if (!IntersectDomainWith(var, Domain(value))) {
1601 return false;
1602 }
1603 } else {
1604 const int64_t other_value = negated_encoding->second;
1605 // b => var == value
1606 // !b => var == other_value
1607 // var = (value - other_value) * b + other_value
1609 "variables: both boolean and its negation fix the same variable");
1610 if (RefIsPositive(literal)) {
1611 StoreAffineRelation(var, literal, value - other_value, other_value);
1612 } else {
1613 StoreAffineRelation(var, NegatedRef(literal), other_value - value,
1614 value);
1615 }
1616 }
1617 }
1618 }
1619 VLOG(2) << "Collect lit(" << literal << ") implies var(" << var
1620 << (imply_eq ? ") == " : ") != ") << value;
1621 UpdateRuleStats("variables: detect half reified value encoding");
1622
1623 // Note(user): We don't expect a lot of literals in these sets, so doing
1624 // a scan should be okay.
1625 auto& other_set = imply_eq ? neq_half_encoding_ : eq_half_encoding_;
1626 auto it = other_set.find({NegatedRef(literal), var});
1627 if (it != other_set.end() && it->second == value) {
1628 UpdateRuleStats("variables: detect fully reified value encoding");
1629 const int imply_eq_literal = imply_eq ? literal : NegatedRef(literal);
1630 if (!InsertVarValueEncodingInternal(imply_eq_literal, var, value,
1631 /*add_constraints=*/false)) {
1632 return false;
1633 }
1634 }
1635
1636 return true;
1637}
1638
1639bool PresolveContext::CanonicalizeEncoding(int* ref, int64_t* value) {
1640 const AffineRelation::Relation r = GetAffineRelation(*ref);
1641 if ((*value - r.offset) % r.coeff != 0) return false;
1642 *ref = r.representative;
1643 *value = (*value - r.offset) / r.coeff;
1644 return true;
1645}
1646
1648 int64_t value) {
1649 if (!CanonicalizeEncoding(&var, &value) || !DomainOf(var).Contains(value)) {
1650 return SetLiteralToFalse(literal);
1651 }
1652 literal = GetLiteralRepresentative(literal);
1653 if (!InsertVarValueEncodingInternal(literal, var, value,
1654 /*add_constraints=*/true)) {
1655 return false;
1656 }
1657
1658 eq_half_encoding_.insert({{literal, var}, value});
1659 neq_half_encoding_.insert({{NegatedRef(literal), var}, value});
1660
1661 solution_crush_.MaybeSetLiteralToValueEncoding(literal, var, value);
1662 return true;
1663}
1664
1666 int64_t value) {
1667 if (!CanonicalizeEncoding(&var, &value) || !DomainOf(var).Contains(value)) {
1668 // The literal cannot be true.
1669 return SetLiteralToFalse(literal);
1670 }
1671 literal = GetLiteralRepresentative(literal);
1672 return InsertHalfVarValueEncoding(literal, var, value, /*imply_eq=*/true);
1673}
1674
1676 int64_t value) {
1677 if (!CanonicalizeEncoding(&var, &value) || !DomainOf(var).Contains(value)) {
1678 // The constraint is trivial.
1679 return false;
1680 }
1681 literal = GetLiteralRepresentative(literal);
1682 return InsertHalfVarValueEncoding(literal, var, value, /*imply_eq=*/false);
1683}
1684
1685bool PresolveContext::HasVarValueEncoding(int ref, int64_t value,
1686 int* literal) {
1687 CHECK(!VariableWasRemoved(ref));
1688 if (!CanonicalizeEncoding(&ref, &value)) return false;
1689 DCHECK(RefIsPositive(ref));
1690
1691 if (!DomainOf(ref).Contains(value)) {
1692 if (literal != nullptr) *literal = GetFalseLiteral();
1693 return true;
1694 }
1695
1696 if (CanBeUsedAsLiteral(ref)) {
1697 if (literal != nullptr) {
1698 *literal = value == 1 ? ref : NegatedRef(ref);
1699 }
1700 return true;
1701 }
1702
1703 const auto first_it = encoding_.find(ref);
1704 if (first_it == encoding_.end()) return false;
1705 const auto it = first_it->second.find(value);
1706 if (it == first_it->second.end()) return false;
1707
1708 if (VariableWasRemoved(it->second.Get(this))) return false;
1709 if (literal != nullptr) {
1710 *literal = it->second.Get(this);
1711 }
1712 return true;
1713}
1714
1716 const int var = PositiveRef(ref);
1717 const int64_t size = domains_[var].Size();
1718 if (size <= 2) return true;
1719 const auto& it = encoding_.find(var);
1720 return it == encoding_.end() ? false : size <= it->second.size();
1721}
1722
1723bool PresolveContext::IsFullyEncoded(const LinearExpressionProto& expr) const {
1724 CHECK_LE(expr.vars_size(), 1);
1725 if (IsFixed(expr)) return true;
1726 return IsFullyEncoded(expr.vars(0));
1727}
1728
1730 CHECK(!VariableWasRemoved(ref));
1731 if (!CanonicalizeEncoding(&ref, &value)) return GetFalseLiteral();
1732
1733 // Positive after CanonicalizeEncoding().
1734 const int var = ref;
1735
1736 // Returns the false literal if the value is not in the domain.
1737 if (!domains_[var].Contains(value)) {
1738 return GetFalseLiteral();
1739 }
1740
1741 // Return the literal itself if this was called or canonicalized to a Boolean.
1742 if (CanBeUsedAsLiteral(ref)) {
1743 return value == 1 ? ref : NegatedRef(ref);
1744 }
1745
1746 // Returns the associated literal if already present.
1747 absl::flat_hash_map<int64_t, SavedLiteral>& var_map = encoding_[var];
1748 auto it = var_map.find(value);
1749 if (it != var_map.end()) {
1750 const int lit = it->second.Get(this);
1751 if (VariableWasRemoved(lit)) {
1752 // If the variable was already removed, for now we create a new one.
1753 // This should be rare hopefully.
1754 var_map.erase(value);
1755 } else {
1756 return lit;
1757 }
1758 }
1759
1760 // Special case for fixed domains.
1761 if (domains_[var].Size() == 1) {
1762 const int true_literal = GetTrueLiteral();
1763 var_map[value] = SavedLiteral(true_literal);
1764 return true_literal;
1765 }
1766
1767 // Special case for domains of size 2.
1768 const int64_t var_min = MinOf(var);
1769 const int64_t var_max = MaxOf(var);
1770 if (domains_[var].Size() == 2) {
1771 // Checks if the other value is already encoded.
1772 const int64_t other_value = value == var_min ? var_max : var_min;
1773 auto other_it = var_map.find(other_value);
1774 if (other_it != var_map.end()) {
1775 const int literal = NegatedRef(other_it->second.Get(this));
1776 if (VariableWasRemoved(literal)) {
1777 // If the variable was already removed, for now we create a new one.
1778 // This should be rare hopefully.
1779 var_map.erase(other_value);
1780 } else {
1781 // Update the encoding map. The domain could have been reduced to size
1782 // two after the creation of the first literal.
1783 var_map[value] = SavedLiteral(literal);
1784 return literal;
1785 }
1786 }
1787
1788 if (var_min == 0 && var_max == 1) {
1789 const int representative = GetLiteralRepresentative(var);
1790 var_map[1] = SavedLiteral(representative);
1791 var_map[0] = SavedLiteral(NegatedRef(representative));
1792 return value == 1 ? representative : NegatedRef(representative);
1793 } else {
1794 const int literal = NewBoolVar("integer encoding");
1795 if (!InsertVarValueEncoding(literal, var, var_max)) {
1796 return GetFalseLiteral();
1797 }
1798 const int representative = GetLiteralRepresentative(literal);
1799 return value == var_max ? representative : NegatedRef(representative);
1800 }
1801 }
1802
1803 const int literal = NewBoolVar("integer encoding");
1804 if (!InsertVarValueEncoding(literal, var, value)) {
1805 return GetFalseLiteral();
1806 }
1807 return GetLiteralRepresentative(literal);
1808}
1809
1811 const LinearExpressionProto& expr, int64_t value) {
1812 DCHECK_LE(expr.vars_size(), 1);
1813 if (IsFixed(expr)) {
1814 if (FixedValue(expr) == value) {
1815 return GetTrueLiteral();
1816 } else {
1817 return GetFalseLiteral();
1818 }
1819 }
1820
1821 if ((value - expr.offset()) % expr.coeffs(0) != 0) {
1822 return GetFalseLiteral();
1823 }
1824
1825 return GetOrCreateVarValueEncoding(expr.vars(0),
1826 (value - expr.offset()) / expr.coeffs(0));
1827}
1828
1830 const CpObjectiveProto& obj = working_model->objective();
1831
1832 // We do some small canonicalization here
1833 objective_proto_is_up_to_date_ = false;
1834
1835 objective_offset_ = obj.offset();
1836 objective_scaling_factor_ = obj.scaling_factor();
1837 if (objective_scaling_factor_ == 0.0) {
1838 objective_scaling_factor_ = 1.0;
1839 }
1840
1841 objective_integer_before_offset_ = obj.integer_before_offset();
1842 objective_integer_after_offset_ = obj.integer_after_offset();
1843 objective_integer_scaling_factor_ = obj.integer_scaling_factor();
1844 if (objective_integer_scaling_factor_ == 0) {
1845 objective_integer_scaling_factor_ = 1;
1846 }
1847
1848 if (!obj.domain().empty()) {
1849 // We might relax this in CanonicalizeObjective() when we will compute
1850 // the possible objective domain from the domains of the variables.
1851 objective_domain_is_constraining_ = true;
1852 objective_domain_ = ReadDomainFromProto(obj);
1853 } else {
1854 objective_domain_is_constraining_ = false;
1855 objective_domain_ = Domain::AllValues();
1856 }
1857
1858 // This is an upper bound of the higher magnitude that can be reach by
1859 // summing an objective partial sum. Because of the model validation, this
1860 // shouldn't overflow, and we make sure it stays this way.
1861 objective_overflow_detection_ = std::abs(objective_integer_before_offset_);
1862 int64_t fixed_terms = 0;
1863
1864 objective_map_.clear();
1865 for (int i = 0; i < obj.vars_size(); ++i) {
1866 int var = obj.vars(i);
1867 int64_t coeff = obj.coeffs(i);
1868
1869 // TODO(user): There should be no negative reference here !
1870 if (!RefIsPositive(var)) {
1871 var = NegatedRef(var);
1872 coeff = -coeff;
1873 }
1874
1875 // We remove fixed terms as we read the objective. This can help a lot on
1876 // LNS problems with a large proportions of fixed terms.
1877 if (IsFixed(var)) {
1878 fixed_terms += FixedValue(var) * coeff;
1879 continue;
1880 }
1881
1882 const int64_t var_max_magnitude =
1883 std::max(std::abs(MinOf(var)), std::abs(MaxOf(var)));
1884 objective_overflow_detection_ += var_max_magnitude * std::abs(coeff);
1885
1886 objective_map_[var] += RefIsPositive(var) ? coeff : -coeff;
1887 if (objective_map_[var] == 0) {
1889 } else {
1890 var_to_constraints_[var].insert(kObjectiveConstraint);
1891 }
1892 }
1893
1894 if (fixed_terms != 0) {
1895 AddToObjectiveOffset(fixed_terms);
1896 }
1897}
1898
1900 const auto it = objective_map_.find(var);
1901 if (it == objective_map_.end()) return true;
1902 const int64_t coeff = it->second;
1903
1904 // If a variable only appear in objective, we can fix it!
1905 // Note that we don't care if it was in affine relation, because if none
1906 // of the relations are left, then we can still fix it.
1907 if (params_.cp_model_presolve() &&
1908 !params_.keep_all_feasible_solutions_in_presolve() &&
1909 !objective_domain_is_constraining_ &&
1911 var_to_constraints_[var].size() == 1 &&
1912 var_to_constraints_[var].contains(kObjectiveConstraint)) {
1913 UpdateRuleStats("objective: variable not used elsewhere");
1914 if (coeff > 0) {
1915 if (!IntersectDomainWith(var, Domain(MinOf(var)))) {
1916 return false;
1917 }
1918 } else {
1919 if (!IntersectDomainWith(var, Domain(MaxOf(var)))) {
1920 return false;
1921 }
1922 }
1923 }
1924
1925 if (IsFixed(var)) {
1926 AddToObjectiveOffset(coeff * MinOf(var));
1928 return true;
1929 }
1930
1932 if (r.representative == var) return true;
1933
1935
1936 // After we removed the variable from the objective it might have become a
1937 // unused affine. Add it to the list of variables to check so we reprocess it.
1938 modified_domains.Set(var);
1939
1940 // Do the substitution.
1941 AddToObjectiveOffset(coeff * r.offset);
1942 const int64_t new_coeff = objective_map_[r.representative] += coeff * r.coeff;
1943
1944 // Process new term.
1945 if (new_coeff == 0) {
1947 } else {
1948 var_to_constraints_[r.representative].insert(kObjectiveConstraint);
1949 if (IsFixed(r.representative)) {
1952 }
1953 }
1954 return true;
1955}
1956
1957bool PresolveContext::CanonicalizeObjective(bool simplify_domain) {
1958 objective_proto_is_up_to_date_ = false;
1959
1960 // We replace each entry by its affine representative.
1961 // Note that the non-deterministic loop is fine, but because we iterate
1962 // one the map while modifying it, it is safer to do a copy rather than to
1963 // try to handle that in one pass.
1964 tmp_entries_.clear();
1965 for (const auto& entry : objective_map_) {
1966 tmp_entries_.push_back(entry);
1967 }
1968
1969 // TODO(user): This is a bit duplicated with the presolve linear code.
1970 // We also do not propagate back any domain restriction from the objective to
1971 // the variables if any.
1972 for (const auto& entry : tmp_entries_) {
1973 if (!CanonicalizeOneObjectiveVariable(entry.first)) {
1974 return NotifyThatModelIsUnsat("canonicalize objective one term");
1975 }
1976 }
1977
1978 Domain implied_domain(0);
1979 int64_t gcd(0);
1980
1981 // We need to sort the entries to be deterministic.
1982 tmp_entries_.clear();
1983 for (const auto& entry : objective_map_) {
1984 tmp_entries_.push_back(entry);
1985 }
1986 std::sort(tmp_entries_.begin(), tmp_entries_.end());
1987 for (const auto& entry : tmp_entries_) {
1988 const int var = entry.first;
1989 const int64_t coeff = entry.second;
1990 gcd = std::gcd(gcd, std::abs(coeff));
1991 implied_domain =
1992 implied_domain.AdditionWith(DomainOf(var).MultiplicationBy(coeff))
1994 }
1995
1996 // This is the new domain.
1997 // Note that the domain never include the offset.
1998 objective_domain_ = objective_domain_.IntersectionWith(implied_domain);
1999
2000 // Depending on the use case, we cannot do that.
2001 if (simplify_domain) {
2002 objective_domain_ =
2003 objective_domain_.SimplifyUsingImpliedDomain(implied_domain);
2004 }
2005
2006 // Maybe divide by GCD.
2007 if (gcd > 1) {
2008 for (auto& entry : objective_map_) {
2009 entry.second /= gcd;
2010 }
2011 objective_domain_ = objective_domain_.InverseMultiplicationBy(gcd);
2012 if (objective_domain_.IsEmpty()) {
2013 return NotifyThatModelIsUnsat("empty objective domain");
2014 }
2015
2016 objective_offset_ /= static_cast<double>(gcd);
2017 objective_scaling_factor_ *= static_cast<double>(gcd);
2018
2019 // We update the offset accordingly.
2020 absl::int128 offset = absl::int128(objective_integer_before_offset_) *
2021 absl::int128(objective_integer_scaling_factor_) +
2022 absl::int128(objective_integer_after_offset_);
2023
2024 if (objective_domain_.IsFixed()) {
2025 // To avoid overflow in (fixed_value * gcd + before_offset) * factor +
2026 // after_offset because the objective is constant (and should fit on an
2027 // int64_t), we can rewrite it as fixed_value + offset.
2028 objective_integer_scaling_factor_ = 1;
2029 offset +=
2030 absl::int128(gcd - 1) * absl::int128(objective_domain_.FixedValue());
2031 } else {
2032 objective_integer_scaling_factor_ *= gcd;
2033 }
2034
2035 objective_integer_before_offset_ = static_cast<int64_t>(
2036 offset / absl::int128(objective_integer_scaling_factor_));
2037 objective_integer_after_offset_ = static_cast<int64_t>(
2038 offset % absl::int128(objective_integer_scaling_factor_));
2039
2040 // It is important to update the implied_domain for the "is constraining"
2041 // test below.
2042 implied_domain = implied_domain.InverseMultiplicationBy(gcd);
2043 }
2044
2045 if (objective_domain_.IsEmpty()) {
2046 return NotifyThatModelIsUnsat("empty objective domain");
2047 }
2048
2049 // Detect if the objective domain do not limit the "optimal" objective value.
2050 // If this is true, then we can apply any reduction that reduce the objective
2051 // value without any issues.
2052 objective_domain_is_constraining_ =
2053 !implied_domain
2054 .IntersectionWith(Domain(std::numeric_limits<int64_t>::min(),
2055 objective_domain_.Max()))
2056 .IsIncludedIn(objective_domain_);
2057 return true;
2058}
2059
2061 CHECK_EQ(objective_map_.size(), 1);
2062 const int var = objective_map_.begin()->first;
2063 const int64_t coeff = objective_map_.begin()->second;
2064
2065 // Transfer all the info to the domain of var.
2066 if (!IntersectDomainWith(var,
2067 objective_domain_.InverseMultiplicationBy(coeff))) {
2068 return false;
2069 }
2070
2071 // Recompute a correct and non-constraining objective domain.
2072 objective_proto_is_up_to_date_ = false;
2073 objective_domain_ = DomainOf(var).ContinuousMultiplicationBy(coeff);
2074 objective_domain_is_constraining_ = false;
2075 return true;
2076}
2077
2079 objective_proto_is_up_to_date_ = false;
2080 const int var = PositiveRef(ref);
2081 objective_map_.erase(var);
2082 EraseFromVarToConstraint(var, kObjectiveConstraint);
2083}
2084
2085void PresolveContext::AddToObjective(int var, int64_t value) {
2086 CHECK(RefIsPositive(var));
2087 objective_proto_is_up_to_date_ = false;
2088 int64_t& map_ref = objective_map_[var];
2089 map_ref += value;
2090 if (map_ref == 0) {
2092 } else {
2093 var_to_constraints_[var].insert(kObjectiveConstraint);
2094 }
2095}
2096
2097void PresolveContext::AddLiteralToObjective(int ref, int64_t value) {
2098 objective_proto_is_up_to_date_ = false;
2099 const int var = PositiveRef(ref);
2100 int64_t& map_ref = objective_map_[var];
2101 if (RefIsPositive(ref)) {
2102 map_ref += value;
2103 } else {
2104 AddToObjectiveOffset(value);
2105 map_ref -= value;
2106 }
2107 if (map_ref == 0) {
2109 } else {
2110 var_to_constraints_[var].insert(kObjectiveConstraint);
2111 }
2112}
2113
2115 objective_proto_is_up_to_date_ = false;
2116 const int64_t temp = CapAdd(objective_integer_before_offset_, delta);
2117 if (temp == std::numeric_limits<int64_t>::min()) return false;
2118 if (temp == std::numeric_limits<int64_t>::max()) return false;
2119 objective_integer_before_offset_ = temp;
2120
2121 // Tricky: The objective domain is without the offset, so we need to shift it.
2122 objective_offset_ += static_cast<double>(delta);
2123 objective_domain_ = objective_domain_.AdditionWith(Domain(-delta));
2124 return true;
2125}
2126
2128 int var_in_equality, int64_t coeff_in_equality,
2129 const ConstraintProto& equality) {
2130 objective_proto_is_up_to_date_ = false;
2131 CHECK(equality.enforcement_literal().empty());
2132 CHECK(RefIsPositive(var_in_equality));
2133
2134 // We can only "easily" substitute if the objective coefficient is a multiple
2135 // of the one in the constraint.
2136 const int64_t coeff_in_objective = objective_map_.at(var_in_equality);
2137 CHECK_NE(coeff_in_equality, 0);
2138 CHECK_EQ(coeff_in_objective % coeff_in_equality, 0);
2139
2140 const int64_t multiplier = coeff_in_objective / coeff_in_equality;
2141
2142 // Abort if the new objective seems to violate our overflow preconditions.
2143 int64_t change = 0;
2144 for (int i = 0; i < equality.linear().vars().size(); ++i) {
2145 int var = equality.linear().vars(i);
2146 if (PositiveRef(var) == var_in_equality) continue;
2147 int64_t coeff = equality.linear().coeffs(i);
2148 change +=
2149 std::abs(coeff) * std::max(std::abs(MinOf(var)), std::abs(MaxOf(var)));
2150 }
2151 const int64_t new_value =
2152 CapAdd(CapProd(std::abs(multiplier), change),
2153 objective_overflow_detection_ -
2154 std::abs(coeff_in_equality) *
2155 std::max(std::abs(MinOf(var_in_equality)),
2156 std::abs(MaxOf(var_in_equality))));
2157 if (new_value == std::numeric_limits<int64_t>::max()) return false;
2158 objective_overflow_detection_ = new_value;
2159
2160 // Compute the objective offset change.
2161 Domain offset = ReadDomainFromProto(equality.linear());
2162 DCHECK_EQ(offset.Min(), offset.Max());
2163 bool exact = true;
2164 offset = offset.MultiplicationBy(multiplier, &exact);
2165 CHECK(exact);
2166 CHECK(!offset.IsEmpty());
2167
2168 // We also need to make sure the integer_offset will not overflow.
2169 if (!AddToObjectiveOffset(offset.Min())) return false;
2170
2171 // Perform the substitution.
2172 for (int i = 0; i < equality.linear().vars().size(); ++i) {
2173 int var = equality.linear().vars(i);
2174 int64_t coeff = equality.linear().coeffs(i);
2175 if (!RefIsPositive(var)) {
2176 var = NegatedRef(var);
2177 coeff = -coeff;
2178 }
2179 if (var == var_in_equality) continue;
2180
2181 int64_t& map_ref = objective_map_[var];
2182 map_ref -= coeff * multiplier;
2183
2184 if (map_ref == 0) {
2186 } else {
2187 var_to_constraints_[var].insert(kObjectiveConstraint);
2188 }
2189 }
2190
2191 RemoveVariableFromObjective(var_in_equality);
2192
2193 // Because we can assume that the constraint we used was constraining
2194 // (otherwise it would have been removed), the objective domain should be now
2195 // constraining.
2196 objective_domain_is_constraining_ = true;
2197
2198 if (objective_domain_.IsEmpty()) {
2199 return NotifyThatModelIsUnsat();
2200 }
2201 return true;
2202}
2203
2205 absl::Span<const int> exactly_one) {
2206 if (objective_map_.empty()) return false;
2207 if (exactly_one.empty()) return false;
2208
2209 int64_t min_coeff = std::numeric_limits<int64_t>::max();
2210 for (const int ref : exactly_one) {
2211 const auto it = objective_map_.find(PositiveRef(ref));
2212 if (it == objective_map_.end()) return false;
2213
2214 const int64_t coeff = it->second;
2215 if (RefIsPositive(ref)) {
2216 min_coeff = std::min(min_coeff, coeff);
2217 } else {
2218 // Objective = coeff * var = coeff * (1 - ref);
2219 min_coeff = std::min(min_coeff, -coeff);
2220 }
2221 }
2222
2223 return ShiftCostInExactlyOne(exactly_one, min_coeff);
2224}
2225
2226bool PresolveContext::ShiftCostInExactlyOne(absl::Span<const int> exactly_one,
2227 int64_t shift) {
2228 if (shift == 0) return true;
2229
2230 // We have to be careful because shifting cost like this might increase the
2231 // min/max possible activity of the sum.
2232 //
2233 // TODO(user): Be more precise with this objective_overflow_detection_ and
2234 // always keep it up to date on each offset / coeff change.
2235 int64_t sum = 0;
2236 int64_t new_sum = 0;
2237 for (const int ref : exactly_one) {
2238 const int var = PositiveRef(ref);
2239 const int64_t obj = ObjectiveCoeff(var);
2240 sum = CapAdd(sum, std::abs(obj));
2241
2242 const int64_t new_obj = RefIsPositive(ref) ? obj - shift : obj + shift;
2243 new_sum = CapAdd(new_sum, std::abs(new_obj));
2244 }
2245 if (AtMinOrMaxInt64(new_sum)) return false;
2246 if (new_sum > sum) {
2247 const int64_t new_value =
2248 CapAdd(objective_overflow_detection_, new_sum - sum);
2249 if (AtMinOrMaxInt64(new_value)) return false;
2250 objective_overflow_detection_ = new_value;
2251 }
2252
2253 int64_t offset = shift;
2254 objective_proto_is_up_to_date_ = false;
2255 for (const int ref : exactly_one) {
2256 const int var = PositiveRef(ref);
2257
2258 // The value will be zero if it wasn't present.
2259 int64_t& map_ref = objective_map_[var];
2260 if (map_ref == 0) {
2261 var_to_constraints_[var].insert(kObjectiveConstraint);
2262 }
2263 if (RefIsPositive(ref)) {
2264 map_ref -= shift;
2265 if (map_ref == 0) {
2267 }
2268 } else {
2269 // Term = coeff * (1 - X) = coeff - coeff * X;
2270 // So -coeff -> -coeff -shift
2271 // And Term = coeff + shift - shift - (coeff + shift) * X
2272 // = (coeff + shift) * (1 - X) - shift;
2273 map_ref += shift;
2274 if (map_ref == 0) {
2276 }
2277 offset -= shift;
2278 }
2279 }
2280
2281 // Note that the domain never include the offset, so we need to update it.
2282 if (offset != 0) AddToObjectiveOffset(offset);
2283
2284 // When we shift the cost using an exactly one, our objective implied bounds
2285 // might be more or less precise. If the objective domain is not constraining
2286 // (and thus just constraining the upper bound), we relax it to make sure its
2287 // stay "non constraining".
2288 //
2289 // TODO(user): This is a bit hacky, find a nicer way.
2290 if (!objective_domain_is_constraining_) {
2291 objective_domain_ =
2292 Domain(std::numeric_limits<int64_t>::min(), objective_domain_.Max());
2293 }
2294
2295 return true;
2296}
2297
2299 if (objective_proto_is_up_to_date_) return;
2300 objective_proto_is_up_to_date_ = true;
2301
2302 // We need to sort the entries to be deterministic.
2303 // Note that --cpu_profile shows it is slightly faster to only compare key.
2304 tmp_entries_.clear();
2305 tmp_entries_.reserve(objective_map_.size());
2306 for (const auto& entry : objective_map_) {
2307 tmp_entries_.push_back(entry);
2308 }
2309 std::sort(tmp_entries_.begin(), tmp_entries_.end(),
2310 [](const std::pair<int, int64_t>& a,
2311 const std::pair<int, int64_t>& b) { return a.first < b.first; });
2312
2313 CpObjectiveProto* mutable_obj = working_model->mutable_objective();
2314 mutable_obj->set_offset(objective_offset_);
2315 mutable_obj->set_scaling_factor(objective_scaling_factor_);
2316 mutable_obj->set_integer_before_offset(objective_integer_before_offset_);
2317 mutable_obj->set_integer_after_offset(objective_integer_after_offset_);
2318 if (objective_integer_scaling_factor_ == 1) {
2319 mutable_obj->set_integer_scaling_factor(0); // Default.
2320 } else {
2321 mutable_obj->set_integer_scaling_factor(objective_integer_scaling_factor_);
2322 }
2323 FillDomainInProto(objective_domain_, mutable_obj);
2324 mutable_obj->clear_vars();
2325 mutable_obj->clear_coeffs();
2326 for (const auto& entry : tmp_entries_) {
2327 mutable_obj->add_vars(entry.first);
2328 mutable_obj->add_coeffs(entry.second);
2329 }
2330}
2331
2333 for (int i = 0; i < working_model->variables_size(); ++i) {
2334 FillDomainInProto(DomainOf(i), working_model->mutable_variables(i));
2335 }
2336}
2337
2339 const LinearExpressionProto& time_i, const LinearExpressionProto& time_j,
2340 int active_i, int active_j) {
2341 CHECK(!LiteralIsFalse(active_i));
2342 CHECK(!LiteralIsFalse(active_j));
2343 DCHECK(ExpressionIsAffine(time_i));
2344 DCHECK(ExpressionIsAffine(time_j));
2345
2346 const std::tuple<int, int64_t, int, int64_t, int64_t, int, int> key =
2347 GetReifiedPrecedenceKey(time_i, time_j, active_i, active_j);
2348 const auto& it = reified_precedences_cache_.find(key);
2349 if (it != reified_precedences_cache_.end()) return it->second;
2350
2351 const int result = NewBoolVar("precedences");
2352 reified_precedences_cache_[key] = result;
2353
2354 solution_crush_.SetVarToReifiedPrecedenceLiteral(result, time_i, time_j,
2355 active_i, active_j);
2356
2357 if (!IsFixed(time_i) && !IsFixed(time_j)) {
2359 {time_i.vars(0), time_j.vars(0)},
2360 {-time_i.coeffs(0), time_j.coeffs(0)}));
2361 }
2362
2363 // result => (time_i <= time_j) && active_i && active_j.
2364 ConstraintProto* const lesseq = working_model->add_constraints();
2365 lesseq->add_enforcement_literal(result);
2366 if (!IsFixed(time_i)) {
2367 lesseq->mutable_linear()->add_vars(time_i.vars(0));
2368 lesseq->mutable_linear()->add_coeffs(-time_i.coeffs(0));
2369 }
2370 if (!IsFixed(time_j)) {
2371 lesseq->mutable_linear()->add_vars(time_j.vars(0));
2372 lesseq->mutable_linear()->add_coeffs(time_j.coeffs(0));
2373 }
2374
2375 const int64_t offset =
2376 (IsFixed(time_i) ? FixedValue(time_i) : time_i.offset()) -
2377 (IsFixed(time_j) ? FixedValue(time_j) : time_j.offset());
2378 lesseq->mutable_linear()->add_domain(offset);
2379 lesseq->mutable_linear()->add_domain(std::numeric_limits<int64_t>::max());
2381
2382 if (!LiteralIsTrue(active_i)) {
2383 AddImplication(result, active_i);
2384 }
2385 if (!LiteralIsTrue(active_j) && active_i != active_j) {
2386 AddImplication(result, active_j);
2387 }
2388
2389 // Not(result) && active_i && active_j => (time_i > time_j)
2390 {
2391 ConstraintProto* const greater = working_model->add_constraints();
2392 if (!IsFixed(time_i)) {
2393 greater->mutable_linear()->add_vars(time_i.vars(0));
2394 greater->mutable_linear()->add_coeffs(-time_i.coeffs(0));
2395 }
2396 if (!IsFixed(time_j)) {
2397 greater->mutable_linear()->add_vars(time_j.vars(0));
2398 greater->mutable_linear()->add_coeffs(time_j.coeffs(0));
2399 }
2400 greater->mutable_linear()->add_domain(std::numeric_limits<int64_t>::min());
2401 greater->mutable_linear()->add_domain(offset - 1);
2402
2403 greater->add_enforcement_literal(NegatedRef(result));
2404 if (!LiteralIsTrue(active_i)) {
2405 greater->add_enforcement_literal(active_i);
2406 }
2407 if (!LiteralIsTrue(active_j) && active_i != active_j) {
2408 greater->add_enforcement_literal(active_j);
2409 }
2411 }
2412
2413 // This is redundant but should improves performance.
2414 //
2415 // If GetOrCreateReifiedPrecedenceLiteral(time_j, time_i, active_j, active_i)
2416 // (the reverse precedence) has been called too, then we can link the two
2417 // precedence literals, and the two active literals together.
2418 const auto& rev_it = reified_precedences_cache_.find(
2419 GetReifiedPrecedenceKey(time_j, time_i, active_j, active_i));
2420 if (rev_it != reified_precedences_cache_.end()) {
2421 auto* const bool_or = working_model->add_constraints()->mutable_bool_or();
2422 bool_or->add_literals(result);
2423 bool_or->add_literals(rev_it->second);
2424 if (!LiteralIsTrue(active_i)) {
2425 bool_or->add_literals(NegatedRef(active_i));
2426 }
2427 if (!LiteralIsTrue(active_j)) {
2428 bool_or->add_literals(NegatedRef(active_j));
2429 }
2430 }
2431
2432 return result;
2433}
2434
2435std::tuple<int, int64_t, int, int64_t, int64_t, int, int>
2436PresolveContext::GetReifiedPrecedenceKey(const LinearExpressionProto& time_i,
2437 const LinearExpressionProto& time_j,
2438 int active_i, int active_j) {
2439 const int var_i =
2440 IsFixed(time_i) ? std::numeric_limits<int>::min() : time_i.vars(0);
2441 const int64_t coeff_i = IsFixed(time_i) ? 0 : time_i.coeffs(0);
2442 const int var_j =
2443 IsFixed(time_j) ? std::numeric_limits<int>::min() : time_j.vars(0);
2444 const int64_t coeff_j = IsFixed(time_j) ? 0 : time_j.coeffs(0);
2445 const int64_t offset =
2446 (IsFixed(time_i) ? FixedValue(time_i) : time_i.offset()) -
2447 (IsFixed(time_j) ? FixedValue(time_j) : time_j.offset());
2448 // In all formulas, active_i and active_j are symmetrical, we can sort the
2449 // active literals.
2450 if (active_j < active_i) std::swap(active_i, active_j);
2451 return std::make_tuple(var_i, coeff_i, var_j, coeff_j, offset, active_i,
2452 active_j);
2453}
2454
2456 reified_precedences_cache_.clear();
2457}
2458
2460 SOLVER_LOG(logger_, "");
2461 SOLVER_LOG(logger_, "Presolve summary:");
2462 SOLVER_LOG(logger_, " - ", NumAffineRelations(),
2463 " affine relations were detected.");
2464 absl::btree_map<std::string, int> sorted_rules(stats_by_rule_name_.begin(),
2465 stats_by_rule_name_.end());
2466 for (const auto& entry : sorted_rules) {
2467 if (entry.second == 1) {
2468 SOLVER_LOG(logger_, " - rule '", entry.first, "' was applied 1 time.");
2469 } else {
2470 SOLVER_LOG(logger_, " - rule '", entry.first, "' was applied ",
2471 FormatCounter(entry.second), " times.");
2472 }
2473 }
2474}
2475
2476// Load the constraints in a local model.
2477//
2478// TODO(user): The model we load does not contain affine relations! But
2479// ideally we should be able to remove all of them once we allow more complex
2480// constraints to contains linear expression.
2481//
2482// TODO(user): remove code duplication with cp_model_solver. Here we also do
2483// not run the heuristic to decide which variable to fully encode.
2484//
2485// TODO(user): Maybe do not load slow to propagate constraints? for instance
2486// we do not use any linear relaxation here.
2487bool LoadModelForProbing(PresolveContext* context, Model* local_model) {
2488 if (context->ModelIsUnsat()) return false;
2489
2490 // Update the domain in the current CpModelProto.
2491 context->WriteVariableDomainsToProto();
2492 const CpModelProto& model_proto = *(context->working_model);
2493 // Adapt some of the parameters during this probing phase.
2494 SatParameters local_params = context->params();
2495 local_params.set_use_implied_bounds(false);
2496 return LoadModelForPresolve(model_proto, std::move(local_params), context,
2497 local_model, "probing");
2498}
2499
2500bool LoadModelForPresolve(const CpModelProto& model_proto, SatParameters params,
2501 PresolveContext* context, Model* local_model,
2502 absl::string_view name_for_logging) {
2503 *local_model->GetOrCreate<SatParameters>() = std::move(params);
2504 local_model->GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(
2505 context->time_limit());
2506 local_model->Register<ModelRandomGenerator>(context->random());
2507 auto* encoder = local_model->GetOrCreate<IntegerEncoder>();
2509 auto* mapping = local_model->GetOrCreate<CpModelMapping>();
2510
2511 // Important: Because the model_proto do not contains affine relation or the
2512 // objective, we cannot call DetectOptionalVariables() ! This might wrongly
2513 // detect optionality and derive bad conclusion.
2514 LoadVariables(model_proto, /*view_all_booleans_as_integers=*/false,
2515 local_model);
2516 ExtractEncoding(model_proto, local_model);
2517 auto* sat_solver = local_model->GetOrCreate<SatSolver>();
2518 if (sat_solver->ModelIsUnsat()) {
2519 return context->NotifyThatModelIsUnsat(
2520 absl::StrCat("Initial loading for ", name_for_logging));
2521 }
2522 for (const ConstraintProto& ct : model_proto.constraints()) {
2523 if (mapping->ConstraintIsAlreadyLoaded(&ct)) continue;
2524 CHECK(LoadConstraint(ct, local_model));
2525 if (sat_solver->ModelIsUnsat()) {
2526 return context->NotifyThatModelIsUnsat(
2527 absl::StrCat("after loading constraint during ", name_for_logging,
2528 " ", ProtobufShortDebugString(ct)));
2529 }
2530 }
2531 encoder->AddAllImplicationsBetweenAssociatedLiterals();
2532 if (sat_solver->ModelIsUnsat()) return false;
2533 if (!sat_solver->Propagate()) {
2534 return context->NotifyThatModelIsUnsat(
2535 "during probing initial propagation");
2536 }
2537
2538 return true;
2539}
2540
2541template <typename ProtoWithVarsAndCoeffs, typename PresolveContextT>
2543 absl::Span<const int> enforcements, ProtoWithVarsAndCoeffs* proto,
2544 int64_t* offset, std::vector<std::pair<int, int64_t>>* tmp_terms,
2545 PresolveContextT* context) {
2546 // First regroup the terms on the same variables and sum the fixed ones.
2547 //
2548 // TODO(user): Add a quick pass to skip most of the work below if the
2549 // constraint is already in canonical form?
2550 tmp_terms->clear();
2551 int64_t sum_of_fixed_terms = 0;
2552 bool remapped = false;
2553 const int old_size = proto->vars().size();
2554 DCHECK_EQ(old_size, proto->coeffs().size());
2555 for (int i = 0; i < old_size; ++i) {
2556 // Remove fixed variable and take affine representative.
2557 //
2558 // Note that we need to do that before we test for equality with an
2559 // enforcement (they should already have been mapped).
2560 int new_var;
2561 int64_t new_coeff;
2562 {
2563 const int ref = proto->vars(i);
2564 const int var = PositiveRef(ref);
2565 const int64_t coeff =
2566 RefIsPositive(ref) ? proto->coeffs(i) : -proto->coeffs(i);
2567 if (coeff == 0) continue;
2568
2569 if (context->IsFixed(var)) {
2570 sum_of_fixed_terms += coeff * context->FixedValue(var);
2571 continue;
2572 }
2573
2574 const AffineRelation::Relation r = context->GetAffineRelation(var);
2575 if (r.representative != var) {
2576 remapped = true;
2577 sum_of_fixed_terms += coeff * r.offset;
2578 }
2579
2580 new_var = r.representative;
2581 new_coeff = coeff * r.coeff;
2582 }
2583
2584 // TODO(user): Avoid the quadratic loop for the corner case of many
2585 // enforcement literal (this should be pretty rare though).
2586 bool removed = false;
2587 for (const int enf : enforcements) {
2588 if (new_var == PositiveRef(enf)) {
2589 if (RefIsPositive(enf)) {
2590 // If the constraint is enforced, we can assume the variable is at 1.
2591 sum_of_fixed_terms += new_coeff;
2592 } else {
2593 // We can assume the variable is at zero.
2594 }
2595 removed = true;
2596 break;
2597 }
2598 }
2599 if (removed) {
2600 context->UpdateRuleStats("linear: enforcement literal in expression");
2601 continue;
2602 }
2603
2604 tmp_terms->push_back({new_var, new_coeff});
2605 }
2606 proto->clear_vars();
2607 proto->clear_coeffs();
2608 std::sort(tmp_terms->begin(), tmp_terms->end());
2609 int current_var = 0;
2610 int64_t current_coeff = 0;
2611 for (const auto& entry : *tmp_terms) {
2612 CHECK(RefIsPositive(entry.first));
2613 if (entry.first == current_var) {
2614 current_coeff += entry.second;
2615 } else {
2616 if (current_coeff != 0) {
2617 proto->add_vars(current_var);
2618 proto->add_coeffs(current_coeff);
2619 }
2620 current_var = entry.first;
2621 current_coeff = entry.second;
2622 }
2623 }
2624 if (current_coeff != 0) {
2625 proto->add_vars(current_var);
2626 proto->add_coeffs(current_coeff);
2627 }
2628 if (remapped) {
2629 context->UpdateRuleStats("linear: remapped using affine relations");
2630 }
2631 if (proto->vars().size() < old_size) {
2632 context->UpdateRuleStats("linear: fixed or dup variables");
2633 }
2634 *offset = sum_of_fixed_terms;
2635 return remapped || proto->vars().size() < old_size;
2636}
2637
2638namespace {
2639bool CanonicalizeLinearExpressionNoContext(absl::Span<const int> enforcements,
2640 LinearConstraintProto* proto) {
2641 struct DummyContext {
2642 bool IsFixed(int /*var*/) const { return false; }
2643 int64_t FixedValue(int /*var*/) const { return 0; }
2644 AffineRelation::Relation GetAffineRelation(int var) const {
2645 return {var, 1, 0};
2646 }
2647 void UpdateRuleStats(absl::string_view /*rule*/) const {}
2648 } dummy_context;
2649 int64_t offset = 0;
2650 std::vector<std::pair<int, int64_t>> tmp_terms;
2651 const bool result = CanonicalizeLinearExpressionInternal(
2652 enforcements, proto, &offset, &tmp_terms, &dummy_context);
2653 if (offset != 0) {
2654 FillDomainInProto(ReadDomainFromProto(*proto).AdditionWith(Domain(-offset)),
2655 proto);
2656 }
2657 return result;
2658}
2659} // namespace
2660
2662 int64_t offset = 0;
2663 const bool result = CanonicalizeLinearExpressionInternal(
2664 ct->enforcement_literal(), ct->mutable_linear(), &offset, &tmp_terms_,
2665 this);
2666 if (offset != 0) {
2668 ReadDomainFromProto(ct->linear()).AdditionWith(Domain(-offset)),
2669 ct->mutable_linear());
2670 }
2671 return result;
2672}
2673
2675 absl::Span<const int> enforcements, LinearExpressionProto* expr) {
2676 int64_t offset = 0;
2677 const bool result = CanonicalizeLinearExpressionInternal(
2678 enforcements, expr, &offset, &tmp_terms_, this);
2679 expr->set_offset(expr->offset() + offset);
2680 return result;
2681}
2682
2683ConstraintProto* PresolveContext::NewMappingConstraint(absl::string_view file,
2684 int line) {
2685 const int c = mapping_model->constraints().size();
2686 ConstraintProto* new_ct = mapping_model->add_constraints();
2687 if (absl::GetFlag(FLAGS_cp_model_debug_postsolve)) {
2688 new_ct->set_name(absl::StrCat("#", c, " ", file, ":", line));
2689 }
2690 return new_ct;
2691}
2692
2694 const ConstraintProto& base_ct, absl::string_view file, int line) {
2695 const int c = mapping_model->constraints().size();
2696 ConstraintProto* new_ct = mapping_model->add_constraints();
2697 *new_ct = base_ct;
2698 if (absl::GetFlag(FLAGS_cp_model_debug_postsolve)) {
2699 new_ct->set_name(absl::StrCat("#c", c, " ", file, ":", line));
2700 }
2701 return new_ct;
2702}
2703
2704void CreateValidModelWithSingleConstraint(const ConstraintProto& ct,
2705 const PresolveContext* context,
2706 std::vector<int>* variable_mapping,
2707 CpModelProto* mini_model) {
2708 mini_model->Clear();
2709
2710 *mini_model->add_constraints() = ct;
2711
2712 absl::flat_hash_map<int, int> inverse_interval_map;
2713 for (const int i : UsedIntervals(ct)) {
2714 auto [it, inserted] =
2715 inverse_interval_map.insert({i, mini_model->constraints_size()});
2716 if (inserted) {
2717 const ConstraintProto& itv_ct = context->working_model->constraints(i);
2718 *mini_model->add_constraints() = itv_ct;
2719
2720 // Now add end = start + size for the interval. This is not strictly
2721 // necessary but it makes the presolve more powerful.
2722 ConstraintProto* linear = mini_model->add_constraints();
2723 *linear->mutable_enforcement_literal() = itv_ct.enforcement_literal();
2724 LinearConstraintProto* mutable_linear = linear->mutable_linear();
2725 const IntervalConstraintProto& itv = itv_ct.interval();
2726
2727 mutable_linear->add_domain(0);
2728 mutable_linear->add_domain(0);
2729 AddLinearExpressionToLinearConstraint(itv.start(), 1, mutable_linear);
2730 AddLinearExpressionToLinearConstraint(itv.size(), 1, mutable_linear);
2731 AddLinearExpressionToLinearConstraint(itv.end(), -1, mutable_linear);
2732 CanonicalizeLinearExpressionNoContext(itv_ct.enforcement_literal(),
2733 mutable_linear);
2734 }
2735 }
2736
2737 absl::flat_hash_map<int, int> inverse_variable_map;
2738 for (const ConstraintProto& cur_ct : mini_model->constraints()) {
2739 for (const int v : UsedVariables(cur_ct)) {
2740 auto [it, inserted] =
2741 inverse_variable_map.insert({v, mini_model->variables_size()});
2742 if (inserted) {
2743 FillDomainInProto(context->DomainOf(v), mini_model->add_variables());
2744 }
2745 }
2746 }
2747
2748 variable_mapping->resize(inverse_variable_map.size());
2749 for (const auto& [k, v] : inverse_variable_map) {
2750 (*variable_mapping)[v] = k;
2751 }
2752 const auto mapping_function = [&inverse_variable_map](int* i) {
2753 const bool is_positive = RefIsPositive(*i);
2754 const int positive_ref = is_positive ? *i : NegatedRef(*i);
2755
2756 const auto it = inverse_variable_map.find(positive_ref);
2757 DCHECK(it != inverse_variable_map.end());
2758 *i = is_positive ? it->second : NegatedRef(it->second);
2759 };
2760 const auto interval_mapping_function = [&inverse_interval_map](int* i) {
2761 const auto it = inverse_interval_map.find(*i);
2762 DCHECK(it != inverse_interval_map.end());
2763 *i = it->second;
2764 };
2765 for (ConstraintProto& ct : *mini_model->mutable_constraints()) {
2766 ApplyToAllVariableIndices(mapping_function, &ct);
2767 ApplyToAllLiteralIndices(mapping_function, &ct);
2768 ApplyToAllIntervalIndices(interval_mapping_function, &ct);
2769 }
2770}
2771
2774 const absl::Span<const int64_t> hint = solution_crush_.GetVarValues();
2775 if (hint.size() != working_model->variables().size()) return false;
2776 return SolutionIsFeasible(*working_model, hint);
2777}
2778
2779} // namespace sat
2780} // namespace operations_research
bool TryAdd(int x, int y, int64_t coeff, int64_t offset)
Domain MultiplicationBy(int64_t coeff, bool *exact=nullptr) const
Domain IntersectionWith(const Domain &domain) const
Domain ContinuousMultiplicationBy(int64_t coeff) const
bool Contains(int64_t value) const
Domain AdditionWith(const Domain &domain) const
bool IsIncludedIn(const Domain &domain) const
Domain InverseMultiplicationBy(int64_t coeff) const
int64_t ClosestValue(int64_t wanted) const
void Register(T *non_owned_class)
Definition model.h:177
ABSL_MUST_USE_RESULT bool CanonicalizeObjective(bool simplify_domain=true)
bool ExpressionIsSingleVariable(const LinearExpressionProto &expr) const
Returns true iff the expr is of the form 1 * var + 0.
bool CanonicalizeAffineVariable(int ref, int64_t coeff, int64_t mod, int64_t rhs)
ABSL_MUST_USE_RESULT bool IntersectDomainWith(int ref, const Domain &domain, bool *domain_modified=nullptr)
bool ExpressionIsALiteral(const LinearExpressionProto &expr, int *literal=nullptr) const
Returns true iff the expr is a literal (x or not(x)).
bool StoreLiteralImpliesVarEqValue(int literal, int var, int64_t value)
void ClearStats()
Clears the "rules" statistics.
bool StoreAffineRelation(int var_x, int var_y, int64_t coeff, int64_t offset, bool debug_no_recursion=false)
SparseBitset< int > modified_domains
Each time a domain is modified this is set to true.
ABSL_MUST_USE_RESULT bool SetLiteralToTrue(int lit)
int NewIntVarWithDefinition(const Domain &domain, absl::Span< const std::pair< int, int64_t > > definition, bool append_constraint_to_mapping_model=false)
bool DomainIsEmpty(int ref) const
Helpers to query the current domain of a variable.
ABSL_MUST_USE_RESULT bool SubstituteVariableInObjective(int var_in_equality, int64_t coeff_in_equality, const ConstraintProto &equality)
bool IntervalIsConstant(int ct_ref) const
Helper to query the state of an interval.
int NewIntVar(const Domain &domain)
Helpers to adds new variables to the presolved model.
bool ShiftCostInExactlyOne(absl::Span< const int > exactly_one, int64_t shift)
ABSL_MUST_USE_RESULT bool SetLiteralToFalse(int lit)
Returns false if the 'lit' doesn't have the desired value in the domain.
ABSL_MUST_USE_RESULT bool NotifyThatModelIsUnsat(absl::string_view message="")
int GetOrCreateVarValueEncoding(int ref, int64_t value)
ABSL_MUST_USE_RESULT bool CanonicalizeOneObjectiveVariable(int var)
std::string IntervalDebugString(int ct_ref) const
bool ExpressionIsAffineBoolean(const LinearExpressionProto &expr) const
bool VariableIsOnlyUsedInLinear1AndOneExtraConstraint(int var) const
bool StoreBooleanEqualityRelation(int ref_a, int ref_b)
void AddImplyInDomain(int b, int x, const Domain &domain)
b => (x ∈ domain).
bool VariableIsOnlyUsedInEncodingAndMaybeInObjective(int var) const
std::tuple< int, int64_t, int, int64_t, int64_t, int, int > GetReifiedPrecedenceKey(const LinearExpressionProto &time_i, const LinearExpressionProto &time_j, int active_i, int active_j)
int NewBoolVar(absl::string_view source)
void LogInfo()
Logs stats to the logger.
void ClearPrecedenceCache()
Clear the precedence cache.
int NewBoolVarWithClause(absl::Span< const int > clause)
bool HasVarValueEncoding(int ref, int64_t value, int *literal=nullptr)
void RemoveVariableFromObjective(int ref)
Allows to manipulate the objective coefficients.
std::optional< int64_t > FixedValueOrNullopt(const LinearExpressionProto &expr) const
This is faster than testing IsFixed() + FixedValue().
Domain DomainSuperSetOf(const LinearExpressionProto &expr) const
Return a super-set of the domain of the linear expression.
void InitializeNewDomains()
Creates the internal structure for any new variables in working_model.
bool VariableIsNotUsedAnymore(int ref) const
Returns true if this ref no longer appears in the model.
bool VariableIsUnique(int ref) const
Returns true if this ref only appear in one constraint.
ConstraintProto * NewMappingConstraint(absl::string_view file, int line)
int GetLiteralRepresentative(int ref) const
Returns the representative of a literal.
int GetOrCreateAffineValueEncoding(const LinearExpressionProto &expr, int64_t value)
int LiteralForExpressionMax(const LinearExpressionProto &expr) const
bool CanonicalizeLinearExpression(absl::Span< const int > enforcements, LinearExpressionProto *expr)
void UpdateRuleStats(const std::string &name, int num_times=1)
AffineRelation::Relation GetAffineRelation(int ref) const
Returns the representative of ref under the affine relations.
void AddToObjective(int var, int64_t value)
const absl::flat_hash_set< int > & VarToConstraints(int var) const
std::string RefDebugString(int ref) const
To facilitate debugging.
void AddLiteralToObjective(int ref, int64_t value)
bool InsertVarValueEncoding(int literal, int var, int64_t value)
void UpdateNewConstraintsVariableUsage()
Calls UpdateConstraintVariableUsage() on all newly created constraints.
int NumAffineRelations() const
Used for statistics.
std::string AffineRelationDebugString(int ref) const
bool StoreLiteralImpliesVarNEqValue(int literal, int var, int64_t value)
bool ExploitExactlyOneInObjective(absl::Span< const int > exactly_one)
bool DomainContains(int ref, int64_t value) const
int NewBoolVarWithConjunction(absl::Span< const int > conjunction)
int GetOrCreateReifiedPrecedenceLiteral(const LinearExpressionProto &time_i, const LinearExpressionProto &time_j, int active_i, int active_j)
bool CanonicalizeLinearConstraint(ConstraintProto *ct)
int Get(PresolveContext *context) const
Definition file.cc:169
bool CanonicalizeLinearExpressionInternal(absl::Span< const int > enforcements, ProtoWithVarsAndCoeffs *proto, int64_t *offset, std::vector< std::pair< int, int64_t > > *tmp_terms, PresolveContextT *context)
bool LoadModelForProbing(PresolveContext *context, Model *local_model)
bool SolutionIsFeasible(const CpModelProto &model, absl::Span< const int64_t > variable_values, const CpModelProto *mapping_proto, const std::vector< int > *postsolve_mapping)
int64_t ProductWithModularInverse(int64_t coeff, int64_t mod, int64_t rhs)
Definition util.cc:182
bool ConstraintIsFeasible(const CpModelProto &model, const ConstraintProto &constraint, absl::Span< const int64_t > variable_values)
void ApplyToAllIntervalIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
void LoadVariables(const CpModelProto &model_proto, bool view_all_booleans_as_integers, Model *m)
bool ScaleFloatingPointObjective(const SatParameters &params, SolverLogger *logger, CpModelProto *proto)
bool ScaleAndSetObjective(const SatParameters &params, absl::Span< const std::pair< int, double > > objective, double objective_offset, bool maximize, CpModelProto *cp_model, SolverLogger *logger)
Definition lp_utils.cc:1354
bool ExpressionIsAffine(const LinearExpressionProto &expr)
Checks if the expression is affine or constant.
constexpr int kAffineRelationConstraint
std::vector< int > UsedVariables(const ConstraintProto &ct)
std::vector< int > UsedIntervals(const ConstraintProto &ct)
Returns the sorted list of interval used by a constraint.
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.
bool PossibleIntegerOverflow(const CpModelProto &model, absl::Span< const int > vars, absl::Span< const int64_t > coeffs, int64_t offset)
constexpr int kObjectiveConstraint
We use some special constraint index in our variable <-> constraint graph.
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Reads a Domain from the domain field of a proto.
void ApplyToAllLiteralIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
bool LoadConstraint(const ConstraintProto &ct, Model *m)
void AddLinearExpressionToLinearConstraint(const LinearExpressionProto &expr, int64_t coefficient, LinearConstraintProto *linear)
void CreateValidModelWithSingleConstraint(const ConstraintProto &ct, const PresolveContext *context, std::vector< int > *variable_mapping, CpModelProto *mini_model)
int NegatedRef(int ref)
Small utility functions to deal with negative variable/literal references.
void ApplyToAllVariableIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
bool LoadModelForPresolve(const CpModelProto &model_proto, SatParameters params, PresolveContext *context, Model *local_model, absl::string_view name_for_logging)
void ExtractEncoding(const CpModelProto &model_proto, Model *m)
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)
std::string ProtobufShortDebugString(const P &message)
Definition proto_utils.h:41
int64_t CapProd(int64_t x, int64_t y)
std::string ProtobufDebugString(const P &message)
Definition proto_utils.h:32
absl::string_view ToString(MPSolver::OptimizationProblemType optimization_problem_type)
ABSL_FLAG(bool, cp_model_debug_postsolve, false, "DEBUG ONLY. When set to true, the mapping_model.proto will contains " "file:line of the code that created that constraint. This is helpful " "for debugging postsolve")
Definition model.h:51
#define SOLVER_LOG(logger,...)
Definition logging.h:109