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