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