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