OR-Tools  9.3
presolve_context.cc
Go to the documentation of this file.
1// Copyright 2010-2021 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/meta/type_traits.h"
31#include "absl/strings/str_cat.h"
32#include "absl/types/span.h"
37#include "ortools/sat/cp_model.pb.h"
41#include "ortools/sat/integer.h"
43#include "ortools/sat/model.h"
44#include "ortools/sat/sat_parameters.pb.h"
46#include "ortools/sat/util.h"
48#include "ortools/util/bitset.h"
53
54namespace operations_research {
55namespace sat {
56
58 return context->GetLiteralRepresentative(ref_);
59}
60
62 return context->GetVariableRepresentative(ref_);
63}
64
65void PresolveContext::ClearStats() { stats_by_rule_name_.clear(); }
66
68 IntegerVariableProto* const var = working_model->add_variables();
69 FillDomainInProto(domain, var);
71 return working_model->variables_size() - 1;
72}
73
75
77 if (!constant_to_ref_.contains(cst)) {
78 constant_to_ref_[cst] = SavedVariable(working_model->variables_size());
79 IntegerVariableProto* const var_proto = working_model->add_variables();
80 var_proto->add_domain(cst);
81 var_proto->add_domain(cst);
83 }
84 return constant_to_ref_[cst].Get(this);
85}
86
87// a => b.
89 ConstraintProto* const ct = working_model->add_constraints();
90 ct->add_enforcement_literal(a);
91 ct->mutable_bool_and()->add_literals(b);
92}
93
94// b => x in [lb, ub].
95void PresolveContext::AddImplyInDomain(int b, int x, const Domain& domain) {
96 ConstraintProto* const imply = working_model->add_constraints();
97
98 // Doing it like this seems to use slightly less memory.
99 // TODO(user): Find the best way to create such small proto.
100 imply->mutable_enforcement_literal()->Resize(1, b);
101 LinearConstraintProto* mutable_linear = imply->mutable_linear();
102 mutable_linear->mutable_vars()->Resize(1, x);
103 mutable_linear->mutable_coeffs()->Resize(1, 1);
104 FillDomainInProto(domain, mutable_linear);
105}
106
108 return domains[PositiveRef(ref)].IsEmpty();
109}
110
111bool PresolveContext::IsFixed(int ref) const {
112 DCHECK_LT(PositiveRef(ref), domains.size());
113 DCHECK(!DomainIsEmpty(ref));
114 return domains[PositiveRef(ref)].IsFixed();
115}
116
118 const int var = PositiveRef(ref);
119 return domains[var].Min() >= 0 && domains[var].Max() <= 1;
120}
121
124 if (RefIsPositive(lit)) {
125 return domains[lit].Min() == 1;
126 } else {
127 return domains[PositiveRef(lit)].Max() == 0;
128 }
129}
130
133 if (RefIsPositive(lit)) {
134 return domains[lit].Max() == 0;
135 } else {
136 return domains[PositiveRef(lit)].Min() == 1;
137 }
138}
139
140int64_t PresolveContext::MinOf(int ref) const {
141 DCHECK(!DomainIsEmpty(ref));
142 return RefIsPositive(ref) ? domains[PositiveRef(ref)].Min()
143 : -domains[PositiveRef(ref)].Max();
144}
145
146int64_t PresolveContext::MaxOf(int ref) const {
147 DCHECK(!DomainIsEmpty(ref));
148 return RefIsPositive(ref) ? domains[PositiveRef(ref)].Max()
149 : -domains[PositiveRef(ref)].Min();
150}
151
152int64_t PresolveContext::FixedValue(int ref) const {
153 DCHECK(!DomainIsEmpty(ref));
154 CHECK(IsFixed(ref));
155 return RefIsPositive(ref) ? domains[PositiveRef(ref)].Min()
156 : -domains[PositiveRef(ref)].Min();
157}
158
159int64_t PresolveContext::MinOf(const LinearExpressionProto& expr) const {
160 int64_t result = expr.offset();
161 for (int i = 0; i < expr.vars_size(); ++i) {
162 const int64_t coeff = expr.coeffs(i);
163 if (coeff > 0) {
164 result += coeff * MinOf(expr.vars(i));
165 } else {
166 result += coeff * MaxOf(expr.vars(i));
167 }
168 }
169 return result;
170}
171
172int64_t PresolveContext::MaxOf(const LinearExpressionProto& expr) const {
173 int64_t result = expr.offset();
174 for (int i = 0; i < expr.vars_size(); ++i) {
175 const int64_t coeff = expr.coeffs(i);
176 if (coeff > 0) {
177 result += coeff * MaxOf(expr.vars(i));
178 } else {
179 result += coeff * MinOf(expr.vars(i));
180 }
181 }
182 return result;
183}
184
185bool PresolveContext::IsFixed(const LinearExpressionProto& expr) const {
186 for (int i = 0; i < expr.vars_size(); ++i) {
187 if (expr.coeffs(i) != 0 && !IsFixed(expr.vars(i))) return false;
188 }
189 return true;
190}
191
192int64_t PresolveContext::FixedValue(const LinearExpressionProto& expr) const {
193 int64_t result = expr.offset();
194 for (int i = 0; i < expr.vars_size(); ++i) {
195 if (expr.coeffs(i) == 0) continue;
196 result += expr.coeffs(i) * FixedValue(expr.vars(i));
197 }
198 return result;
199}
200
202 const LinearExpressionProto& expr) const {
203 Domain result(expr.offset());
204 for (int i = 0; i < expr.vars_size(); ++i) {
205 result = result.AdditionWith(
206 DomainOf(expr.vars(i)).MultiplicationBy(expr.coeffs(i)));
207 }
208 return result;
209}
210
212 const LinearExpressionProto& expr) const {
213 if (expr.vars().size() != 1) return false;
214 return CanBeUsedAsLiteral(expr.vars(0));
215}
216
218 const LinearExpressionProto& expr) const {
219 const int ref = expr.vars(0);
220 return RefIsPositive(ref) == (expr.coeffs(0) > 0) ? ref : NegatedRef(ref);
221}
222
224 const LinearExpressionProto& expr) const {
225 return expr.offset() == 0 && expr.vars_size() == 1 && expr.coeffs(0) == 1;
226}
227
228bool PresolveContext::ExpressionIsALiteral(const LinearExpressionProto& expr,
229 int* literal) const {
230 if (expr.vars_size() != 1) return false;
231 const int ref = expr.vars(0);
232 const int var = PositiveRef(ref);
233 if (MinOf(var) < 0 || MaxOf(var) > 1) return false;
234
235 if (expr.offset() == 0 && expr.coeffs(0) == 1 && RefIsPositive(ref)) {
236 if (literal != nullptr) *literal = ref;
237 return true;
238 }
239 if (expr.offset() == 1 && expr.coeffs(0) == -1 && RefIsPositive(ref)) {
240 if (literal != nullptr) *literal = NegatedRef(ref);
241 return true;
242 }
243 if (expr.offset() == 1 && expr.coeffs(0) == 1 && !RefIsPositive(ref)) {
244 if (literal != nullptr) *literal = ref;
245 return true;
246 }
247 return false;
248}
249
250// Note that we only support converted intervals.
252 const ConstraintProto& proto = working_model->constraints(ct_ref);
253 if (!proto.enforcement_literal().empty()) return false;
254 if (!IsFixed(proto.interval().start())) return false;
255 if (!IsFixed(proto.interval().size())) return false;
256 if (!IsFixed(proto.interval().end())) return false;
257 return true;
258}
259
260std::string PresolveContext::IntervalDebugString(int ct_ref) const {
261 if (IntervalIsConstant(ct_ref)) {
262 return absl::StrCat("interval_", ct_ref, "(", StartMin(ct_ref), "..",
263 EndMax(ct_ref), ")");
264 } else if (ConstraintIsOptional(ct_ref)) {
265 const int literal =
266 working_model->constraints(ct_ref).enforcement_literal(0);
267 if (SizeMin(ct_ref) == SizeMax(ct_ref)) {
268 return absl::StrCat("interval_", ct_ref, "(lit=", literal, ", ",
269 StartMin(ct_ref), " --(", SizeMin(ct_ref), ")--> ",
270 EndMax(ct_ref), ")");
271 } else {
272 return absl::StrCat("interval_", ct_ref, "(lit=", literal, ", ",
273 StartMin(ct_ref), " --(", SizeMin(ct_ref), "..",
274 SizeMax(ct_ref), ")--> ", EndMax(ct_ref), ")");
275 }
276 } else if (SizeMin(ct_ref) == SizeMax(ct_ref)) {
277 return absl::StrCat("interval_", ct_ref, "(", StartMin(ct_ref), " --(",
278 SizeMin(ct_ref), ")--> ", EndMax(ct_ref), ")");
279 } else {
280 return absl::StrCat("interval_", ct_ref, "(", StartMin(ct_ref), " --(",
281 SizeMin(ct_ref), "..", SizeMax(ct_ref), ")--> ",
282 EndMax(ct_ref), ")");
283 }
284}
285
286int64_t PresolveContext::StartMin(int ct_ref) const {
287 const IntervalConstraintProto& interval =
288 working_model->constraints(ct_ref).interval();
289 return MinOf(interval.start());
290}
291
292int64_t PresolveContext::StartMax(int ct_ref) const {
293 const IntervalConstraintProto& interval =
294 working_model->constraints(ct_ref).interval();
295 return MaxOf(interval.start());
296}
297
298int64_t PresolveContext::EndMin(int ct_ref) const {
299 const IntervalConstraintProto& interval =
300 working_model->constraints(ct_ref).interval();
301 return MinOf(interval.end());
302}
303
304int64_t PresolveContext::EndMax(int ct_ref) const {
305 const IntervalConstraintProto& interval =
306 working_model->constraints(ct_ref).interval();
307 return MaxOf(interval.end());
308}
309
310int64_t PresolveContext::SizeMin(int ct_ref) const {
311 const IntervalConstraintProto& interval =
312 working_model->constraints(ct_ref).interval();
313 return MinOf(interval.size());
314}
315
316int64_t PresolveContext::SizeMax(int ct_ref) const {
317 const IntervalConstraintProto& interval =
318 working_model->constraints(ct_ref).interval();
319 return MaxOf(interval.size());
320}
321
322// Important: To be sure a variable can be removed, we need it to not be a
323// representative of both affine and equivalence relation.
324bool PresolveContext::VariableIsNotRepresentativeOfEquivalenceClass(
325 int var) const {
327 if (affine_relations_.ClassSize(var) > 1 &&
328 affine_relations_.Get(var).representative == var) {
329 return false;
330 }
331 if (var_equiv_relations_.ClassSize(var) > 1 &&
332 var_equiv_relations_.Get(var).representative == var) {
333 return false;
334 }
335 return true;
336}
337
339 const int var = PositiveRef(ref);
340 return VariableIsNotRepresentativeOfEquivalenceClass(var) &&
342}
343
344// Tricky: If this variable is equivalent to another one (but not the
345// representative) and appear in just one constraint, then this constraint must
346// be the affine defining one. And in this case the code using this function
347// should do the proper stuff.
349 if (!ConstraintVariableGraphIsUpToDate()) return false;
350 const int var = PositiveRef(ref);
351 return var_to_constraints_[var].size() == 1 && VariableIsRemovable(var);
352}
353
355 if (!ConstraintVariableGraphIsUpToDate()) return false;
356 const int var = PositiveRef(ref);
357 return VariableIsNotRepresentativeOfEquivalenceClass(var) &&
358 var_to_constraints_[var].contains(kObjectiveConstraint) &&
359 var_to_constraints_[var].size() == 2;
360}
361
362// Tricky: Same remark as for VariableIsUniqueAndRemovable().
363//
364// Also if the objective domain is constraining, we can't have a preferred
365// direction, so we cannot easily remove such variable.
367 if (!ConstraintVariableGraphIsUpToDate()) return false;
368 const int var = PositiveRef(ref);
369 return VariableIsRemovable(var) && !objective_domain_is_constraining_ &&
371}
372
373// Here, even if the variable is equivalent to others, if its affine defining
374// constraints where removed, then it is not needed anymore.
376 if (!ConstraintVariableGraphIsUpToDate()) return false;
377 return var_to_constraints_[PositiveRef(ref)].empty();
378}
379
381 removed_variables_.insert(PositiveRef(ref));
382}
383
384// Note(user): I added an indirection and a function for this to be able to
385// display debug information when this return false. This should actually never
386// return false in the cases where it is used.
388 // It is okay to reuse removed fixed variable.
389 if (IsFixed(ref)) return false;
390 if (!removed_variables_.contains(PositiveRef(ref))) return false;
391 if (!var_to_constraints_[PositiveRef(ref)].empty()) {
392 SOLVER_LOG(logger_, "Variable ", PositiveRef(ref),
393 " was removed, yet it appears in some constraints!");
394 SOLVER_LOG(logger_, "affine relation: ",
396 for (const int c : var_to_constraints_[PositiveRef(ref)]) {
398 logger_, "constraint #", c, " : ",
399 c >= 0 ? working_model->constraints(c).ShortDebugString() : "");
400 }
401 }
402 return true;
403}
404
406 int ref) const {
407 if (!ConstraintVariableGraphIsUpToDate()) return false;
408 const int var = PositiveRef(ref);
409 return var_to_num_linear1_[var] == var_to_constraints_[var].size() ||
410 (var_to_constraints_[var].contains(kObjectiveConstraint) &&
411 var_to_num_linear1_[var] + 1 == var_to_constraints_[var].size());
412}
413
415 Domain result;
416 if (RefIsPositive(ref)) {
417 result = domains[ref];
418 } else {
419 result = domains[PositiveRef(ref)].Negation();
420 }
421 return result;
422}
423
424bool PresolveContext::DomainContains(int ref, int64_t value) const {
425 if (!RefIsPositive(ref)) {
426 return domains[PositiveRef(ref)].Contains(-value);
427 }
428 return domains[ref].Contains(value);
429}
430
431bool PresolveContext::DomainContains(const LinearExpressionProto& expr,
432 int64_t value) const {
433 CHECK_LE(expr.vars_size(), 1);
434 if (IsFixed(expr)) {
435 return FixedValue(expr) == value;
436 }
437 if ((value - expr.offset()) % expr.coeffs(0) != 0) return false;
438 return DomainContains(expr.vars(0), (value - expr.offset()) / expr.coeffs(0));
439}
440
441ABSL_MUST_USE_RESULT bool PresolveContext::IntersectDomainWith(
442 int ref, const Domain& domain, bool* domain_modified) {
443 DCHECK(!DomainIsEmpty(ref));
444 const int var = PositiveRef(ref);
445
446 if (RefIsPositive(ref)) {
447 if (domains[var].IsIncludedIn(domain)) {
448 return true;
449 }
450 domains[var] = domains[var].IntersectionWith(domain);
451 } else {
452 const Domain temp = domain.Negation();
453 if (domains[var].IsIncludedIn(temp)) {
454 return true;
455 }
456 domains[var] = domains[var].IntersectionWith(temp);
457 }
458
459 if (domain_modified != nullptr) {
460 *domain_modified = true;
461 }
463 if (domains[var].IsEmpty()) {
464 is_unsat_ = true;
465 return false;
466 }
467
468 // Propagate the domain of the representative right away.
469 // Note that the recursive call should only by one level deep.
471 if (r.representative == var) return true;
474 .AdditionWith(Domain(-r.offset))
476}
477
478ABSL_MUST_USE_RESULT bool PresolveContext::IntersectDomainWith(
479 const LinearExpressionProto& expr, const Domain& domain,
480 bool* domain_modified) {
481 if (expr.vars().empty()) {
482 if (domain.Contains(expr.offset())) {
483 return true;
484 } else {
485 is_unsat_ = true;
486 return false;
487 }
488 }
489 if (expr.vars().size() == 1) { // Affine
490 return IntersectDomainWith(expr.vars(0),
491 domain.AdditionWith(Domain(-expr.offset()))
492 .InverseMultiplicationBy(expr.coeffs(0)),
493 domain_modified);
494 }
495
496 // We don't do anything for longer expression for now.
497 return true;
498}
499
500ABSL_MUST_USE_RESULT bool PresolveContext::SetLiteralToFalse(int lit) {
501 const int var = PositiveRef(lit);
502 const int64_t value = RefIsPositive(lit) ? 0 : 1;
504}
505
506ABSL_MUST_USE_RESULT bool PresolveContext::SetLiteralToTrue(int lit) {
507 return SetLiteralToFalse(NegatedRef(lit));
508}
509
511 const ConstraintProto& ct = working_model->constraints(index);
512 if (ct.constraint_case() ==
513 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
514 return true;
515 }
516 for (const int literal : ct.enforcement_literal()) {
517 if (LiteralIsFalse(literal)) return true;
518 }
519 return false;
520}
521
523 const ConstraintProto& ct = working_model->constraints(ct_ref);
524 bool contains_one_free_literal = false;
525 for (const int literal : ct.enforcement_literal()) {
526 if (LiteralIsFalse(literal)) return false;
527 if (!LiteralIsTrue(literal)) contains_one_free_literal = true;
528 }
529 return contains_one_free_literal;
530}
531
532void PresolveContext::UpdateRuleStats(const std::string& name, int num_times) {
533 // We only count if we are going to display it.
534 if (logger_->LoggingIsEnabled()) {
535 VLOG(2) << num_presolve_operations << " : " << name;
536 stats_by_rule_name_[name] += num_times;
537 }
538 num_presolve_operations += num_times;
539}
540
541void PresolveContext::UpdateLinear1Usage(const ConstraintProto& ct, int c) {
542 const int old_var = constraint_to_linear1_var_[c];
543 if (old_var >= 0) {
544 var_to_num_linear1_[old_var]--;
545 }
546 if (ct.constraint_case() == ConstraintProto::ConstraintCase::kLinear &&
547 ct.linear().vars().size() == 1) {
548 const int var = PositiveRef(ct.linear().vars(0));
549 constraint_to_linear1_var_[c] = var;
550 var_to_num_linear1_[var]++;
551 }
552}
553
554void PresolveContext::AddVariableUsage(int c) {
555 const ConstraintProto& ct = working_model->constraints(c);
556 constraint_to_vars_[c] = UsedVariables(ct);
557 constraint_to_intervals_[c] = UsedIntervals(ct);
558 for (const int v : constraint_to_vars_[c]) {
560 var_to_constraints_[v].insert(c);
561 }
562 for (const int i : constraint_to_intervals_[c]) interval_usage_[i]++;
563 UpdateLinear1Usage(ct, c);
564}
565
566void PresolveContext::EraseFromVarToConstraint(int var, int c) {
567 var_to_constraints_[var].erase(c);
568 if (var_to_constraints_[var].size() <= 2) {
570 }
571}
572
574 if (is_unsat_) return;
575 DCHECK_EQ(constraint_to_vars_.size(), working_model->constraints_size());
576 const ConstraintProto& ct = working_model->constraints(c);
577
578 // We don't optimize the interval usage as this is not super frequent.
579 for (const int i : constraint_to_intervals_[c]) interval_usage_[i]--;
580 constraint_to_intervals_[c] = UsedIntervals(ct);
581 for (const int i : constraint_to_intervals_[c]) interval_usage_[i]++;
582
583 // For the variables, we avoid an erase() followed by an insert() for the
584 // variables that didn't change.
585 tmp_new_usage_ = UsedVariables(ct);
586 const std::vector<int>& old_usage = constraint_to_vars_[c];
587 const int old_size = old_usage.size();
588 int i = 0;
589 for (const int var : tmp_new_usage_) {
591 while (i < old_size && old_usage[i] < var) {
592 EraseFromVarToConstraint(old_usage[i], c);
593 ++i;
594 }
595 if (i < old_size && old_usage[i] == var) {
596 ++i;
597 } else {
598 var_to_constraints_[var].insert(c);
599 }
600 }
601 for (; i < old_size; ++i) {
602 EraseFromVarToConstraint(old_usage[i], c);
603 }
604 constraint_to_vars_[c] = tmp_new_usage_;
605
606 UpdateLinear1Usage(ct, c);
607}
608
610 return constraint_to_vars_.size() == working_model->constraints_size();
611}
612
614 if (is_unsat_) return;
615 const int old_size = constraint_to_vars_.size();
616 const int new_size = working_model->constraints_size();
617 CHECK_LE(old_size, new_size);
618 constraint_to_vars_.resize(new_size);
619 constraint_to_linear1_var_.resize(new_size, -1);
620 constraint_to_intervals_.resize(new_size);
621 interval_usage_.resize(new_size);
622 for (int c = old_size; c < new_size; ++c) {
623 AddVariableUsage(c);
624 }
625}
626
627// TODO(user): Also test var_to_constraints_ !!
629 if (is_unsat_) return true; // We do not care in this case.
630 if (constraint_to_vars_.size() != working_model->constraints_size()) {
631 LOG(INFO) << "Wrong constraint_to_vars size!";
632 return false;
633 }
634 for (int c = 0; c < constraint_to_vars_.size(); ++c) {
635 if (constraint_to_vars_[c] !=
636 UsedVariables(working_model->constraints(c))) {
637 LOG(INFO) << "Wrong variables usage for constraint: \n"
638 << ProtobufDebugString(working_model->constraints(c))
639 << "old_size: " << constraint_to_vars_[c].size();
640 return false;
641 }
642 }
643 int num_in_objective = 0;
644 for (int v = 0; v < var_to_constraints_.size(); ++v) {
645 if (var_to_constraints_[v].contains(kObjectiveConstraint)) {
646 ++num_in_objective;
647 if (!objective_map_.contains(v)) {
648 LOG(INFO) << "Variable " << v
649 << " is marked as part of the objective but isn't.";
650 return false;
651 }
652 }
653 }
654 if (num_in_objective != objective_map_.size()) {
655 LOG(INFO) << "Not all variables are marked as part of the objective";
656 return false;
657 }
658
659 return true;
660}
661
662// If a Boolean variable (one with domain [0, 1]) appear in this affine
663// equivalence class, then we want its representative to be Boolean. Note that
664// this is always possible because a Boolean variable can never be equal to a
665// multiple of another if std::abs(coeff) is greater than 1 and if it is not
666// fixed to zero. This is important because it allows to simply use the same
667// representative for any referenced literals.
668//
669// Note(user): When both domain contains [0,1] and later the wrong variable
670// become usable as boolean, then we have a bug. Because of that, the code
671// for GetLiteralRepresentative() is not as simple as it should be.
672bool PresolveContext::AddRelation(int x, int y, int64_t c, int64_t o,
673 AffineRelation* repo) {
674 // When the coefficient is larger than one, then if later one variable becomes
675 // Boolean, it must be the representative.
676 if (std::abs(c) != 1) return repo->TryAdd(x, y, c, o);
677
680
681 // To avoid integer overflow, we always want to use the representative with
682 // the smallest domain magnitude. Otherwise we might express a variable in say
683 // [0, 3] as ([x, x + 3] - x) for an arbitrary large x, and substituting
684 // something like this in a linear expression could break our overflow
685 // precondition.
686 //
687 // Note that if either rep_x or rep_y can be used as a literal, then it will
688 // also be the variable with the smallest domain magnitude (1 or 0 if fixed).
689 const int rep_x = repo->Get(x).representative;
690 const int rep_y = repo->Get(y).representative;
691 const int64_t m_x = std::max(std::abs(MinOf(rep_x)), std::abs(MaxOf(rep_x)));
692 const int64_t m_y = std::max(std::abs(MinOf(rep_y)), std::abs(MaxOf(rep_y)));
693 bool allow_rep_x = m_x < m_y;
694 bool allow_rep_y = m_y < m_x;
695 if (m_x == m_y) {
696 // If both magnitude are the same, we prefer a positive domain.
697 // This is important so we don't use [-1, 0] as a representative for [0, 1].
698 allow_rep_x = MinOf(rep_x) >= MinOf(rep_y);
699 allow_rep_y = MinOf(rep_y) >= MinOf(rep_x);
700 }
701 if (allow_rep_x && allow_rep_y) {
702 // If both representative are okay, we force the choice to the variable
703 // with lower index. This is needed because we have two "equivalence"
704 // relations, and we want the same representative in both.
705 if (rep_x < rep_y) {
706 allow_rep_y = false;
707 } else {
708 allow_rep_x = false;
709 }
710 }
711 return repo->TryAdd(x, y, c, o, allow_rep_x, allow_rep_y);
712}
713
714// Note that we just add the relation to the var_equiv_relations_, not to the
715// affine one. This is enough, and should prevent overflow in the affine
716// relation class: if we keep chaining variable fixed to zero, the coefficient
717// in the relation can overflow. For instance if x = 200 y and z = 200 t,
718// nothing prevent us if all end up being zero, to say y = z, which will result
719// in x = 200^2 t. If we make a few bad choices like this, then we can have an
720// overflow.
724 const int64_t min = MinOf(var);
725 if (constant_to_ref_.contains(min)) {
726 const int rep = constant_to_ref_[min].Get(this);
727 if (RefIsPositive(rep)) {
728 if (rep != var) {
729 AddRelation(var, rep, 1, 0, &var_equiv_relations_);
730 }
731 } else {
732 if (PositiveRef(rep) == var) {
733 CHECK_EQ(min, 0);
734 } else {
735 AddRelation(var, PositiveRef(rep), -1, 0, &var_equiv_relations_);
736 }
737 }
738 } else {
739 constant_to_ref_[min] = SavedVariable(var);
740 }
741}
742
744 const int var = PositiveRef(ref);
746 if (r.representative == var) return true;
747
748 // Propagate domains both ways.
749 // var = coeff * rep + offset
752 .AdditionWith(Domain(-r.offset))
754 return false;
755 }
758 .AdditionWith(Domain(r.offset)))) {
759 return false;
760 }
761
762 return true;
763}
764
766 for (auto& ref_map : var_to_constraints_) {
767 ref_map.erase(kAffineRelationConstraint);
768 }
769}
770
771// We only call that for a non representative variable that is only used in
772// the kAffineRelationConstraint. Such variable can be ignored and should never
773// be seen again in the presolve.
775 const int rep = GetAffineRelation(var).representative;
776
778 CHECK_NE(var, rep);
779 CHECK_EQ(var_to_constraints_[var].size(), 1);
780 CHECK(var_to_constraints_[var].contains(kAffineRelationConstraint));
781 CHECK(var_to_constraints_[rep].contains(kAffineRelationConstraint));
782
783 // We shouldn't reuse this variable again!
785
786 // We do not call EraseFromVarToConstraint() on purpose here since the
787 // variable is removed.
788 var_to_constraints_[var].erase(kAffineRelationConstraint);
789 affine_relations_.IgnoreFromClassSize(var);
790 var_equiv_relations_.IgnoreFromClassSize(var);
791
792 // If the representative is left alone, we can remove it from the special
793 // affine relation constraint too.
794 if (affine_relations_.ClassSize(rep) == 1 &&
795 var_equiv_relations_.ClassSize(rep) == 1) {
796 EraseFromVarToConstraint(rep, kAffineRelationConstraint);
797 }
798
799 if (VLOG_IS_ON(2)) {
800 LOG(INFO) << "Removing affine relation: " << AffineRelationDebugString(var);
801 }
802}
803
805 const int var = GetAffineRelation(ref).representative;
806 const int64_t min = MinOf(var);
807 if (min == 0 || IsFixed(var)) return; // Nothing to do.
808
809 const int new_var = NewIntVar(DomainOf(var).AdditionWith(Domain(-min)));
810 CHECK(StoreAffineRelation(var, new_var, 1, min, /*debug_no_recursion=*/true));
811 UpdateRuleStats("variables: canonicalize domain");
813}
814
816 DCHECK(working_model->has_floating_point_objective());
817 DCHECK(!working_model->has_objective());
818 const auto& objective = working_model->floating_point_objective();
819 std::vector<std::pair<int, double>> terms;
820 for (int i = 0; i < objective.vars_size(); ++i) {
821 DCHECK(RefIsPositive(objective.vars(i)));
822 terms.push_back({objective.vars(i), objective.coeffs(i)});
823 }
824 const double offset = objective.offset();
825 const bool maximize = objective.maximize();
826 working_model->clear_floating_point_objective();
827
828 // We need the domains up to date before scaling.
830 return ScaleAndSetObjective(params_, terms, offset, maximize, working_model,
831 logger_);
832}
833
835 int64_t mod, int64_t rhs) {
836 CHECK_NE(mod, 0);
837 CHECK_NE(coeff, 0);
838
839 const int64_t gcd = std::gcd(coeff, mod);
840 if (gcd != 1) {
841 if (rhs % gcd != 0) {
843 absl::StrCat("Infeasible ", coeff, " * X = ", rhs, " % ", mod));
844 }
845 coeff /= gcd;
846 mod /= gcd;
847 rhs /= gcd;
848 }
849
850 // We just abort in this case as there is no point introducing a new variable.
851 if (std::abs(mod) == 1) return true;
852
853 int var = ref;
854 if (!RefIsPositive(var)) {
855 var = NegatedRef(ref);
856 coeff = -coeff;
857 rhs = -rhs;
858 }
859
860 // From var * coeff % mod = rhs
861 // We have var = mod * X + offset.
862 const int64_t offset = ProductWithModularInverse(coeff, mod, rhs);
863
864 // Lets create a new integer variable and add the affine relation.
865 const Domain new_domain =
867 if (new_domain.IsEmpty()) {
869 "Empty domain in CanonicalizeAffineVariable()");
870 }
871 if (new_domain.IsFixed()) {
872 UpdateRuleStats("variables: fixed value due to affine relation");
873 return IntersectDomainWith(
875 Domain(offset)));
876 }
877
878 // We make sure the new variable has a domain starting at zero to minimize
879 // future overflow issues. If it end up Boolean, it is also nice to be able to
880 // use it as such.
881 //
882 // A potential problem with this is that it messes up the natural variable
883 // order chosen by the modeler. We try to correct that when mapping variables
884 // at the end of the presolve.
885 const int64_t min_value = new_domain.Min();
886 const int new_var = NewIntVar(new_domain.AdditionWith(Domain(-min_value)));
887 CHECK(StoreAffineRelation(var, new_var, mod, offset + mod * min_value,
888 /*debug_no_recursion=*/true));
889 UpdateRuleStats("variables: canonicalize affine domain");
891 return true;
892}
893
894bool PresolveContext::StoreAffineRelation(int ref_x, int ref_y, int64_t coeff,
895 int64_t offset,
896 bool debug_no_recursion) {
897 CHECK_NE(coeff, 0);
898 if (is_unsat_) return false;
899
900 // TODO(user): I am not 100% sure why, but sometimes the representative is
901 // fixed but that is not propagated to ref_x or ref_y and this causes issues.
902 if (!PropagateAffineRelation(ref_x)) return false;
903 if (!PropagateAffineRelation(ref_y)) return false;
904
905 if (IsFixed(ref_x)) {
906 const int64_t lhs = DomainOf(ref_x).FixedValue() - offset;
907 if (lhs % std::abs(coeff) != 0) {
908 return NotifyThatModelIsUnsat();
909 }
910 UpdateRuleStats("affine: fixed");
911 return IntersectDomainWith(ref_y, Domain(lhs / coeff));
912 }
913
914 if (IsFixed(ref_y)) {
915 const int64_t value_x = DomainOf(ref_y).FixedValue() * coeff + offset;
916 UpdateRuleStats("affine: fixed");
917 return IntersectDomainWith(ref_x, Domain(value_x));
918 }
919
920 // If both are already in the same class, we need to make sure the relations
921 // are compatible.
924 if (rx.representative == ry.representative) {
925 // x = rx.coeff * rep + rx.offset;
926 // y = ry.coeff * rep + ry.offset;
927 // And x == coeff * ry.coeff * rep + (coeff * ry.offset + offset).
928 //
929 // So we get the relation a * rep == b with a and b defined here:
930 const int64_t a = coeff * ry.coeff - rx.coeff;
931 const int64_t b = coeff * ry.offset + offset - rx.offset;
932 if (a == 0) {
933 if (b != 0) return NotifyThatModelIsUnsat();
934 return true;
935 }
936 if (b % a != 0) {
937 return NotifyThatModelIsUnsat();
938 }
939 UpdateRuleStats("affine: unique solution");
940 const int64_t unique_value = -b / a;
941 if (!IntersectDomainWith(rx.representative, Domain(unique_value))) {
942 return false;
943 }
944 if (!IntersectDomainWith(ref_x,
945 Domain(unique_value * rx.coeff + rx.offset))) {
946 return false;
947 }
948 if (!IntersectDomainWith(ref_y,
949 Domain(unique_value * ry.coeff + ry.offset))) {
950 return false;
951 }
952 return true;
953 }
954
955 // ref_x = coeff * ref_y + offset;
956 // rx.coeff * rep_x + rx.offset =
957 // coeff * (ry.coeff * rep_y + ry.offset) + offset
958 //
959 // We have a * rep_x + b * rep_y == o
960 int64_t a = rx.coeff;
961 int64_t b = coeff * ry.coeff;
962 int64_t o = coeff * ry.offset + offset - rx.offset;
963 CHECK_NE(a, 0);
964 CHECK_NE(b, 0);
965 {
966 const int64_t gcd = MathUtil::GCD64(std::abs(a), std::abs(b));
967 if (gcd != 1) {
968 a /= gcd;
969 b /= gcd;
970 if (o % gcd != 0) return NotifyThatModelIsUnsat();
971 o /= gcd;
972 }
973 }
974
975 // In this (rare) case, we need to canonicalize one of the variable that will
976 // become the representative for both.
977 if (std::abs(a) > 1 && std::abs(b) > 1) {
978 UpdateRuleStats("affine: created common representative");
979 if (!CanonicalizeAffineVariable(rx.representative, a, std::abs(b),
980 offset)) {
981 return false;
982 }
983
984 // Re-add the relation now that a will resolve to a multiple of b.
985 return StoreAffineRelation(ref_x, ref_y, coeff, offset,
986 /*debug_no_recursion=*/true);
987 }
988
989 // Canonicalize to x = c * y + o
990 int x, y;
991 int64_t c;
992 bool negate = false;
993 if (std::abs(a) == 1) {
994 x = rx.representative;
995 y = ry.representative;
996 c = b;
997 negate = a < 0;
998 } else {
999 CHECK_EQ(std::abs(b), 1);
1000 x = ry.representative;
1001 y = rx.representative;
1002 c = a;
1003 negate = b < 0;
1004 }
1005 if (negate) {
1006 c = -c;
1007 o = -o;
1008 }
1009 CHECK(RefIsPositive(x));
1010 CHECK(RefIsPositive(y));
1011
1012 // Lets propagate domains first.
1014 y, DomainOf(x).AdditionWith(Domain(-o)).InverseMultiplicationBy(c))) {
1015 return false;
1016 }
1018 x,
1019 DomainOf(y).ContinuousMultiplicationBy(c).AdditionWith(Domain(o)))) {
1020 return false;
1021 }
1022
1023 // To avoid corner cases where replacing x by y in a linear expression
1024 // can cause overflow, we might want to canonicalize y first to avoid
1025 // cases like x = c * [large_value, ...] - large_value.
1026 //
1027 // TODO(user): we can do better for overflow by not always choosing the
1028 // min at zero, do the best things if it becomes needed.
1029 if (std::abs(o) > std::max(std::abs(MinOf(x)), std::abs(MaxOf(x)))) {
1030 // Both these function recursively call StoreAffineRelation() but shouldn't
1031 // be able to cascade (CHECKED).
1032 CHECK(!debug_no_recursion);
1034 return StoreAffineRelation(x, y, c, o, /*debug_no_recursion=*/true);
1035 }
1036
1037 // TODO(user): can we force the rep and remove GetAffineRelation()?
1038 CHECK(AddRelation(x, y, c, o, &affine_relations_));
1039 if ((c == 1 || c == -1) && o == 0) {
1040 CHECK(AddRelation(x, y, c, o, &var_equiv_relations_));
1041 }
1042
1043 UpdateRuleStats("affine: new relation");
1044
1045 // Lets propagate again the new relation. We might as well do it as early
1046 // as possible and not all call site do it.
1047 //
1048 // TODO(user): I am not sure this is needed given the propagation above.
1049 if (!PropagateAffineRelation(ref_x)) return false;
1050 if (!PropagateAffineRelation(ref_y)) return false;
1051
1052 // These maps should only contains representative, so only need to remap
1053 // either x or y.
1054 const int rep = GetAffineRelation(x).representative;
1055
1056 // The domain didn't change, but this notification allows to re-process any
1057 // constraint containing these variables. Note that we do not need to
1058 // retrigger a propagation of the constraint containing a variable whose
1059 // representative didn't change.
1060 if (x != rep) modified_domains.Set(x);
1061 if (y != rep) modified_domains.Set(y);
1062
1063 var_to_constraints_[x].insert(kAffineRelationConstraint);
1064 var_to_constraints_[y].insert(kAffineRelationConstraint);
1065 return true;
1066}
1067
1069 if (is_unsat_) return false;
1070
1071 CHECK(!VariableWasRemoved(ref_a));
1072 CHECK(!VariableWasRemoved(ref_b));
1073 CHECK(!DomainOf(ref_a).IsEmpty());
1074 CHECK(!DomainOf(ref_b).IsEmpty());
1075 CHECK(CanBeUsedAsLiteral(ref_a));
1076 CHECK(CanBeUsedAsLiteral(ref_b));
1077
1078 if (ref_a == ref_b) return true;
1079 if (ref_a == NegatedRef(ref_b)) return IntersectDomainWith(ref_a, Domain(0));
1080
1081 const int var_a = PositiveRef(ref_a);
1082 const int var_b = PositiveRef(ref_b);
1083 if (RefIsPositive(ref_a) == RefIsPositive(ref_b)) {
1084 // a = b
1085 return StoreAffineRelation(var_a, var_b, /*coeff=*/1, /*offset=*/0);
1086 }
1087 // a = 1 - b
1088 return StoreAffineRelation(var_a, var_b, /*coeff=*/-1, /*offset=*/1);
1089}
1090
1091bool PresolveContext::StoreAbsRelation(int target_ref, int ref) {
1092 const auto insert_status = abs_relations_.insert(
1093 std::make_pair(target_ref, SavedVariable(PositiveRef(ref))));
1094 if (!insert_status.second) {
1095 // Tricky: overwrite if the old value refer to a now unused variable.
1096 const int candidate = insert_status.first->second.Get(this);
1097 if (removed_variables_.contains(candidate)) {
1098 insert_status.first->second = SavedVariable(PositiveRef(ref));
1099 return true;
1100 }
1101 return false;
1102 }
1103 return true;
1104}
1105
1106bool PresolveContext::GetAbsRelation(int target_ref, int* ref) {
1107 auto it = abs_relations_.find(target_ref);
1108 if (it == abs_relations_.end()) return false;
1109
1110 // Tricky: In some rare case the stored relation can refer to a deleted
1111 // variable, so we need to ignore it.
1112 //
1113 // TODO(user): Incorporate this as part of SavedVariable/SavedLiteral so we
1114 // make sure we never forget about this.
1115 const int candidate = PositiveRef(it->second.Get(this));
1116 if (removed_variables_.contains(candidate)) {
1117 abs_relations_.erase(it);
1118 return false;
1119 }
1120 CHECK(!VariableWasRemoved(candidate));
1121 *ref = candidate;
1122 return true;
1123}
1124
1127
1130 // Note(user): This can happen is some corner cases where the affine
1131 // relation where added before the variable became usable as Boolean. When
1132 // this is the case, the domain will be of the form [x, x + 1] and should be
1133 // later remapped to a Boolean variable.
1134 return ref;
1135 }
1136
1137 // We made sure that the affine representative can always be used as a
1138 // literal. However, if some variable are fixed, we might not have only
1139 // (coeff=1 offset=0) or (coeff=-1 offset=1) and we might have something like
1140 // (coeff=8 offset=0) which is only valid for both variable at zero...
1141 //
1142 // What is sure is that depending on the value, only one mapping can be valid
1143 // because r.coeff can never be zero.
1144 const bool positive_possible = (r.offset == 0 || r.coeff + r.offset == 1);
1145 const bool negative_possible = (r.offset == 1 || r.coeff + r.offset == 0);
1146 DCHECK_NE(positive_possible, negative_possible);
1147 if (RefIsPositive(ref)) {
1148 return positive_possible ? r.representative : NegatedRef(r.representative);
1149 } else {
1150 return positive_possible ? NegatedRef(r.representative) : r.representative;
1151 }
1152}
1153
1155 const AffineRelation::Relation r = var_equiv_relations_.Get(PositiveRef(ref));
1156 CHECK_EQ(std::abs(r.coeff), 1);
1157 CHECK_EQ(r.offset, 0);
1158 return RefIsPositive(ref) == (r.coeff == 1) ? r.representative
1160}
1161
1162// This makes sure that the affine relation only uses one of the
1163// representative from the var_equiv_relations_.
1165 AffineRelation::Relation r = affine_relations_.Get(PositiveRef(ref));
1166 AffineRelation::Relation o = var_equiv_relations_.Get(r.representative);
1168 if (o.coeff == -1) r.coeff = -r.coeff;
1169 if (!RefIsPositive(ref)) {
1170 r.coeff *= -1;
1171 r.offset *= -1;
1172 }
1173 return r;
1174}
1175
1176std::string PresolveContext::RefDebugString(int ref) const {
1177 return absl::StrCat(RefIsPositive(ref) ? "X" : "-X", PositiveRef(ref),
1178 DomainOf(ref).ToString());
1179}
1180
1183 return absl::StrCat(RefDebugString(ref), " = ", r.coeff, " * ",
1184 RefDebugString(r.representative), " + ", r.offset);
1185}
1186
1187// Create the internal structure for any new variables in working_model.
1189 for (int i = domains.size(); i < working_model->variables_size(); ++i) {
1190 domains.emplace_back(ReadDomainFromProto(working_model->variables(i)));
1191 if (domains.back().IsEmpty()) {
1192 is_unsat_ = true;
1193 return;
1194 }
1195 if (IsFixed(i)) ExploitFixedDomain(i);
1196 }
1197 modified_domains.Resize(domains.size());
1198 var_with_reduced_small_degree.Resize(domains.size());
1199 var_to_constraints_.resize(domains.size());
1200 var_to_num_linear1_.resize(domains.size());
1201 var_to_ub_only_constraints.resize(domains.size());
1202 var_to_lb_only_constraints.resize(domains.size());
1203}
1204
1207 CHECK_EQ(DomainOf(var).Size(), 2);
1208 const int64_t var_min = MinOf(var);
1209 const int64_t var_max = MaxOf(var);
1210
1211 if (is_unsat_) return;
1212
1213 absl::flat_hash_map<int64_t, SavedLiteral>& var_map = encoding_[var];
1214
1215 // Find encoding for min if present.
1216 auto min_it = var_map.find(var_min);
1217 if (min_it != var_map.end()) {
1218 const int old_var = PositiveRef(min_it->second.Get(this));
1219 if (removed_variables_.contains(old_var)) {
1220 var_map.erase(min_it);
1221 min_it = var_map.end();
1222 }
1223 }
1224
1225 // Find encoding for max if present.
1226 auto max_it = var_map.find(var_max);
1227 if (max_it != var_map.end()) {
1228 const int old_var = PositiveRef(max_it->second.Get(this));
1229 if (removed_variables_.contains(old_var)) {
1230 var_map.erase(max_it);
1231 max_it = var_map.end();
1232 }
1233 }
1234
1235 // Insert missing encoding.
1236 int min_literal;
1237 int max_literal;
1238 if (min_it != var_map.end() && max_it != var_map.end()) {
1239 min_literal = min_it->second.Get(this);
1240 max_literal = max_it->second.Get(this);
1241 if (min_literal != NegatedRef(max_literal)) {
1242 UpdateRuleStats("variables with 2 values: merge encoding literals");
1243 StoreBooleanEqualityRelation(min_literal, NegatedRef(max_literal));
1244 if (is_unsat_) return;
1245 }
1246 min_literal = GetLiteralRepresentative(min_literal);
1247 max_literal = GetLiteralRepresentative(max_literal);
1248 if (!IsFixed(min_literal)) CHECK_EQ(min_literal, NegatedRef(max_literal));
1249 } else if (min_it != var_map.end() && max_it == var_map.end()) {
1250 UpdateRuleStats("variables with 2 values: register other encoding");
1251 min_literal = min_it->second.Get(this);
1252 max_literal = NegatedRef(min_literal);
1253 var_map[var_max] = SavedLiteral(max_literal);
1254 } else if (min_it == var_map.end() && max_it != var_map.end()) {
1255 UpdateRuleStats("variables with 2 values: register other encoding");
1256 max_literal = max_it->second.Get(this);
1257 min_literal = NegatedRef(max_literal);
1258 var_map[var_min] = SavedLiteral(min_literal);
1259 } else {
1260 UpdateRuleStats("variables with 2 values: create encoding literal");
1261 max_literal = NewBoolVar();
1262 min_literal = NegatedRef(max_literal);
1263 var_map[var_min] = SavedLiteral(min_literal);
1264 var_map[var_max] = SavedLiteral(max_literal);
1265 }
1266
1267 if (IsFixed(min_literal) || IsFixed(max_literal)) {
1268 CHECK(IsFixed(min_literal));
1269 CHECK(IsFixed(max_literal));
1270 UpdateRuleStats("variables with 2 values: fixed encoding");
1271 if (LiteralIsTrue(min_literal)) {
1272 return static_cast<void>(IntersectDomainWith(var, Domain(var_min)));
1273 } else {
1274 return static_cast<void>(IntersectDomainWith(var, Domain(var_max)));
1275 }
1276 }
1277
1278 // Add affine relation.
1279 if (GetAffineRelation(var).representative != PositiveRef(min_literal)) {
1280 UpdateRuleStats("variables with 2 values: new affine relation");
1281 if (RefIsPositive(max_literal)) {
1282 (void)StoreAffineRelation(var, PositiveRef(max_literal),
1283 var_max - var_min, var_min);
1284 } else {
1285 (void)StoreAffineRelation(var, PositiveRef(max_literal),
1286 var_min - var_max, var_max);
1287 }
1288 }
1289}
1290
1291void PresolveContext::InsertVarValueEncodingInternal(int literal, int var,
1292 int64_t value,
1293 bool add_constraints) {
1297 absl::flat_hash_map<int64_t, SavedLiteral>& var_map = encoding_[var];
1298
1299 // The code below is not 100% correct if this is not the case.
1300 DCHECK(DomainOf(var).Contains(value));
1301
1302 // If an encoding already exist, make the two Boolean equals.
1303 const auto [it, inserted] =
1304 var_map.insert(std::make_pair(value, SavedLiteral(literal)));
1305 if (!inserted) {
1306 const int previous_literal = it->second.Get(this);
1307
1308 // Ticky and rare: I have only observed this on the LNS of
1309 // radiation_m18_12_05_sat.fzn. The value was encoded, but maybe we never
1310 // used the involved variables / constraints, so it was removed (with the
1311 // encoding constraints) from the model already! We have to be careful.
1312 if (VariableWasRemoved(previous_literal)) {
1313 it->second = SavedLiteral(literal);
1314 } else {
1315 if (literal != previous_literal) {
1317 "variables: merge equivalent var value encoding literals");
1318 StoreBooleanEqualityRelation(literal, previous_literal);
1319 }
1320 }
1321 return;
1322 }
1323
1324 if (DomainOf(var).Size() == 2) {
1325 // TODO(user): There is a bug here if the var == value was not in the
1326 // domain, it will just be ignored.
1328 } else {
1329 VLOG(2) << "Insert lit(" << literal << ") <=> var(" << var
1330 << ") == " << value;
1331 eq_half_encoding_[var][value].insert(literal);
1332 neq_half_encoding_[var][value].insert(NegatedRef(literal));
1333 if (add_constraints) {
1334 UpdateRuleStats("variables: add encoding constraint");
1335 AddImplyInDomain(literal, var, Domain(value));
1336 AddImplyInDomain(NegatedRef(literal), var, Domain(value).Complement());
1337 }
1338 }
1339}
1340
1341bool PresolveContext::InsertHalfVarValueEncoding(int literal, int var,
1342 int64_t value, bool imply_eq) {
1343 if (is_unsat_) return false;
1345
1346 // Creates the linking sets on demand.
1347 // Insert the enforcement literal in the half encoding map.
1348 auto& direct_set =
1349 imply_eq ? eq_half_encoding_[var][value] : neq_half_encoding_[var][value];
1350 if (!direct_set.insert(literal).second) return false; // Already there.
1351
1352 VLOG(2) << "Collect lit(" << literal << ") implies var(" << var
1353 << (imply_eq ? ") == " : ") != ") << value;
1354 UpdateRuleStats("variables: detect half reified value encoding");
1355
1356 // Note(user): We don't expect a lot of literals in these sets, so doing
1357 // a scan should be okay.
1358 auto& other_set =
1359 imply_eq ? neq_half_encoding_[var][value] : eq_half_encoding_[var][value];
1360 for (const int other : other_set) {
1361 if (GetLiteralRepresentative(other) != NegatedRef(literal)) continue;
1362
1363 UpdateRuleStats("variables: detect fully reified value encoding");
1364 const int imply_eq_literal = imply_eq ? literal : NegatedRef(literal);
1365 InsertVarValueEncodingInternal(imply_eq_literal, var, value,
1366 /*add_constraints=*/false);
1367 break;
1368 }
1369
1370 return true;
1371}
1372
1373bool PresolveContext::CanonicalizeEncoding(int* ref, int64_t* value) {
1374 const AffineRelation::Relation r = GetAffineRelation(*ref);
1375 if ((*value - r.offset) % r.coeff != 0) return false;
1376 *ref = r.representative;
1377 *value = (*value - r.offset) / r.coeff;
1378 return true;
1379}
1380
1382 int64_t value) {
1383 if (!CanonicalizeEncoding(&ref, &value)) {
1384 return SetLiteralToFalse(literal);
1385 }
1387 InsertVarValueEncodingInternal(literal, ref, value, /*add_constraints=*/true);
1388 return true;
1389}
1390
1392 int64_t value) {
1393 if (!CanonicalizeEncoding(&var, &value)) return false;
1395 return InsertHalfVarValueEncoding(literal, var, value, /*imply_eq=*/true);
1396}
1397
1399 int64_t value) {
1400 if (!CanonicalizeEncoding(&var, &value)) return false;
1402 return InsertHalfVarValueEncoding(literal, var, value, /*imply_eq=*/false);
1403}
1404
1406 int* literal) {
1408 if (!CanonicalizeEncoding(&ref, &value)) return false;
1409 const absl::flat_hash_map<int64_t, SavedLiteral>& var_map = encoding_[ref];
1410 const auto it = var_map.find(value);
1411 if (it != var_map.end()) {
1412 if (literal != nullptr) {
1413 *literal = it->second.Get(this);
1414 }
1415 return true;
1416 }
1417 return false;
1418}
1419
1421 const int var = PositiveRef(ref);
1422 const int64_t size = domains[var].Size();
1423 if (size <= 2) return true;
1424 const auto& it = encoding_.find(var);
1425 return it == encoding_.end() ? false : size <= it->second.size();
1426}
1427
1428bool PresolveContext::IsFullyEncoded(const LinearExpressionProto& expr) const {
1429 CHECK_LE(expr.vars_size(), 1);
1430 if (IsFixed(expr)) return true;
1431 return IsFullyEncoded(expr.vars(0));
1432}
1433
1436 if (!CanonicalizeEncoding(&ref, &value)) return GetOrCreateConstantVar(0);
1437
1438 // Positive after CanonicalizeEncoding().
1439 const int var = ref;
1440
1441 // Returns the false literal if the value is not in the domain.
1442 if (!domains[var].Contains(value)) {
1443 return GetOrCreateConstantVar(0);
1444 }
1445
1446 // Returns the associated literal if already present.
1447 absl::flat_hash_map<int64_t, SavedLiteral>& var_map = encoding_[var];
1448 auto it = var_map.find(value);
1449 if (it != var_map.end()) {
1450 const int lit = it->second.Get(this);
1451 if (VariableWasRemoved(lit)) {
1452 // If the variable was already removed, for now we create a new one.
1453 // This should be rare hopefully.
1454 var_map.erase(value);
1455 } else {
1456 return lit;
1457 }
1458 }
1459
1460 // Special case for fixed domains.
1461 if (domains[var].Size() == 1) {
1462 const int true_literal = GetOrCreateConstantVar(1);
1463 var_map[value] = SavedLiteral(true_literal);
1464 return true_literal;
1465 }
1466
1467 // Special case for domains of size 2.
1468 const int64_t var_min = MinOf(var);
1469 const int64_t var_max = MaxOf(var);
1470 if (domains[var].Size() == 2) {
1471 // Checks if the other value is already encoded.
1472 const int64_t other_value = value == var_min ? var_max : var_min;
1473 auto other_it = var_map.find(other_value);
1474 if (other_it != var_map.end()) {
1475 const int literal = NegatedRef(other_it->second.Get(this));
1477 // If the variable was already removed, for now we create a new one.
1478 // This should be rare hopefully.
1479 var_map.erase(other_value);
1480 } else {
1481 // Update the encoding map. The domain could have been reduced to size
1482 // two after the creation of the first literal.
1483 var_map[value] = SavedLiteral(literal);
1484 return literal;
1485 }
1486 }
1487
1488 if (var_min == 0 && var_max == 1) {
1490 var_map[1] = SavedLiteral(representative);
1491 var_map[0] = SavedLiteral(NegatedRef(representative));
1493 } else {
1494 const int literal = NewBoolVar();
1497 return value == var_max ? representative : NegatedRef(representative);
1498 }
1499 }
1500
1501 const int literal = NewBoolVar();
1504}
1505
1507 const LinearExpressionProto& expr, int64_t value) {
1508 DCHECK_LE(expr.vars_size(), 1);
1509 if (IsFixed(expr)) {
1510 if (FixedValue(expr) == value) {
1511 return GetOrCreateConstantVar(1);
1512 } else {
1513 return GetOrCreateConstantVar(0);
1514 }
1515 }
1516
1517 if ((value - expr.offset()) % expr.coeffs(0) != 0) {
1518 return GetOrCreateConstantVar(0);
1519 }
1520
1521 return GetOrCreateVarValueEncoding(expr.vars(0),
1522 (value - expr.offset()) / expr.coeffs(0));
1523}
1524
1526 const CpObjectiveProto& obj = working_model->objective();
1527
1528 objective_offset_ = obj.offset();
1529 objective_scaling_factor_ = obj.scaling_factor();
1530 if (objective_scaling_factor_ == 0.0) {
1531 objective_scaling_factor_ = 1.0;
1532 }
1533
1534 objective_integer_offset_ = obj.integer_offset();
1535 objective_integer_scaling_factor_ = obj.integer_scaling_factor();
1536 if (objective_integer_scaling_factor_ == 0) {
1537 objective_integer_scaling_factor_ = 1;
1538 }
1539
1540 if (!obj.domain().empty()) {
1541 // We might relax this in CanonicalizeObjective() when we will compute
1542 // the possible objective domain from the domains of the variables.
1543 objective_domain_is_constraining_ = true;
1544 objective_domain_ = ReadDomainFromProto(obj);
1545 } else {
1546 objective_domain_is_constraining_ = false;
1547 objective_domain_ = Domain::AllValues();
1548 }
1549
1550 // This is an upper bound of the higher magnitude that can be reach by
1551 // summing an objective partial sum. Because of the model validation, this
1552 // shouldn't overflow, and we make sure it stays this way.
1553 objective_overflow_detection_ = 0;
1554
1555 objective_map_.clear();
1556 for (int i = 0; i < obj.vars_size(); ++i) {
1557 const int ref = obj.vars(i);
1558 const int64_t var_max_magnitude =
1559 std::max(std::abs(MinOf(ref)), std::abs(MaxOf(ref)));
1560
1561 // Skipping var fixed to zero allow to avoid some overflow in situation
1562 // were we can deal with it.
1563 if (var_max_magnitude == 0) continue;
1564
1565 const int64_t coeff = obj.coeffs(i);
1566 objective_overflow_detection_ += var_max_magnitude * std::abs(coeff);
1567
1568 const int var = PositiveRef(ref);
1569 objective_map_[var] += RefIsPositive(ref) ? coeff : -coeff;
1570 if (objective_map_[var] == 0) {
1572 } else {
1573 var_to_constraints_[var].insert(kObjectiveConstraint);
1574 }
1575 }
1576}
1577
1579 const auto it = objective_map_.find(var);
1580 if (it == objective_map_.end()) return true;
1581 const int64_t coeff = it->second;
1582
1583 // If a variable only appear in objective, we can fix it!
1584 // Note that we don't care if it was in affine relation, because if none
1585 // of the relations are left, then we can still fix it.
1586 if (!keep_all_feasible_solutions && !objective_domain_is_constraining_ &&
1588 var_to_constraints_[var].size() == 1 &&
1589 var_to_constraints_[var].contains(kObjectiveConstraint)) {
1590 UpdateRuleStats("objective: variable not used elsewhere");
1591 if (coeff > 0) {
1593 return false;
1594 }
1595 } else {
1597 return false;
1598 }
1599 }
1600 }
1601
1602 if (IsFixed(var)) {
1605 return true;
1606 }
1607
1609 if (r.representative == var) return true;
1610
1611 objective_map_.erase(var);
1612 EraseFromVarToConstraint(var, kObjectiveConstraint);
1613
1614 // Do the substitution.
1616 const int64_t new_coeff = objective_map_[r.representative] += coeff * r.coeff;
1617
1618 // Process new term.
1619 if (new_coeff == 0) {
1621 } else {
1622 var_to_constraints_[r.representative].insert(kObjectiveConstraint);
1623 if (IsFixed(r.representative)) {
1626 }
1627 }
1628 return true;
1629}
1630
1631bool PresolveContext::CanonicalizeObjective(bool simplify_domain) {
1632 // We replace each entry by its affine representative.
1633 // Note that the non-deterministic loop is fine, but because we iterate
1634 // one the map while modifying it, it is safer to do a copy rather than to
1635 // try to handle that in one pass.
1636 tmp_entries_.clear();
1637 for (const auto& entry : objective_map_) {
1638 tmp_entries_.push_back(entry);
1639 }
1640
1641 // TODO(user): This is a bit duplicated with the presolve linear code.
1642 // We also do not propagate back any domain restriction from the objective to
1643 // the variables if any.
1644 for (const auto& entry : tmp_entries_) {
1645 if (!CanonicalizeOneObjectiveVariable(entry.first)) return false;
1646 }
1647
1648 Domain implied_domain(0);
1649 int64_t gcd(0);
1650
1651 // We need to sort the entries to be deterministic.
1652 tmp_entries_.clear();
1653 for (const auto& entry : objective_map_) {
1654 tmp_entries_.push_back(entry);
1655 }
1656 std::sort(tmp_entries_.begin(), tmp_entries_.end());
1657 for (const auto& entry : tmp_entries_) {
1658 const int var = entry.first;
1659 const int64_t coeff = entry.second;
1660 gcd = MathUtil::GCD64(gcd, std::abs(coeff));
1661 implied_domain =
1662 implied_domain.AdditionWith(DomainOf(var).MultiplicationBy(coeff))
1664 }
1665
1666 // This is the new domain.
1667 // Note that the domain never include the offset.
1668 objective_domain_ = objective_domain_.IntersectionWith(implied_domain);
1669
1670 // Depending on the use case, we cannot do that.
1671 if (simplify_domain) {
1672 objective_domain_ =
1673 objective_domain_.SimplifyUsingImpliedDomain(implied_domain);
1674 }
1675
1676 // Maybe divide by GCD.
1677 if (gcd > 1) {
1678 for (auto& entry : objective_map_) {
1679 entry.second /= gcd;
1680 }
1681 objective_domain_ = objective_domain_.InverseMultiplicationBy(gcd);
1682 objective_offset_ /= static_cast<double>(gcd);
1683 objective_scaling_factor_ *= static_cast<double>(gcd);
1684 objective_integer_scaling_factor_ *= gcd;
1685 }
1686
1687 if (objective_domain_.IsEmpty()) return false;
1688
1689 // Detect if the objective domain do not limit the "optimal" objective value.
1690 // If this is true, then we can apply any reduction that reduce the objective
1691 // value without any issues.
1692 objective_domain_is_constraining_ =
1693 !implied_domain
1695 objective_domain_.Max()))
1696 .IsIncludedIn(objective_domain_);
1697 return true;
1698}
1699
1701 objective_map_.erase(var);
1702 EraseFromVarToConstraint(var, kObjectiveConstraint);
1703}
1704
1706 int64_t& map_ref = objective_map_[var];
1707 map_ref += value;
1708 if (map_ref == 0) {
1710 } else {
1711 var_to_constraints_[var].insert(kObjectiveConstraint);
1712 }
1713}
1714
1716 // Tricky: The objective domain is without the offset, so we need to shift it.
1717 objective_offset_ += static_cast<double>(delta);
1718 objective_integer_offset_ += delta * objective_integer_scaling_factor_;
1719 objective_domain_ = objective_domain_.AdditionWith(Domain(-delta));
1720}
1721
1723 int var_in_equality, int64_t coeff_in_equality,
1724 const ConstraintProto& equality, std::vector<int>* new_vars_in_objective) {
1725 CHECK(equality.enforcement_literal().empty());
1726 CHECK(RefIsPositive(var_in_equality));
1727
1728 if (new_vars_in_objective != nullptr) new_vars_in_objective->clear();
1729
1730 // We can only "easily" substitute if the objective coefficient is a multiple
1731 // of the one in the constraint.
1732 const int64_t coeff_in_objective =
1733 gtl::FindOrDie(objective_map_, var_in_equality);
1734 CHECK_NE(coeff_in_equality, 0);
1735 CHECK_EQ(coeff_in_objective % coeff_in_equality, 0);
1736
1737 const int64_t multiplier = coeff_in_objective / coeff_in_equality;
1738
1739 // Abort if the new objective seems to violate our overflow preconditions.
1740 int64_t change = 0;
1741 for (int i = 0; i < equality.linear().vars().size(); ++i) {
1742 int var = equality.linear().vars(i);
1743 if (PositiveRef(var) == var_in_equality) continue;
1744 int64_t coeff = equality.linear().coeffs(i);
1745 change +=
1746 std::abs(coeff) * std::max(std::abs(MinOf(var)), std::abs(MaxOf(var)));
1747 }
1748 const int64_t new_value =
1749 CapAdd(CapProd(std::abs(multiplier), change),
1750 objective_overflow_detection_ -
1751 std::abs(coeff_in_equality) *
1752 std::max(std::abs(MinOf(var_in_equality)),
1753 std::abs(MaxOf(var_in_equality))));
1754 if (new_value == std::numeric_limits<int64_t>::max()) return false;
1755 objective_overflow_detection_ = new_value;
1756
1757 // Compute the objective offset change.
1758 Domain offset = ReadDomainFromProto(equality.linear());
1759 DCHECK_EQ(offset.Min(), offset.Max());
1760 bool exact = true;
1761 offset = offset.MultiplicationBy(multiplier, &exact);
1762 CHECK(exact);
1763 CHECK(!offset.IsEmpty());
1764
1765 // We also need to make sure the integer_offset will not overflow.
1766 {
1767 int64_t temp = CapProd(offset.Min(), objective_integer_scaling_factor_);
1768 if (temp == std::numeric_limits<int64_t>::max()) return false;
1769 if (temp == std::numeric_limits<int64_t>::min()) return false;
1770 temp = CapAdd(temp, objective_integer_offset_);
1771 if (temp == std::numeric_limits<int64_t>::max()) return false;
1772 if (temp == std::numeric_limits<int64_t>::min()) return false;
1773 }
1774
1775 // Perform the substitution.
1776 for (int i = 0; i < equality.linear().vars().size(); ++i) {
1777 int var = equality.linear().vars(i);
1778 int64_t coeff = equality.linear().coeffs(i);
1779 if (!RefIsPositive(var)) {
1780 var = NegatedRef(var);
1781 coeff = -coeff;
1782 }
1783 if (var == var_in_equality) continue;
1784
1785 int64_t& map_ref = objective_map_[var];
1786 if (map_ref == 0 && new_vars_in_objective != nullptr) {
1787 new_vars_in_objective->push_back(var);
1788 }
1789 map_ref -= coeff * multiplier;
1790
1791 if (map_ref == 0) {
1793 } else {
1794 var_to_constraints_[var].insert(kObjectiveConstraint);
1795 }
1796 }
1797
1798 RemoveVariableFromObjective(var_in_equality);
1799
1800 // Tricky: The objective domain is without the offset, so we need to shift it.
1801 objective_offset_ += static_cast<double>(offset.Min());
1802 objective_integer_offset_ += offset.Min() * objective_integer_scaling_factor_;
1803 objective_domain_ = objective_domain_.AdditionWith(Domain(-offset.Min()));
1804
1805 // Because we can assume that the constraint we used was constraining
1806 // (otherwise it would have been removed), the objective domain should be now
1807 // constraining.
1808 objective_domain_is_constraining_ = true;
1809
1810 if (objective_domain_.IsEmpty()) {
1811 return NotifyThatModelIsUnsat();
1812 }
1813 return true;
1814}
1815
1817 absl::Span<const int> exactly_one) {
1818 if (objective_map_.empty()) return false;
1819
1820 int64_t min_coeff = std::numeric_limits<int64_t>::max();
1821 for (const int ref : exactly_one) {
1822 const auto it = objective_map_.find(PositiveRef(ref));
1823 if (it == objective_map_.end()) return false;
1824
1825 const int64_t coeff = it->second;
1826 if (RefIsPositive(ref)) {
1827 min_coeff = std::min(min_coeff, coeff);
1828 } else {
1829 // Objective = coeff * var = coeff * (1 - ref);
1830 min_coeff = std::min(min_coeff, -coeff);
1831 }
1832 }
1833
1834 int64_t offset = min_coeff;
1835 for (const int ref : exactly_one) {
1836 const int var = PositiveRef(ref);
1837 int64_t& map_ref = objective_map_.at(var);
1838 if (RefIsPositive(ref)) {
1839 map_ref -= min_coeff;
1840 if (map_ref == 0) {
1842 }
1843 } else {
1844 // Term = coeff * (1 - X) = coeff - coeff * X;
1845 // So -coeff -> -coeff -min_coeff
1846 // And Term = coeff + min_coeff - min_coeff - (coeff + min_coeff) * X
1847 // = (coeff + min_coeff) * (1 - X) - min_coeff;
1848 map_ref += min_coeff;
1849 if (map_ref == 0) {
1851 }
1852 offset -= min_coeff;
1853 }
1854 }
1855
1856 // Note that the domain never include the offset, so we need to update it.
1857 if (offset != 0) {
1858 objective_offset_ += offset;
1859 objective_integer_offset_ += offset * objective_integer_scaling_factor_;
1860 objective_domain_ = objective_domain_.AdditionWith(Domain(-offset));
1861 }
1862
1863 return true;
1864}
1865
1867 // We need to sort the entries to be deterministic.
1868 std::vector<std::pair<int, int64_t>> entries;
1869 for (const auto& entry : objective_map_) {
1870 entries.push_back(entry);
1871 }
1872 std::sort(entries.begin(), entries.end());
1873
1874 CpObjectiveProto* mutable_obj = working_model->mutable_objective();
1875 mutable_obj->set_offset(objective_offset_);
1876 mutable_obj->set_scaling_factor(objective_scaling_factor_);
1877 mutable_obj->set_integer_offset(objective_integer_offset_);
1878 if (objective_integer_scaling_factor_ == 1) {
1879 mutable_obj->set_integer_scaling_factor(0); // Default.
1880 } else {
1881 mutable_obj->set_integer_scaling_factor(objective_integer_scaling_factor_);
1882 }
1883 FillDomainInProto(objective_domain_, mutable_obj);
1884 mutable_obj->clear_vars();
1885 mutable_obj->clear_coeffs();
1886 for (const auto& entry : entries) {
1887 mutable_obj->add_vars(entry.first);
1888 mutable_obj->add_coeffs(entry.second);
1889 }
1890}
1891
1893 for (int i = 0; i < working_model->variables_size(); ++i) {
1894 FillDomainInProto(DomainOf(i), working_model->mutable_variables(i));
1895 }
1896}
1897
1899 const LinearExpressionProto& time_i, const LinearExpressionProto& time_j,
1900 int active_i, int active_j) {
1901 CHECK(!LiteralIsFalse(active_i));
1902 CHECK(!LiteralIsFalse(active_j));
1903 DCHECK(ExpressionIsAffine(time_i));
1904 DCHECK(ExpressionIsAffine(time_j));
1905
1906 const std::tuple<int, int64_t, int, int64_t, int64_t, int, int> key =
1907 GetReifiedPrecedenceKey(time_i, time_j, active_i, active_j);
1908 const auto& it = reified_precedences_cache_.find(key);
1909 if (it != reified_precedences_cache_.end()) return it->second;
1910
1911 const int result = NewBoolVar();
1912 reified_precedences_cache_[key] = result;
1913
1914 // result => (time_i <= time_j) && active_i && active_j.
1915 ConstraintProto* const lesseq = working_model->add_constraints();
1916 lesseq->add_enforcement_literal(result);
1917 if (!IsFixed(time_i)) {
1918 lesseq->mutable_linear()->add_vars(time_i.vars(0));
1919 lesseq->mutable_linear()->add_coeffs(-time_i.coeffs(0));
1920 }
1921 if (!IsFixed(time_j)) {
1922 lesseq->mutable_linear()->add_vars(time_j.vars(0));
1923 lesseq->mutable_linear()->add_coeffs(time_j.coeffs(0));
1924 }
1925
1926 const int64_t offset =
1927 (IsFixed(time_i) ? FixedValue(time_i) : time_i.offset()) -
1928 (IsFixed(time_j) ? FixedValue(time_j) : time_j.offset());
1929 lesseq->mutable_linear()->add_domain(offset);
1930 lesseq->mutable_linear()->add_domain(std::numeric_limits<int64_t>::max());
1931 if (!LiteralIsTrue(active_i)) {
1932 AddImplication(result, active_i);
1933 }
1934 if (!LiteralIsTrue(active_j)) {
1935 AddImplication(result, active_j);
1936 }
1937
1938 // Not(result) && active_i && active_j => (time_i > time_j)
1939 ConstraintProto* const greater = working_model->add_constraints();
1940 if (!IsFixed(time_i)) {
1941 greater->mutable_linear()->add_vars(time_i.vars(0));
1942 greater->mutable_linear()->add_coeffs(-time_i.coeffs(0));
1943 }
1944 if (!IsFixed(time_j)) {
1945 greater->mutable_linear()->add_vars(time_j.vars(0));
1946 greater->mutable_linear()->add_coeffs(time_j.coeffs(0));
1947 }
1948 greater->mutable_linear()->add_domain(std::numeric_limits<int64_t>::min());
1949 greater->mutable_linear()->add_domain(offset - 1);
1950
1951 // Manages enforcement literal.
1952 greater->add_enforcement_literal(NegatedRef(result));
1953 if (!LiteralIsTrue(active_i)) {
1954 greater->add_enforcement_literal(active_i);
1955 }
1956 if (!LiteralIsTrue(active_j)) {
1957 greater->add_enforcement_literal(active_j);
1958 }
1959
1960 // This is redundant but should improves performance.
1961 //
1962 // If GetOrCreateReifiedPrecedenceLiteral(time_j, time_i, active_j, active_i)
1963 // (the reverse precedence) has been called too, then we can link the two
1964 // precedence literals, and the two active literals together.
1965 const auto& rev_it = reified_precedences_cache_.find(
1966 GetReifiedPrecedenceKey(time_j, time_i, active_j, active_i));
1967 if (rev_it != reified_precedences_cache_.end()) {
1968 auto* const bool_or = working_model->add_constraints()->mutable_bool_or();
1969 bool_or->add_literals(result);
1970 bool_or->add_literals(rev_it->second);
1971 bool_or->add_literals(NegatedRef(active_i));
1972 bool_or->add_literals(NegatedRef(active_j));
1973 }
1974
1975 return result;
1976}
1977
1978std::tuple<int, int64_t, int, int64_t, int64_t, int, int>
1979PresolveContext::GetReifiedPrecedenceKey(const LinearExpressionProto& time_i,
1980 const LinearExpressionProto& time_j,
1981 int active_i, int active_j) {
1982 const int var_i =
1983 IsFixed(time_i) ? std::numeric_limits<int>::min() : time_i.vars(0);
1984 const int64_t coeff_i = IsFixed(time_i) ? 0 : time_i.coeffs(0);
1985 const int var_j =
1986 IsFixed(time_j) ? std::numeric_limits<int>::min() : time_j.vars(0);
1987 const int64_t coeff_j = IsFixed(time_j) ? 0 : time_j.coeffs(0);
1988 const int64_t offset =
1989 (IsFixed(time_i) ? FixedValue(time_i) : time_i.offset()) -
1990 (IsFixed(time_j) ? FixedValue(time_j) : time_j.offset());
1991 // In all formulas, active_i and active_j are symmetrical, we can sort the
1992 // active literals.
1993 if (active_j < active_i) std::swap(active_i, active_j);
1994 return std::make_tuple(var_i, coeff_i, var_j, coeff_j, offset, active_i,
1995 active_j);
1996}
1997
1999 reified_precedences_cache_.clear();
2000}
2001
2003 SOLVER_LOG(logger_, "");
2004 SOLVER_LOG(logger_, "Presolve summary:");
2005 SOLVER_LOG(logger_, " - ", NumAffineRelations(),
2006 " affine relations were detected.");
2007 absl::btree_map<std::string, int> sorted_rules(stats_by_rule_name_.begin(),
2008 stats_by_rule_name_.end());
2009 for (const auto& entry : sorted_rules) {
2010 if (entry.second == 1) {
2011 SOLVER_LOG(logger_, " - rule '", entry.first, "' was applied 1 time.");
2012 } else {
2013 SOLVER_LOG(logger_, " - rule '", entry.first, "' was applied ",
2014 entry.second, " times.");
2015 }
2016 }
2017}
2018
2020 if (context->ModelIsUnsat()) return false;
2021
2022 // Update the domain in the current CpModelProto.
2023 context->WriteVariableDomainsToProto();
2024 const CpModelProto& model_proto = *(context->working_model);
2025
2026 // Load the constraints in a local model.
2027 //
2028 // TODO(user): The model we load does not contain affine relations! But
2029 // ideally we should be able to remove all of them once we allow more complex
2030 // constraints to contains linear expression.
2031 //
2032 // TODO(user): remove code duplication with cp_model_solver. Here we also do
2033 // not run the heuristic to decide which variable to fully encode.
2034 //
2035 // TODO(user): Maybe do not load slow to propagate constraints? for instance
2036 // we do not use any linear relaxation here.
2037 Model model;
2038 local_model->Register<SolverLogger>(context->logger());
2039
2040 // Adapt some of the parameters during this probing phase.
2041 auto* local_param = local_model->GetOrCreate<SatParameters>();
2042 *local_param = context->params();
2043 local_param->set_use_implied_bounds(false);
2044
2045 local_model->GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(
2046 context->time_limit());
2047 local_model->Register<ModelRandomGenerator>(context->random());
2048 auto* encoder = local_model->GetOrCreate<IntegerEncoder>();
2049 encoder->DisableImplicationBetweenLiteral();
2050 auto* mapping = local_model->GetOrCreate<CpModelMapping>();
2051
2052 // Important: Because the model_proto do not contains affine relation or the
2053 // objective, we cannot call DetectOptionalVariables() ! This might wrongly
2054 // detect optionality and derive bad conclusion.
2055 LoadVariables(model_proto, /*view_all_booleans_as_integers=*/false,
2056 local_model);
2057 ExtractEncoding(model_proto, local_model);
2058 auto* sat_solver = local_model->GetOrCreate<SatSolver>();
2059 for (const ConstraintProto& ct : model_proto.constraints()) {
2060 if (mapping->ConstraintIsAlreadyLoaded(&ct)) continue;
2061 CHECK(LoadConstraint(ct, local_model));
2062 if (sat_solver->IsModelUnsat()) {
2063 return context->NotifyThatModelIsUnsat(absl::StrCat(
2064 "after loading constraint during probing ", ct.ShortDebugString()));
2065 }
2066 }
2067 encoder->AddAllImplicationsBetweenAssociatedLiterals();
2068 if (!sat_solver->Propagate()) {
2069 return context->NotifyThatModelIsUnsat(
2070 "during probing initial propagation");
2071 }
2072
2073 return true;
2074}
2075
2076} // namespace sat
2077} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:495
#define DCHECK_LE(val1, val2)
Definition: base/logging.h:893
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:892
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:703
#define CHECK_NE(val1, val2)
Definition: base/logging.h:704
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:894
#define LOG(severity)
Definition: base/logging.h:420
#define DCHECK(condition)
Definition: base/logging.h:890
#define CHECK_LE(val1, val2)
Definition: base/logging.h:705
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:891
#define VLOG(verboselevel)
Definition: base/logging.h:984
bool TryAdd(int x, int y, int64_t coeff, int64_t offset)
We call domain any subset of Int64 = [kint64min, kint64max].
static Domain AllValues()
Returns the full domain Int64.
Domain InverseMultiplicationBy(const int64_t coeff) const
Returns {x ∈ Int64, ∃ e ∈ D, x * coeff = e}.
Domain Negation() const
Returns {x ∈ Int64, ∃ e ∈ D, x = -e}.
bool IsIncludedIn(const Domain &domain) const
Returns true iff D is included in the given domain.
bool Contains(int64_t value) const
Returns true iff value is in Domain.
Domain ContinuousMultiplicationBy(int64_t coeff) const
Returns a superset of MultiplicationBy() to avoid the explosion in the representation size.
int64_t FixedValue() const
Returns the value of a fixed domain.
Domain AdditionWith(const Domain &domain) const
Returns {x ∈ Int64, ∃ a ∈ D, ∃ b ∈ domain, x = a + b}.
Domain MultiplicationBy(int64_t coeff, bool *exact=nullptr) const
Returns {x ∈ Int64, ∃ e ∈ D, x = e * coeff}.
bool IsFixed() const
Returns true iff the domain is reduced to a single value.
Domain IntersectionWith(const Domain &domain) const
Returns the intersection of D and domain.
int64_t Min() const
Returns the min value of the domain.
bool IsEmpty() const
Returns true if this is the empty set.
int64_t Max() const
Returns the max value of the domain.
Domain RelaxIfTooComplex() const
If NumIntervals() is too large, this return a superset of the domain.
Domain SimplifyUsingImpliedDomain(const Domain &implied_domain) const
Advanced usage.
static int64_t GCD64(int64_t x, int64_t y)
Definition: mathutil.h:107
void Set(IntegerType index)
Definition: bitset.h:809
void Resize(IntegerType size)
Definition: bitset.h:795
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:106
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
void Register(T *non_owned_class)
Register a non-owned class that will be "singleton" in the model.
Definition: sat/model.h:173
T * GetOrCreate()
Returns an object of type T that is unique to this model (like a "local" singleton).
Definition: sat/model.h:110
bool CanonicalizeAffineVariable(int ref, int64_t coeff, int64_t mod, int64_t rhs)
bool ExpressionIsALiteral(const LinearExpressionProto &expr, int *literal=nullptr) const
bool StoreAbsRelation(int target_ref, int ref)
void AddToObjective(int var, int64_t value)
ABSL_MUST_USE_RESULT bool IntersectDomainWith(int ref, const Domain &domain, bool *domain_modified=nullptr)
std::vector< absl::flat_hash_set< int > > var_to_lb_only_constraints
bool StoreLiteralImpliesVarNEqValue(int literal, int var, int64_t value)
int GetOrCreateReifiedPrecedenceLiteral(const LinearExpressionProto &time_i, const LinearExpressionProto &time_j, int active_i, int active_j)
ABSL_MUST_USE_RESULT bool CanonicalizeObjective(bool simplify_domain=true)
bool StoreBooleanEqualityRelation(int ref_a, int ref_b)
bool VariableWithCostIsUniqueAndRemovable(int ref) const
bool ExpressionIsSingleVariable(const LinearExpressionProto &expr) const
ABSL_MUST_USE_RESULT bool SetLiteralToTrue(int lit)
int GetOrCreateAffineValueEncoding(const LinearExpressionProto &expr, int64_t value)
ABSL_MUST_USE_RESULT bool ScaleFloatingPointObjective()
std::vector< absl::flat_hash_set< int > > var_to_ub_only_constraints
ABSL_MUST_USE_RESULT bool CanonicalizeOneObjectiveVariable(int var)
ABSL_MUST_USE_RESULT bool SubstituteVariableInObjective(int var_in_equality, int64_t coeff_in_equality, const ConstraintProto &equality, std::vector< int > *new_vars_in_objective=nullptr)
int GetOrCreateVarValueEncoding(int ref, int64_t value)
ABSL_MUST_USE_RESULT bool NotifyThatModelIsUnsat(const std::string &message="")
std::string AffineRelationDebugString(int ref) const
bool InsertVarValueEncoding(int literal, int ref, int64_t value)
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 HasVarValueEncoding(int ref, int64_t value, int *literal=nullptr)
bool DomainContains(int ref, int64_t value) const
void UpdateRuleStats(const std::string &name, int num_times=1)
AffineRelation::Relation GetAffineRelation(int ref) const
bool StoreAffineRelation(int ref_x, int ref_y, int64_t coeff, int64_t offset, bool debug_no_recursion=false)
std::string IntervalDebugString(int ct_ref) const
ABSL_MUST_USE_RESULT bool SetLiteralToFalse(int lit)
int LiteralForExpressionMax(const LinearExpressionProto &expr) const
bool ExpressionIsAffineBoolean(const LinearExpressionProto &expr) const
bool ExploitExactlyOneInObjective(absl::Span< const int > exactly_one)
Domain DomainSuperSetOf(const LinearExpressionProto &expr) const
void AddImplyInDomain(int b, int x, const Domain &domain)
bool VariableIsOnlyUsedInEncodingAndMaybeInObjective(int ref) const
bool GetAbsRelation(int target_ref, int *ref)
bool StoreLiteralImpliesVarEqValue(int literal, int var, int64_t value)
int Get(PresolveContext *context) const
int Get(PresolveContext *context) const
int64_t b
int64_t a
CpModelProto proto
CpModelProto const * model_proto
const std::string name
const Constraint * ct
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
GurobiMPCallbackContext * context
int index
const int INFO
Definition: log_severity.h:31
const Collection::value_type::second_type & FindOrDie(const Collection &collection, const typename Collection::value_type::first_type &key)
Definition: map_util.h:206
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:262
bool ScaleAndSetObjective(const SatParameters &params, const std::vector< std::pair< int, double > > &objective, double objective_offset, bool maximize, CpModelProto *cp_model, SolverLogger *logger)
void LoadVariables(const CpModelProto &model_proto, bool view_all_booleans_as_integers, Model *m)
bool LoadConstraint(const ConstraintProto &ct, Model *m)
std::vector< int > UsedVariables(const ConstraintProto &ct)
bool RefIsPositive(int ref)
std::vector< int > UsedIntervals(const ConstraintProto &ct)
constexpr int kAffineRelationConstraint
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
bool ExpressionIsAffine(const LinearExpressionProto &expr)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
int64_t ProductWithModularInverse(int64_t coeff, int64_t mod, int64_t rhs)
Definition: sat/util.cc:125
bool LoadModelForProbing(PresolveContext *context, Model *local_model)
constexpr int kObjectiveConstraint
void ExtractEncoding(const CpModelProto &model_proto, Model *m)
Collection of objects used to extend the Constraint Solver library.
int64_t CapAdd(int64_t x, int64_t y)
const absl::string_view ToString(MPSolver::OptimizationProblemType optimization_problem_type)
int64_t CapProd(int64_t x, int64_t y)
std::string ProtobufDebugString(const P &message)
Literal literal
Definition: optimization.cc:89
ColIndex representative
int64_t delta
Definition: resource.cc:1694
IntervalVar * interval
Definition: resource.cc:100
#define SOLVER_LOG(logger,...)
Definition: util/logging.h:69
const double coeff
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:44