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