OR-Tools  9.0
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 <cstdint>
17 #include <limits>
18 
19 #include "ortools/base/map_util.h"
20 #include "ortools/base/mathutil.h"
23 
24 namespace operations_research {
25 namespace sat {
26 
28  return context->GetLiteralRepresentative(ref_);
29 }
30 
32  return context->GetVariableRepresentative(ref_);
33 }
34 
36 
37 int PresolveContext::NewIntVar(const Domain& domain) {
38  IntegerVariableProto* const var = working_model->add_variables();
39  FillDomainInProto(domain, var);
41  return working_model->variables_size() - 1;
42 }
43 
45 
47  if (!gtl::ContainsKey(constant_to_ref_, cst)) {
48  constant_to_ref_[cst] = SavedVariable(working_model->variables_size());
49  IntegerVariableProto* const var_proto = working_model->add_variables();
50  var_proto->add_domain(cst);
51  var_proto->add_domain(cst);
53  }
54  return constant_to_ref_[cst].Get(this);
55 }
56 
57 // a => b.
59  ConstraintProto* const ct = working_model->add_constraints();
60  ct->add_enforcement_literal(a);
61  ct->mutable_bool_and()->add_literals(b);
62 }
63 
64 // b => x in [lb, ub].
65 void PresolveContext::AddImplyInDomain(int b, int x, const Domain& domain) {
66  ConstraintProto* const imply = working_model->add_constraints();
67 
68  // Doing it like this seems to use slightly less memory.
69  // TODO(user): Find the best way to create such small proto.
70  imply->mutable_enforcement_literal()->Resize(1, b);
71  LinearConstraintProto* mutable_linear = imply->mutable_linear();
72  mutable_linear->mutable_vars()->Resize(1, x);
73  mutable_linear->mutable_coeffs()->Resize(1, 1);
74  FillDomainInProto(domain, mutable_linear);
75 }
76 
77 bool PresolveContext::DomainIsEmpty(int ref) const {
78  return domains[PositiveRef(ref)].IsEmpty();
79 }
80 
81 bool PresolveContext::IsFixed(int ref) const {
82  DCHECK_LT(PositiveRef(ref), domains.size());
83  DCHECK(!DomainIsEmpty(ref));
84  return domains[PositiveRef(ref)].IsFixed();
85 }
86 
88  const int var = PositiveRef(ref);
89  return domains[var].Min() >= 0 && domains[var].Max() <= 1;
90 }
91 
92 bool PresolveContext::LiteralIsTrue(int lit) const {
94  if (RefIsPositive(lit)) {
95  return domains[lit].Min() == 1;
96  } else {
97  return domains[PositiveRef(lit)].Max() == 0;
98  }
99 }
100 
101 bool PresolveContext::LiteralIsFalse(int lit) const {
103  if (RefIsPositive(lit)) {
104  return domains[lit].Max() == 0;
105  } else {
106  return domains[PositiveRef(lit)].Min() == 1;
107  }
108 }
109 
110 int64_t PresolveContext::MinOf(int ref) const {
111  DCHECK(!DomainIsEmpty(ref));
112  return RefIsPositive(ref) ? domains[PositiveRef(ref)].Min()
113  : -domains[PositiveRef(ref)].Max();
114 }
115 
116 int64_t PresolveContext::MaxOf(int ref) const {
117  DCHECK(!DomainIsEmpty(ref));
118  return RefIsPositive(ref) ? domains[PositiveRef(ref)].Max()
119  : -domains[PositiveRef(ref)].Min();
120 }
121 
122 int64_t PresolveContext::MinOf(const LinearExpressionProto& expr) const {
123  int64_t result = expr.offset();
124  for (int i = 0; i < expr.vars_size(); ++i) {
125  const int64_t coeff = expr.coeffs(i);
126  if (coeff > 0) {
127  result += coeff * MinOf(expr.vars(i));
128  } else {
129  result += coeff * MaxOf(expr.vars(i));
130  }
131  }
132  return result;
133 }
134 
135 int64_t PresolveContext::MaxOf(const LinearExpressionProto& expr) const {
136  int64_t result = expr.offset();
137  for (int i = 0; i < expr.vars_size(); ++i) {
138  const int64_t coeff = expr.coeffs(i);
139  if (coeff > 0) {
140  result += coeff * MaxOf(expr.vars(i));
141  } else {
142  result += coeff * MinOf(expr.vars(i));
143  }
144  }
145  return result;
146 }
147 
148 // Important: To be sure a variable can be removed, we need it to not be a
149 // representative of both affine and equivalence relation.
150 bool PresolveContext::VariableIsNotRepresentativeOfEquivalenceClass(
151  int var) const {
153  if (affine_relations_.ClassSize(var) > 1 &&
154  affine_relations_.Get(var).representative == var) {
155  return false;
156  }
157  if (var_equiv_relations_.ClassSize(var) > 1 &&
158  var_equiv_relations_.Get(var).representative == var) {
159  return false;
160  }
161  return true;
162 }
163 
164 // Tricky: If this variable is equivalent to another one (but not the
165 // representative) and appear in just one constraint, then this constraint must
166 // be the affine defining one. And in this case the code using this function
167 // should do the proper stuff.
169  if (!ConstraintVariableGraphIsUpToDate()) return false;
170  const int var = PositiveRef(ref);
171  return var_to_constraints_[var].size() == 1 &&
172  VariableIsNotRepresentativeOfEquivalenceClass(var) &&
174 }
175 
176 // Tricky: Same remark as for VariableIsUniqueAndRemovable().
178  if (!ConstraintVariableGraphIsUpToDate()) return false;
179  const int var = PositiveRef(ref);
180  return !keep_all_feasible_solutions &&
181  var_to_constraints_[var].contains(kObjectiveConstraint) &&
182  var_to_constraints_[var].size() == 2 &&
183  VariableIsNotRepresentativeOfEquivalenceClass(var);
184 }
185 
186 // Here, even if the variable is equivalent to others, if its affine defining
187 // constraints where removed, then it is not needed anymore.
189  if (!ConstraintVariableGraphIsUpToDate()) return false;
190  return var_to_constraints_[PositiveRef(ref)].empty();
191 }
192 
194  removed_variables_.insert(PositiveRef(ref));
195 }
196 
197 // Note(user): I added an indirection and a function for this to be able to
198 // display debug information when this return false. This should actually never
199 // return false in the cases where it is used.
201  // It is okay to reuse removed fixed variable.
202  if (IsFixed(ref)) return false;
203  if (!removed_variables_.contains(PositiveRef(ref))) return false;
204  if (!var_to_constraints_[PositiveRef(ref)].empty()) {
205  SOLVER_LOG(logger_, "Variable ", PositiveRef(ref),
206  " was removed, yet it appears in some constraints!");
207  SOLVER_LOG(logger_, "affine relation: ",
209  for (const int c : var_to_constraints_[PositiveRef(ref)]) {
210  SOLVER_LOG(
211  logger_, "constraint #", c, " : ",
212  c >= 0 ? working_model->constraints(c).ShortDebugString() : "");
213  }
214  }
215  return true;
216 }
217 
219  if (!ConstraintVariableGraphIsUpToDate()) return false;
220  const int var = PositiveRef(ref);
221  return var_to_num_linear1_[var] == var_to_constraints_[var].size();
222 }
223 
225  Domain result;
226  if (RefIsPositive(ref)) {
227  result = domains[ref];
228  } else {
229  result = domains[PositiveRef(ref)].Negation();
230  }
231  return result;
232 }
233 
234 bool PresolveContext::DomainContains(int ref, int64_t value) const {
235  if (!RefIsPositive(ref)) {
236  return domains[PositiveRef(ref)].Contains(-value);
237  }
238  return domains[ref].Contains(value);
239 }
240 
241 ABSL_MUST_USE_RESULT bool PresolveContext::IntersectDomainWith(
242  int ref, const Domain& domain, bool* domain_modified) {
243  DCHECK(!DomainIsEmpty(ref));
244  const int var = PositiveRef(ref);
245 
246  if (RefIsPositive(ref)) {
247  if (domains[var].IsIncludedIn(domain)) {
248  return true;
249  }
250  domains[var] = domains[var].IntersectionWith(domain);
251  } else {
252  const Domain temp = domain.Negation();
253  if (domains[var].IsIncludedIn(temp)) {
254  return true;
255  }
256  domains[var] = domains[var].IntersectionWith(temp);
257  }
258 
259  if (domain_modified != nullptr) {
260  *domain_modified = true;
261  }
263  if (domains[var].IsEmpty()) {
264  is_unsat = true;
265  return false;
266  }
267 
268  // Propagate the domain of the representative right away.
269  // Note that the recursive call should only by one level deep.
271  if (r.representative == var) return true;
273  DomainOf(var)
274  .AdditionWith(Domain(-r.offset))
276 }
277 
278 ABSL_MUST_USE_RESULT bool PresolveContext::SetLiteralToFalse(int lit) {
279  const int var = PositiveRef(lit);
280  const int64_t value = RefIsPositive(lit) ? 0 : 1;
282 }
283 
284 ABSL_MUST_USE_RESULT bool PresolveContext::SetLiteralToTrue(int lit) {
285  return SetLiteralToFalse(NegatedRef(lit));
286 }
287 
288 void PresolveContext::UpdateRuleStats(const std::string& name, int num_times) {
289  // We only count if we are going to display it.
290  if (logger_->LoggingIsEnabled()) {
291  VLOG(1) << num_presolve_operations << " : " << name;
292  stats_by_rule_name[name] += num_times;
293  }
294  num_presolve_operations += num_times;
295 }
296 
297 void PresolveContext::UpdateLinear1Usage(const ConstraintProto& ct, int c) {
298  const int old_var = constraint_to_linear1_var_[c];
299  if (old_var >= 0) {
300  var_to_num_linear1_[old_var]--;
301  }
302  if (ct.constraint_case() == ConstraintProto::ConstraintCase::kLinear &&
303  ct.linear().vars().size() == 1) {
304  const int var = PositiveRef(ct.linear().vars(0));
305  constraint_to_linear1_var_[c] = var;
306  var_to_num_linear1_[var]++;
307  }
308 }
309 
310 void PresolveContext::AddVariableUsage(int c) {
311  const ConstraintProto& ct = working_model->constraints(c);
312  constraint_to_vars_[c] = UsedVariables(ct);
313  constraint_to_intervals_[c] = UsedIntervals(ct);
314  for (const int v : constraint_to_vars_[c]) {
316  var_to_constraints_[v].insert(c);
317  }
318  for (const int i : constraint_to_intervals_[c]) interval_usage_[i]++;
319  UpdateLinear1Usage(ct, c);
320 }
321 
323  if (is_unsat) return;
324  DCHECK_EQ(constraint_to_vars_.size(), working_model->constraints_size());
325  const ConstraintProto& ct = working_model->constraints(c);
326 
327  // We don't optimize the interval usage as this is not super frequent.
328  for (const int i : constraint_to_intervals_[c]) interval_usage_[i]--;
329  constraint_to_intervals_[c] = UsedIntervals(ct);
330  for (const int i : constraint_to_intervals_[c]) interval_usage_[i]++;
331 
332  // For the variables, we avoid an erase() followed by an insert() for the
333  // variables that didn't change.
334  tmp_new_usage_ = UsedVariables(ct);
335  const std::vector<int>& old_usage = constraint_to_vars_[c];
336  const int old_size = old_usage.size();
337  int i = 0;
338  for (const int var : tmp_new_usage_) {
340  while (i < old_size && old_usage[i] < var) {
341  var_to_constraints_[old_usage[i]].erase(c);
342  ++i;
343  }
344  if (i < old_size && old_usage[i] == var) {
345  ++i;
346  } else {
347  var_to_constraints_[var].insert(c);
348  }
349  }
350  for (; i < old_size; ++i) var_to_constraints_[old_usage[i]].erase(c);
351  constraint_to_vars_[c] = tmp_new_usage_;
352 
353  UpdateLinear1Usage(ct, c);
354 }
355 
357  return constraint_to_vars_.size() == working_model->constraints_size();
358 }
359 
361  if (is_unsat) return;
362  const int old_size = constraint_to_vars_.size();
363  const int new_size = working_model->constraints_size();
364  CHECK_LE(old_size, new_size);
365  constraint_to_vars_.resize(new_size);
366  constraint_to_linear1_var_.resize(new_size, -1);
367  constraint_to_intervals_.resize(new_size);
368  interval_usage_.resize(new_size);
369  for (int c = old_size; c < new_size; ++c) {
370  AddVariableUsage(c);
371  }
372 }
373 
374 // TODO(user): Also test var_to_constraints_ !!
376  if (is_unsat) return true; // We do not care in this case.
377  if (constraint_to_vars_.size() != working_model->constraints_size()) {
378  LOG(INFO) << "Wrong constraint_to_vars size!";
379  return false;
380  }
381  for (int c = 0; c < constraint_to_vars_.size(); ++c) {
382  if (constraint_to_vars_[c] !=
383  UsedVariables(working_model->constraints(c))) {
384  LOG(INFO) << "Wrong variables usage for constraint: \n"
385  << ProtobufDebugString(working_model->constraints(c))
386  << "old_size: " << constraint_to_vars_[c].size();
387  return false;
388  }
389  }
390  int num_in_objective = 0;
391  for (int v = 0; v < var_to_constraints_.size(); ++v) {
392  if (var_to_constraints_[v].contains(kObjectiveConstraint)) {
393  ++num_in_objective;
394  if (!objective_map_.contains(v)) {
395  LOG(INFO) << "Variable " << v
396  << " is marked as part of the objective but isn't.";
397  return false;
398  }
399  }
400  }
401  if (num_in_objective != objective_map_.size()) {
402  LOG(INFO) << "Not all variables are marked as part of the objective";
403  return false;
404  }
405 
406  return true;
407 }
408 
409 // If a Boolean variable (one with domain [0, 1]) appear in this affine
410 // equivalence class, then we want its representative to be Boolean. Note that
411 // this is always possible because a Boolean variable can never be equal to a
412 // multiple of another if std::abs(coeff) is greater than 1 and if it is not
413 // fixed to zero. This is important because it allows to simply use the same
414 // representative for any referenced literals.
415 //
416 // Note(user): When both domain contains [0,1] and later the wrong variable
417 // become usable as boolean, then we have a bug. Because of that, the code
418 // for GetLiteralRepresentative() is not as simple as it should be.
419 bool PresolveContext::AddRelation(int x, int y, int64_t c, int64_t o,
420  AffineRelation* repo) {
421  // When the coefficient is larger than one, then if later one variable becomes
422  // Boolean, it must be the representative.
423  if (std::abs(c) != 1) return repo->TryAdd(x, y, c, o);
424 
427 
428  // To avoid integer overflow, we always want to use the representative with
429  // the smallest domain magnitude. Otherwise we might express a variable in say
430  // [0, 3] as ([x, x + 3] - x) for an arbitrary large x, and substituting
431  // something like this in a linear expression could break our overflow
432  // precondition.
433  //
434  // Note that if either rep_x or rep_y can be used as a literal, then it will
435  // also be the variable with the smallest domain magnitude (1 or 0 if fixed).
436  const int rep_x = repo->Get(x).representative;
437  const int rep_y = repo->Get(y).representative;
438  const int64_t m_x = std::max(std::abs(MinOf(rep_x)), std::abs(MaxOf(rep_x)));
439  const int64_t m_y = std::max(std::abs(MinOf(rep_y)), std::abs(MaxOf(rep_y)));
440  bool allow_rep_x = m_x < m_y;
441  bool allow_rep_y = m_y < m_x;
442  if (m_x == m_y) {
443  // If both magnitude are the same, we prefer a positive domain.
444  // This is important so we don't use [-1, 0] as a representative for [0, 1].
445  allow_rep_x = MinOf(rep_x) >= MinOf(rep_y);
446  allow_rep_y = MinOf(rep_y) >= MinOf(rep_x);
447  }
448  return repo->TryAdd(x, y, c, o, allow_rep_x, allow_rep_y);
449 }
450 
453  CHECK(IsFixed(var));
454  const int64_t min = MinOf(var);
455  if (gtl::ContainsKey(constant_to_ref_, min)) {
456  const int rep = constant_to_ref_[min].Get(this);
457  if (RefIsPositive(rep)) {
458  if (rep != var) {
459  AddRelation(var, rep, 1, 0, &affine_relations_);
460  AddRelation(var, rep, 1, 0, &var_equiv_relations_);
461  }
462  } else {
463  if (PositiveRef(rep) == var) {
464  CHECK_EQ(min, 0);
465  } else {
466  AddRelation(var, PositiveRef(rep), -1, 0, &affine_relations_);
467  AddRelation(var, PositiveRef(rep), -1, 0, &var_equiv_relations_);
468  }
469  }
470  } else {
471  constant_to_ref_[min] = SavedVariable(var);
472  }
473 }
474 
476  const int var = PositiveRef(ref);
478  if (r.representative == var) return true;
479 
480  // Propagate domains both ways.
481  // var = coeff * rep + offset
483  DomainOf(var)
484  .AdditionWith(Domain(-r.offset))
486  return false;
487  }
490  .AdditionWith(Domain(r.offset)))) {
491  return false;
492  }
493 
494  return true;
495 }
496 
498  for (auto& ref_map : var_to_constraints_) {
499  ref_map.erase(kAffineRelationConstraint);
500  }
501 }
502 
503 // We only call that for a non representative variable that is only used in
504 // the kAffineRelationConstraint. Such variable can be ignored and should never
505 // be seen again in the presolve.
507  const int rep = GetAffineRelation(var).representative;
508 
510  CHECK_NE(var, rep);
511  CHECK_EQ(var_to_constraints_[var].size(), 1);
512  CHECK(var_to_constraints_[var].contains(kAffineRelationConstraint));
513  CHECK(var_to_constraints_[rep].contains(kAffineRelationConstraint));
514 
515  // We shouldn't reuse this variable again!
517 
518  var_to_constraints_[var].erase(kAffineRelationConstraint);
519  affine_relations_.IgnoreFromClassSize(var);
520  var_equiv_relations_.IgnoreFromClassSize(var);
521 
522  // If the representative is left alone, we can remove it from the special
523  // affine relation constraint too.
524  if (affine_relations_.ClassSize(rep) == 1 &&
525  var_equiv_relations_.ClassSize(rep) == 1) {
526  var_to_constraints_[rep].erase(kAffineRelationConstraint);
527  }
528 
529  if (VLOG_IS_ON(2)) {
530  LOG(INFO) << "Removing affine relation: " << AffineRelationDebugString(var);
531  }
532 }
533 
534 bool PresolveContext::StoreAffineRelation(int ref_x, int ref_y, int64_t coeff,
535  int64_t offset) {
536  CHECK_NE(coeff, 0);
537  if (is_unsat) return false;
538 
539  // TODO(user): I am not 100% sure why, but sometimes the representative is
540  // fixed but that is not propagated to ref_x or ref_y and this causes issues.
541  if (!PropagateAffineRelation(ref_x)) return true;
542  if (!PropagateAffineRelation(ref_y)) return true;
543 
544  if (IsFixed(ref_x)) {
545  const int64_t lhs = DomainOf(ref_x).Min() - offset;
546  if (lhs % std::abs(coeff) != 0) {
547  is_unsat = true;
548  return true;
549  }
550  static_cast<void>(IntersectDomainWith(ref_y, Domain(lhs / coeff)));
551  UpdateRuleStats("affine: fixed");
552  return true;
553  }
554 
555  if (IsFixed(ref_y)) {
556  const int64_t value_x = DomainOf(ref_y).Min() * coeff + offset;
557  static_cast<void>(IntersectDomainWith(ref_x, Domain(value_x)));
558  UpdateRuleStats("affine: fixed");
559  return true;
560  }
561 
562  // If both are already in the same class, we need to make sure the relations
563  // are compatible.
566  if (rx.representative == ry.representative) {
567  // x = rx.coeff * rep + rx.offset;
568  // y = ry.coeff * rep + ry.offset_y;
569  // And x == coeff * ry.coeff * rep + (coeff * ry.offset + offset).
570  //
571  // So we get the relation a * rep == b with a and b defined here:
572  const int64_t a = coeff * ry.coeff - rx.coeff;
573  const int64_t b = coeff * ry.offset + offset - rx.offset;
574  if (a == 0) {
575  if (b != 0) is_unsat = true;
576  return true;
577  }
578  if (b % a != 0) {
579  is_unsat = true;
580  return true;
581  }
582  UpdateRuleStats("affine: unique solution");
583  const int64_t unique_value = -b / a;
584  if (!IntersectDomainWith(rx.representative, Domain(unique_value))) {
585  return true;
586  }
587  if (!IntersectDomainWith(ref_x,
588  Domain(unique_value * rx.coeff + rx.offset))) {
589  return true;
590  }
591  if (!IntersectDomainWith(ref_y,
592  Domain(unique_value * ry.coeff + ry.offset))) {
593  return true;
594  }
595  return true;
596  }
597 
598  const int x = PositiveRef(ref_x);
599  const int y = PositiveRef(ref_y);
600  const int64_t c =
601  RefIsPositive(ref_x) == RefIsPositive(ref_y) ? coeff : -coeff;
602  const int64_t o = RefIsPositive(ref_x) ? offset : -offset;
603 
604  // TODO(user): can we force the rep and remove GetAffineRelation()?
605  bool added = AddRelation(x, y, c, o, &affine_relations_);
606  if ((c == 1 || c == -1) && o == 0) {
607  added |= AddRelation(x, y, c, o, &var_equiv_relations_);
608  }
609  if (added) {
610  UpdateRuleStats("affine: new relation");
611 
612  // Lets propagate again the new relation. We might as well do it as early
613  // as possible and not all call site do it.
614  if (!PropagateAffineRelation(ref_x)) return true;
615  if (!PropagateAffineRelation(ref_y)) return true;
616 
617  // These maps should only contains representative, so only need to remap
618  // either x or y.
619  const int rep = GetAffineRelation(x).representative;
620  if (x != rep) encoding_remap_queue_.push_back(x);
621  if (y != rep) encoding_remap_queue_.push_back(y);
622 
623  // The domain didn't change, but this notification allows to re-process any
624  // constraint containing these variables. Note that we do not need to
625  // retrigger a propagation of the constraint containing a variable whose
626  // representative didn't change.
627  if (x != rep) modified_domains.Set(x);
628  if (y != rep) modified_domains.Set(y);
629 
630  var_to_constraints_[x].insert(kAffineRelationConstraint);
631  var_to_constraints_[y].insert(kAffineRelationConstraint);
632  return true;
633  }
634 
635  UpdateRuleStats("affine: incompatible relation");
636  if (VLOG_IS_ON(1)) {
637  LOG(INFO) << "Cannot add relation " << DomainOf(ref_x) << " = " << coeff
638  << " * " << DomainOf(ref_y) << " + " << offset
639  << " because of incompatibilities with existing relation: ";
640  for (const int ref : {ref_x, ref_y}) {
641  const auto r = GetAffineRelation(ref);
642  LOG(INFO) << DomainOf(ref) << " = " << r.coeff << " * "
643  << DomainOf(r.representative) << " + " << r.offset;
644  }
645  }
646 
647  return false;
648 }
649 
651  if (is_unsat) return;
652 
653  CHECK(!VariableWasRemoved(ref_a));
654  CHECK(!VariableWasRemoved(ref_b));
655  CHECK(!DomainOf(ref_a).IsEmpty());
656  CHECK(!DomainOf(ref_b).IsEmpty());
657  CHECK(CanBeUsedAsLiteral(ref_a));
658  CHECK(CanBeUsedAsLiteral(ref_b));
659 
660  if (ref_a == ref_b) return;
661  if (ref_a == NegatedRef(ref_b)) {
662  is_unsat = true;
663  return;
664  }
665  const int var_a = PositiveRef(ref_a);
666  const int var_b = PositiveRef(ref_b);
667  if (RefIsPositive(ref_a) == RefIsPositive(ref_b)) {
668  // a = b
669  CHECK(StoreAffineRelation(var_a, var_b, /*coeff=*/1, /*offset=*/0));
670  } else {
671  // a = 1 - b
672  CHECK(StoreAffineRelation(var_a, var_b, /*coeff=*/-1, /*offset=*/1));
673  }
674 }
675 
676 bool PresolveContext::StoreAbsRelation(int target_ref, int ref) {
677  const auto insert_status = abs_relations_.insert(
678  std::make_pair(target_ref, SavedVariable(PositiveRef(ref))));
679  if (!insert_status.second) {
680  // Tricky: overwrite if the old value refer to a now unused variable.
681  const int candidate = insert_status.first->second.Get(this);
682  if (removed_variables_.contains(candidate)) {
683  insert_status.first->second = SavedVariable(PositiveRef(ref));
684  return true;
685  }
686  return false;
687  }
688  return true;
689 }
690 
691 bool PresolveContext::GetAbsRelation(int target_ref, int* ref) {
692  auto it = abs_relations_.find(target_ref);
693  if (it == abs_relations_.end()) return false;
694 
695  // Tricky: In some rare case the stored relation can refer to a deleted
696  // variable, so we need to ignore it.
697  //
698  // TODO(user): Incorporate this as part of SavedVariable/SavedLiteral so we
699  // make sure we never forget about this.
700  const int candidate = it->second.Get(this);
701  if (removed_variables_.contains(candidate)) {
702  abs_relations_.erase(it);
703  return false;
704  }
705  *ref = candidate;
706  return true;
707 }
708 
711 
714  // Note(user): This can happen is some corner cases where the affine
715  // relation where added before the variable became usable as Boolean. When
716  // this is the case, the domain will be of the form [x, x + 1] and should be
717  // later remapped to a Boolean variable.
718  return ref;
719  }
720 
721  // We made sure that the affine representative can always be used as a
722  // literal. However, if some variable are fixed, we might not have only
723  // (coeff=1 offset=0) or (coeff=-1 offset=1) and we might have something like
724  // (coeff=8 offset=0) which is only valid for both variable at zero...
725  //
726  // What is sure is that depending on the value, only one mapping can be valid
727  // because r.coeff can never be zero.
728  const bool positive_possible = (r.offset == 0 || r.coeff + r.offset == 1);
729  const bool negative_possible = (r.offset == 1 || r.coeff + r.offset == 0);
730  DCHECK_NE(positive_possible, negative_possible);
731  if (RefIsPositive(ref)) {
732  return positive_possible ? r.representative : NegatedRef(r.representative);
733  } else {
734  return positive_possible ? NegatedRef(r.representative) : r.representative;
735  }
736 }
737 
739  const AffineRelation::Relation r = var_equiv_relations_.Get(PositiveRef(ref));
740  CHECK_EQ(std::abs(r.coeff), 1);
741  CHECK_EQ(r.offset, 0);
742  return RefIsPositive(ref) == (r.coeff == 1) ? r.representative
744 }
745 
746 // This makes sure that the affine relation only uses one of the
747 // representative from the var_equiv_relations_.
749  AffineRelation::Relation r = affine_relations_.Get(PositiveRef(ref));
750  AffineRelation::Relation o = var_equiv_relations_.Get(r.representative);
752  if (o.coeff == -1) r.coeff = -r.coeff;
753  if (!RefIsPositive(ref)) {
754  r.coeff *= -1;
755  r.offset *= -1;
756  }
757  return r;
758 }
759 
760 std::string PresolveContext::RefDebugString(int ref) const {
761  return absl::StrCat(RefIsPositive(ref) ? "X" : "-X", PositiveRef(ref),
762  DomainOf(ref).ToString());
763 }
764 
765 std::string PresolveContext::AffineRelationDebugString(int ref) const {
767  return absl::StrCat(RefDebugString(ref), " = ", r.coeff, " * ",
768  RefDebugString(r.representative), " + ", r.offset);
769 }
770 
771 // Create the internal structure for any new variables in working_model.
773  for (int i = domains.size(); i < working_model->variables_size(); ++i) {
774  domains.emplace_back(ReadDomainFromProto(working_model->variables(i)));
775  if (domains.back().IsEmpty()) {
776  is_unsat = true;
777  return;
778  }
779  if (IsFixed(i)) ExploitFixedDomain(i);
780  }
781  modified_domains.Resize(domains.size());
782  var_to_constraints_.resize(domains.size());
783  var_to_num_linear1_.resize(domains.size());
784  var_to_ub_only_constraints.resize(domains.size());
785  var_to_lb_only_constraints.resize(domains.size());
786 }
787 
788 bool PresolveContext::RemapEncodingMaps() {
789  // TODO(user): for now, while the code works most of the time, it triggers
790  // weird side effect that causes some issues in some LNS presolve...
791  // We should continue the investigation before activating it.
792  //
793  // Note also that because all our encoding constraints are present in the
794  // model, they will be remapped, and the new mapping re-added again. So while
795  // the current code might not be efficient, it should eventually reach the
796  // same effect.
797  encoding_remap_queue_.clear();
798 
799  // Note that InsertVarValueEncodingInternal() will potentially add new entry
800  // to the encoding_ map, but for a different variables. So this code relies on
801  // the fact that the var_map shouldn't change content nor address of the
802  // "var_map" below while we iterate on them.
803  for (const int var : encoding_remap_queue_) {
806  if (r.representative == var) return true;
807  int num_remapping = 0;
808 
809  // Encoding.
810  {
811  const absl::flat_hash_map<int64_t, SavedLiteral>& var_map =
812  encoding_[var];
813  for (const auto& entry : var_map) {
814  const int lit = entry.second.Get(this);
815  if (removed_variables_.contains(PositiveRef(lit))) continue;
816  if ((entry.first - r.offset) % r.coeff != 0) continue;
817  const int64_t rep_value = (entry.first - r.offset) / r.coeff;
818  ++num_remapping;
819  InsertVarValueEncodingInternal(lit, r.representative, rep_value,
820  /*add_constraints=*/false);
821  if (is_unsat) return false;
822  }
823  encoding_.erase(var);
824  }
825 
826  // Eq half encoding.
827  {
828  const absl::flat_hash_map<int64_t, absl::flat_hash_set<int>>& var_map =
829  eq_half_encoding_[var];
830  for (const auto& entry : var_map) {
831  if ((entry.first - r.offset) % r.coeff != 0) continue;
832  const int64_t rep_value = (entry.first - r.offset) / r.coeff;
833  for (int literal : entry.second) {
834  ++num_remapping;
835  InsertHalfVarValueEncoding(GetLiteralRepresentative(literal),
836  r.representative, rep_value,
837  /*imply_eq=*/true);
838  if (is_unsat) return false;
839  }
840  }
841  eq_half_encoding_.erase(var);
842  }
843 
844  // Neq half encoding.
845  {
846  const absl::flat_hash_map<int64_t, absl::flat_hash_set<int>>& var_map =
847  neq_half_encoding_[var];
848  for (const auto& entry : var_map) {
849  if ((entry.first - r.offset) % r.coeff != 0) continue;
850  const int64_t rep_value = (entry.first - r.offset) / r.coeff;
851  for (int literal : entry.second) {
852  ++num_remapping;
853  InsertHalfVarValueEncoding(GetLiteralRepresentative(literal),
854  r.representative, rep_value,
855  /*imply_eq=*/false);
856  if (is_unsat) return false;
857  }
858  }
859  neq_half_encoding_.erase(var);
860  }
861 
862  if (num_remapping > 0) {
863  VLOG(1) << "Remapped " << num_remapping << " encodings due to " << var
864  << " -> " << r.representative << ".";
865  }
866  }
867  encoding_remap_queue_.clear();
868  return !is_unsat;
869 }
870 
873  CHECK_EQ(DomainOf(var).Size(), 2);
874  const int64_t var_min = MinOf(var);
875  const int64_t var_max = MaxOf(var);
876 
877  if (is_unsat) return;
878 
879  absl::flat_hash_map<int64_t, SavedLiteral>& var_map = encoding_[var];
880 
881  // Find encoding for min if present.
882  auto min_it = var_map.find(var_min);
883  if (min_it != var_map.end()) {
884  const int old_var = PositiveRef(min_it->second.Get(this));
885  if (removed_variables_.contains(old_var)) {
886  var_map.erase(min_it);
887  min_it = var_map.end();
888  }
889  }
890 
891  // Find encoding for max if present.
892  auto max_it = var_map.find(var_max);
893  if (max_it != var_map.end()) {
894  const int old_var = PositiveRef(max_it->second.Get(this));
895  if (removed_variables_.contains(old_var)) {
896  var_map.erase(max_it);
897  max_it = var_map.end();
898  }
899  }
900 
901  // Insert missing encoding.
902  int min_literal;
903  int max_literal;
904  if (min_it != var_map.end() && max_it != var_map.end()) {
905  min_literal = min_it->second.Get(this);
906  max_literal = max_it->second.Get(this);
907  if (min_literal != NegatedRef(max_literal)) {
908  UpdateRuleStats("variables with 2 values: merge encoding literals");
909  StoreBooleanEqualityRelation(min_literal, NegatedRef(max_literal));
910  if (is_unsat) return;
911  }
912  min_literal = GetLiteralRepresentative(min_literal);
913  max_literal = GetLiteralRepresentative(max_literal);
914  if (!IsFixed(min_literal)) CHECK_EQ(min_literal, NegatedRef(max_literal));
915  } else if (min_it != var_map.end() && max_it == var_map.end()) {
916  UpdateRuleStats("variables with 2 values: register other encoding");
917  min_literal = min_it->second.Get(this);
918  max_literal = NegatedRef(min_literal);
919  var_map[var_max] = SavedLiteral(max_literal);
920  } else if (min_it == var_map.end() && max_it != var_map.end()) {
921  UpdateRuleStats("variables with 2 values: register other encoding");
922  max_literal = max_it->second.Get(this);
923  min_literal = NegatedRef(max_literal);
924  var_map[var_min] = SavedLiteral(min_literal);
925  } else {
926  UpdateRuleStats("variables with 2 values: create encoding literal");
927  max_literal = NewBoolVar();
928  min_literal = NegatedRef(max_literal);
929  var_map[var_min] = SavedLiteral(min_literal);
930  var_map[var_max] = SavedLiteral(max_literal);
931  }
932 
933  if (IsFixed(min_literal) || IsFixed(max_literal)) {
934  CHECK(IsFixed(min_literal));
935  CHECK(IsFixed(max_literal));
936  UpdateRuleStats("variables with 2 values: fixed encoding");
937  if (LiteralIsTrue(min_literal)) {
938  return static_cast<void>(IntersectDomainWith(var, Domain(var_min)));
939  } else {
940  return static_cast<void>(IntersectDomainWith(var, Domain(var_max)));
941  }
942  }
943 
944  // Add affine relation.
945  if (GetAffineRelation(var).representative != PositiveRef(min_literal)) {
946  UpdateRuleStats("variables with 2 values: new affine relation");
947  if (RefIsPositive(max_literal)) {
949  var_max - var_min, var_min));
950  } else {
952  var_min - var_max, var_max));
953  }
954  }
955 }
956 
957 void PresolveContext::InsertVarValueEncodingInternal(int literal, int var,
958  int64_t value,
959  bool add_constraints) {
962  absl::flat_hash_map<int64_t, SavedLiteral>& var_map = encoding_[var];
963 
964  // Ticky and rare: I have only observed this on the LNS of
965  // radiation_m18_12_05_sat.fzn. The value was encoded, but maybe we never
966  // used the involved variables / constraints, so it was removed (with the
967  // encoding constraints) from the model already! We have to be careful.
968  const auto it = var_map.find(value);
969  if (it != var_map.end()) {
970  const int old_var = PositiveRef(it->second.Get(this));
971  if (removed_variables_.contains(old_var)) {
972  var_map.erase(it);
973  }
974  }
975 
976  const auto insert =
977  var_map.insert(std::make_pair(value, SavedLiteral(literal)));
978 
979  // If an encoding already exist, make the two Boolean equals.
980  if (!insert.second) {
981  const int previous_literal = insert.first->second.Get(this);
982  CHECK(!VariableWasRemoved(previous_literal));
983  if (literal != previous_literal) {
985  "variables: merge equivalent var value encoding literals");
986  StoreBooleanEqualityRelation(literal, previous_literal);
987  }
988  return;
989  }
990 
991  if (DomainOf(var).Size() == 2) {
993  } else {
994  VLOG(2) << "Insert lit(" << literal << ") <=> var(" << var
995  << ") == " << value;
996  eq_half_encoding_[var][value].insert(literal);
997  neq_half_encoding_[var][value].insert(NegatedRef(literal));
998  if (add_constraints) {
999  UpdateRuleStats("variables: add encoding constraint");
1000  AddImplyInDomain(literal, var, Domain(value));
1001  AddImplyInDomain(NegatedRef(literal), var, Domain(value).Complement());
1002  }
1003  }
1004 }
1005 
1006 bool PresolveContext::InsertHalfVarValueEncoding(int literal, int var,
1007  int64_t value, bool imply_eq) {
1008  if (is_unsat) return false;
1010 
1011  // Creates the linking sets on demand.
1012  // Insert the enforcement literal in the half encoding map.
1013  auto& direct_set =
1014  imply_eq ? eq_half_encoding_[var][value] : neq_half_encoding_[var][value];
1015  if (!direct_set.insert(literal).second) return false; // Already there.
1016 
1017  VLOG(2) << "Collect lit(" << literal << ") implies var(" << var
1018  << (imply_eq ? ") == " : ") != ") << value;
1019  UpdateRuleStats("variables: detect half reified value encoding");
1020 
1021  // Note(user): We don't expect a lot of literals in these sets, so doing
1022  // a scan should be okay.
1023  auto& other_set =
1024  imply_eq ? neq_half_encoding_[var][value] : eq_half_encoding_[var][value];
1025  for (const int other : other_set) {
1026  if (GetLiteralRepresentative(other) != NegatedRef(literal)) continue;
1027 
1028  UpdateRuleStats("variables: detect fully reified value encoding");
1029  const int imply_eq_literal = imply_eq ? literal : NegatedRef(literal);
1030  InsertVarValueEncodingInternal(imply_eq_literal, var, value,
1031  /*add_constraints=*/false);
1032  break;
1033  }
1034 
1035  return true;
1036 }
1037 
1038 bool PresolveContext::CanonicalizeEncoding(int* ref, int64_t* value) {
1039  const AffineRelation::Relation r = GetAffineRelation(*ref);
1040  if ((*value - r.offset) % r.coeff != 0) return false;
1041  *ref = r.representative;
1042  *value = (*value - r.offset) / r.coeff;
1043  return true;
1044 }
1045 
1047  int64_t value) {
1048  if (!RemapEncodingMaps()) return;
1049  if (!CanonicalizeEncoding(&ref, &value)) return;
1051  InsertVarValueEncodingInternal(literal, ref, value, /*add_constraints=*/true);
1052 }
1053 
1055  int64_t value) {
1056  if (!RemapEncodingMaps()) return false;
1057  if (!CanonicalizeEncoding(&var, &value)) return false;
1059  return InsertHalfVarValueEncoding(literal, var, value, /*imply_eq=*/true);
1060 }
1061 
1063  int64_t value) {
1064  if (!RemapEncodingMaps()) return false;
1065  if (!CanonicalizeEncoding(&var, &value)) return false;
1067  return InsertHalfVarValueEncoding(literal, var, value, /*imply_eq=*/false);
1068 }
1069 
1071  int* literal) {
1072  if (!RemapEncodingMaps()) return false;
1073  if (!CanonicalizeEncoding(&ref, &value)) return false;
1074  const absl::flat_hash_map<int64_t, SavedLiteral>& var_map = encoding_[ref];
1075  const auto it = var_map.find(value);
1076  if (it != var_map.end()) {
1077  if (literal != nullptr) {
1078  *literal = it->second.Get(this);
1079  }
1080  return true;
1081  }
1082  return false;
1083 }
1084 
1086  if (!RemapEncodingMaps()) return GetOrCreateConstantVar(0);
1087  if (!CanonicalizeEncoding(&ref, &value)) return GetOrCreateConstantVar(0);
1088 
1089  // Positive after CanonicalizeEncoding().
1090  const int var = ref;
1091 
1092  // Returns the false literal if the value is not in the domain.
1093  if (!domains[var].Contains(value)) {
1094  return GetOrCreateConstantVar(0);
1095  }
1096 
1097  // Returns the associated literal if already present.
1098  absl::flat_hash_map<int64_t, SavedLiteral>& var_map = encoding_[var];
1099  auto it = var_map.find(value);
1100  if (it != var_map.end()) {
1101  return it->second.Get(this);
1102  }
1103 
1104  // Special case for fixed domains.
1105  if (domains[var].Size() == 1) {
1106  const int true_literal = GetOrCreateConstantVar(1);
1107  var_map[value] = SavedLiteral(true_literal);
1108  return true_literal;
1109  }
1110 
1111  // Special case for domains of size 2.
1112  const int64_t var_min = MinOf(var);
1113  const int64_t var_max = MaxOf(var);
1114  if (domains[var].Size() == 2) {
1115  // Checks if the other value is already encoded.
1116  const int64_t other_value = value == var_min ? var_max : var_min;
1117  auto other_it = var_map.find(other_value);
1118  if (other_it != var_map.end()) {
1119  // Update the encoding map. The domain could have been reduced to size
1120  // two after the creation of the first literal.
1121  const int literal = NegatedRef(other_it->second.Get(this));
1122  var_map[value] = SavedLiteral(literal);
1123  return literal;
1124  }
1125 
1126  if (var_min == 0 && var_max == 1) {
1128  var_map[1] = SavedLiteral(representative);
1129  var_map[0] = SavedLiteral(NegatedRef(representative));
1130  return value == 1 ? representative : NegatedRef(representative);
1131  } else {
1132  const int literal = NewBoolVar();
1133  InsertVarValueEncoding(literal, var, var_max);
1135  return value == var_max ? representative : NegatedRef(representative);
1136  }
1137  }
1138 
1139  const int literal = NewBoolVar();
1142 }
1143 
1145  const CpObjectiveProto& obj = working_model->objective();
1146 
1147  objective_offset_ = obj.offset();
1148  objective_scaling_factor_ = obj.scaling_factor();
1149  if (objective_scaling_factor_ == 0.0) {
1150  objective_scaling_factor_ = 1.0;
1151  }
1152  if (!obj.domain().empty()) {
1153  // We might relax this in CanonicalizeObjective() when we will compute
1154  // the possible objective domain from the domains of the variables.
1155  objective_domain_is_constraining_ = true;
1156  objective_domain_ = ReadDomainFromProto(obj);
1157  } else {
1158  objective_domain_is_constraining_ = false;
1159  objective_domain_ = Domain::AllValues();
1160  }
1161 
1162  // This is an upper bound of the higher magnitude that can be reach by
1163  // summing an objective partial sum. Because of the model validation, this
1164  // shouldn't overflow, and we make sure it stays this way.
1165  objective_overflow_detection_ = 0;
1166 
1167  objective_map_.clear();
1168  for (int i = 0; i < obj.vars_size(); ++i) {
1169  const int ref = obj.vars(i);
1170  int64_t coeff = obj.coeffs(i);
1171  if (!RefIsPositive(ref)) coeff = -coeff;
1172  int var = PositiveRef(ref);
1173 
1174  objective_overflow_detection_ +=
1175  std::abs(coeff) * std::max(std::abs(MinOf(var)), std::abs(MaxOf(var)));
1176 
1177  objective_map_[var] += coeff;
1178  if (objective_map_[var] == 0) {
1179  objective_map_.erase(var);
1180  var_to_constraints_[var].erase(kObjectiveConstraint);
1181  } else {
1182  var_to_constraints_[var].insert(kObjectiveConstraint);
1183  }
1184  }
1185 }
1186 
1188  int64_t offset_change = 0;
1189 
1190  // We replace each entry by its affine representative.
1191  // Note that the non-deterministic loop is fine, but because we iterate
1192  // one the map while modifying it, it is safer to do a copy rather than to
1193  // try to handle that in one pass.
1194  tmp_entries_.clear();
1195  for (const auto& entry : objective_map_) {
1196  tmp_entries_.push_back(entry);
1197  }
1198 
1199  // TODO(user): This is a bit duplicated with the presolve linear code.
1200  // We also do not propagate back any domain restriction from the objective to
1201  // the variables if any.
1202  for (const auto& entry : tmp_entries_) {
1203  const int var = entry.first;
1204  const auto it = objective_map_.find(var);
1205  if (it == objective_map_.end()) continue;
1206  const int64_t coeff = it->second;
1207 
1208  // If a variable only appear in objective, we can fix it!
1209  // Note that we don't care if it was in affine relation, because if none
1210  // of the relations are left, then we can still fix it.
1211  if (!keep_all_feasible_solutions && !objective_domain_is_constraining_ &&
1213  var_to_constraints_[var].size() == 1 &&
1214  var_to_constraints_[var].contains(kObjectiveConstraint)) {
1215  UpdateRuleStats("objective: variable not used elsewhere");
1216  if (coeff > 0) {
1217  if (!IntersectDomainWith(var, Domain(MinOf(var)))) {
1218  return false;
1219  }
1220  } else {
1221  if (!IntersectDomainWith(var, Domain(MaxOf(var)))) {
1222  return false;
1223  }
1224  }
1225  }
1226 
1227  if (IsFixed(var)) {
1228  offset_change += coeff * MinOf(var);
1229  var_to_constraints_[var].erase(kObjectiveConstraint);
1230  objective_map_.erase(var);
1231  continue;
1232  }
1233 
1235  if (r.representative == var) continue;
1236 
1237  objective_map_.erase(var);
1238  var_to_constraints_[var].erase(kObjectiveConstraint);
1239 
1240  // Do the substitution.
1241  offset_change += coeff * r.offset;
1242  const int64_t new_coeff = objective_map_[r.representative] +=
1243  coeff * r.coeff;
1244 
1245  // Process new term.
1246  if (new_coeff == 0) {
1247  objective_map_.erase(r.representative);
1248  var_to_constraints_[r.representative].erase(kObjectiveConstraint);
1249  } else {
1250  var_to_constraints_[r.representative].insert(kObjectiveConstraint);
1251  if (IsFixed(r.representative)) {
1252  offset_change += new_coeff * MinOf(r.representative);
1253  var_to_constraints_[r.representative].erase(kObjectiveConstraint);
1254  objective_map_.erase(r.representative);
1255  }
1256  }
1257  }
1258 
1259  Domain implied_domain(0);
1260  int64_t gcd(0);
1261 
1262  // We need to sort the entries to be deterministic.
1263  tmp_entries_.clear();
1264  for (const auto& entry : objective_map_) {
1265  tmp_entries_.push_back(entry);
1266  }
1267  std::sort(tmp_entries_.begin(), tmp_entries_.end());
1268  for (const auto& entry : tmp_entries_) {
1269  const int var = entry.first;
1270  const int64_t coeff = entry.second;
1271  gcd = MathUtil::GCD64(gcd, std::abs(coeff));
1272  implied_domain =
1273  implied_domain.AdditionWith(DomainOf(var).MultiplicationBy(coeff))
1274  .RelaxIfTooComplex();
1275  }
1276 
1277  // This is the new domain.
1278  // Note that the domain never include the offset.
1279  objective_domain_ = objective_domain_.AdditionWith(Domain(-offset_change))
1280  .IntersectionWith(implied_domain);
1281  objective_domain_ =
1282  objective_domain_.SimplifyUsingImpliedDomain(implied_domain);
1283 
1284  // Updat the offset.
1285  objective_offset_ += offset_change;
1286 
1287  // Maybe divide by GCD.
1288  if (gcd > 1) {
1289  for (auto& entry : objective_map_) {
1290  entry.second /= gcd;
1291  }
1292  objective_domain_ = objective_domain_.InverseMultiplicationBy(gcd);
1293  objective_offset_ /= static_cast<double>(gcd);
1294  objective_scaling_factor_ *= static_cast<double>(gcd);
1295  }
1296 
1297  if (objective_domain_.IsEmpty()) return false;
1298 
1299  // Detect if the objective domain do not limit the "optimal" objective value.
1300  // If this is true, then we can apply any reduction that reduce the objective
1301  // value without any issues.
1302  objective_domain_is_constraining_ =
1303  !implied_domain
1305  objective_domain_.Max()))
1306  .IsIncludedIn(objective_domain_);
1307  return true;
1308 }
1309 
1311  int var_in_equality, int64_t coeff_in_equality,
1312  const ConstraintProto& equality, std::vector<int>* new_vars_in_objective) {
1313  CHECK(equality.enforcement_literal().empty());
1314  CHECK(RefIsPositive(var_in_equality));
1315 
1316  if (new_vars_in_objective != nullptr) new_vars_in_objective->clear();
1317 
1318  // We can only "easily" substitute if the objective coefficient is a multiple
1319  // of the one in the constraint.
1320  const int64_t coeff_in_objective =
1321  gtl::FindOrDie(objective_map_, var_in_equality);
1322  CHECK_NE(coeff_in_equality, 0);
1323  CHECK_EQ(coeff_in_objective % coeff_in_equality, 0);
1324  const int64_t multiplier = coeff_in_objective / coeff_in_equality;
1325 
1326  // Abort if the new objective seems to violate our overflow preconditions.
1327  int64_t change = 0;
1328  for (int i = 0; i < equality.linear().vars().size(); ++i) {
1329  int var = equality.linear().vars(i);
1330  if (PositiveRef(var) == var_in_equality) continue;
1331  int64_t coeff = equality.linear().coeffs(i);
1332  change +=
1333  std::abs(coeff) * std::max(std::abs(MinOf(var)), std::abs(MaxOf(var)));
1334  }
1335  const int64_t new_value =
1336  CapAdd(CapProd(std::abs(multiplier), change),
1337  objective_overflow_detection_ -
1338  std::abs(coeff_in_equality) *
1339  std::max(std::abs(MinOf(var_in_equality)),
1340  std::abs(MaxOf(var_in_equality))));
1341  if (new_value == std::numeric_limits<int64_t>::max()) return false;
1342  objective_overflow_detection_ = new_value;
1343 
1344  for (int i = 0; i < equality.linear().vars().size(); ++i) {
1345  int var = equality.linear().vars(i);
1346  int64_t coeff = equality.linear().coeffs(i);
1347  if (!RefIsPositive(var)) {
1348  var = NegatedRef(var);
1349  coeff = -coeff;
1350  }
1351  if (var == var_in_equality) continue;
1352 
1353  int64_t& map_ref = objective_map_[var];
1354  if (map_ref == 0 && new_vars_in_objective != nullptr) {
1355  new_vars_in_objective->push_back(var);
1356  }
1357  map_ref -= coeff * multiplier;
1358 
1359  if (map_ref == 0) {
1360  objective_map_.erase(var);
1361  var_to_constraints_[var].erase(kObjectiveConstraint);
1362  } else {
1363  var_to_constraints_[var].insert(kObjectiveConstraint);
1364  }
1365  }
1366 
1367  objective_map_.erase(var_in_equality);
1368  var_to_constraints_[var_in_equality].erase(kObjectiveConstraint);
1369 
1370  // Deal with the offset.
1371  Domain offset = ReadDomainFromProto(equality.linear());
1372  DCHECK_EQ(offset.Min(), offset.Max());
1373  bool exact = true;
1374  offset = offset.MultiplicationBy(multiplier, &exact);
1375  CHECK(exact);
1376  CHECK(!offset.IsEmpty());
1377 
1378  // Tricky: The objective domain is without the offset, so we need to shift it.
1379  objective_offset_ += static_cast<double>(offset.Min());
1380  objective_domain_ = objective_domain_.AdditionWith(Domain(-offset.Min()));
1381 
1382  // Because we can assume that the constraint we used was constraining
1383  // (otherwise it would have been removed), the objective domain should be now
1384  // constraining.
1385  objective_domain_is_constraining_ = true;
1386 
1387  if (objective_domain_.IsEmpty()) {
1388  return NotifyThatModelIsUnsat();
1389  }
1390  return true;
1391 }
1392 
1394  // We need to sort the entries to be deterministic.
1395  std::vector<std::pair<int, int64_t>> entries;
1396  for (const auto& entry : objective_map_) {
1397  entries.push_back(entry);
1398  }
1399  std::sort(entries.begin(), entries.end());
1400 
1401  CpObjectiveProto* mutable_obj = working_model->mutable_objective();
1402  mutable_obj->set_offset(objective_offset_);
1403  mutable_obj->set_scaling_factor(objective_scaling_factor_);
1404  FillDomainInProto(objective_domain_, mutable_obj);
1405  mutable_obj->clear_vars();
1406  mutable_obj->clear_coeffs();
1407  for (const auto& entry : entries) {
1408  mutable_obj->add_vars(entry.first);
1409  mutable_obj->add_coeffs(entry.second);
1410  }
1411 }
1412 
1414  int active_i,
1415  int active_j) {
1416  // Sort the active literals.
1417  if (active_j < active_i) std::swap(active_i, active_j);
1418 
1419  const std::tuple<int, int, int, int> key =
1420  std::make_tuple(time_i, time_j, active_i, active_j);
1421  const auto& it = reified_precedences_cache_.find(key);
1422  if (it != reified_precedences_cache_.end()) return it->second;
1423 
1424  const int result = NewBoolVar();
1425  reified_precedences_cache_[key] = result;
1426 
1427  // result => (time_i <= time_j) && active_i && active_j.
1428  ConstraintProto* const lesseq = working_model->add_constraints();
1429  lesseq->add_enforcement_literal(result);
1430  lesseq->mutable_linear()->add_vars(time_i);
1431  lesseq->mutable_linear()->add_vars(time_j);
1432  lesseq->mutable_linear()->add_coeffs(-1);
1433  lesseq->mutable_linear()->add_coeffs(1);
1434  lesseq->mutable_linear()->add_domain(0);
1435  lesseq->mutable_linear()->add_domain(std::numeric_limits<int64_t>::max());
1436  if (!LiteralIsTrue(active_i)) {
1437  AddImplication(result, active_i);
1438  }
1439  if (!LiteralIsTrue(active_j)) {
1440  AddImplication(result, active_j);
1441  }
1442 
1443  // Not(result) && active_i && active_j => (time_i > time_j)
1444  ConstraintProto* const greater = working_model->add_constraints();
1445  greater->mutable_linear()->add_vars(time_i);
1446  greater->mutable_linear()->add_vars(time_j);
1447  greater->mutable_linear()->add_coeffs(-1);
1448  greater->mutable_linear()->add_coeffs(1);
1449  greater->mutable_linear()->add_domain(std::numeric_limits<int64_t>::min());
1450  greater->mutable_linear()->add_domain(-1);
1451 
1452  // Manages enforcement literal.
1453  greater->add_enforcement_literal(NegatedRef(result));
1454  greater->add_enforcement_literal(active_i);
1455  greater->add_enforcement_literal(active_j);
1456 
1457  // This is redundant but should improves performance.
1458  //
1459  // If GetOrCreateReifiedPrecedenceLiteral(time_j, time_i, active_j, active_j)
1460  // (the reverse precedence) has been called too, then we can link the two
1461  // precedence literals, and the two active literals together.
1462  const auto& rev_it = reified_precedences_cache_.find(
1463  std::make_tuple(time_j, time_i, active_i, active_j));
1464  if (rev_it != reified_precedences_cache_.end()) {
1465  auto* const bool_or = working_model->add_constraints()->mutable_bool_or();
1466  bool_or->add_literals(result);
1467  bool_or->add_literals(rev_it->second);
1468  bool_or->add_literals(NegatedRef(active_i));
1469  bool_or->add_literals(NegatedRef(active_j));
1470  }
1471 
1472  return result;
1473 }
1474 
1476  reified_precedences_cache_.clear();
1477 }
1478 
1479 } // namespace sat
1480 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:498
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:894
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:705
#define CHECK_NE(val1, val2)
Definition: base/logging.h:706
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:896
#define LOG(severity)
Definition: base/logging.h:423
#define DCHECK(condition)
Definition: base/logging.h:892
#define CHECK_LE(val1, val2)
Definition: base/logging.h:707
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:893
#define VLOG(verboselevel)
Definition: base/logging.h:986
bool TryAdd(int x, int y, int64_t coeff, int64_t offset)
We call domain any subset of Int64 = [kint64min, kint64max].
static Domain AllValues()
Returns the full domain Int64.
Domain InverseMultiplicationBy(const int64_t coeff) const
Returns {x ∈ Int64, ∃ e ∈ D, x * coeff = e}.
Domain Negation() const
Returns {x ∈ Int64, ∃ e ∈ D, x = -e}.
bool IsIncludedIn(const Domain &domain) const
Returns true iff D is included in the given domain.
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}.
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:804
void Resize(IntegerType size)
Definition: bitset.h:790
bool StoreAbsRelation(int target_ref, int ref)
void InsertVarValueEncoding(int literal, int ref, int64_t value)
ABSL_MUST_USE_RESULT bool IntersectDomainWith(int ref, const Domain &domain, bool *domain_modified=nullptr)
std::vector< absl::flat_hash_set< int > > var_to_lb_only_constraints
bool StoreLiteralImpliesVarNEqValue(int literal, int var, int64_t value)
bool VariableWithCostIsUniqueAndRemovable(int ref) const
ABSL_MUST_USE_RESULT bool SetLiteralToTrue(int lit)
bool StoreAffineRelation(int ref_x, int ref_y, int64_t coeff, int64_t offset)
std::vector< absl::flat_hash_set< int > > var_to_ub_only_constraints
bool SubstituteVariableInObjective(int var_in_equality, int64_t coeff_in_equality, const ConstraintProto &equality, std::vector< int > *new_vars_in_objective=nullptr)
int GetOrCreateVarValueEncoding(int ref, int64_t value)
ABSL_MUST_USE_RESULT bool NotifyThatModelIsUnsat(const std::string &message="")
std::string AffineRelationDebugString(int ref) const
absl::flat_hash_map< std::string, int > stats_by_rule_name
void StoreBooleanEqualityRelation(int ref_a, int ref_b)
bool HasVarValueEncoding(int ref, int64_t value, int *literal=nullptr)
bool DomainContains(int ref, int64_t value) const
void UpdateRuleStats(const std::string &name, int num_times=1)
ABSL_MUST_USE_RESULT bool CanonicalizeObjective()
AffineRelation::Relation GetAffineRelation(int ref) const
ABSL_MUST_USE_RESULT bool SetLiteralToFalse(int lit)
int GetOrCreateReifiedPrecedenceLiteral(int time_i, int time_j, int active_i, int active_j)
void AddImplyInDomain(int b, int x, const Domain &domain)
bool GetAbsRelation(int target_ref, int *ref)
bool StoreLiteralImpliesVarEqValue(int literal, int var, int64_t value)
int Get(PresolveContext *context) const
int Get(PresolveContext *context) const
int64_t b
int64_t a
const std::string name
const Constraint * ct
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GurobiMPCallbackContext * context
const int INFO
Definition: log_severity.h:31
bool ContainsKey(const Collection &collection, const Key &key)
Definition: map_util.h:200
const Collection::value_type::second_type & FindOrDie(const Collection &collection, const typename Collection::value_type::first_type &key)
Definition: map_util.h:206
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:263
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)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
constexpr int kObjectiveConstraint
Collection of objects used to extend the Constraint Solver library.
int64_t CapAdd(int64_t x, int64_t y)
const absl::string_view ToString(MPSolver::OptimizationProblemType optimization_problem_type)
int64_t CapProd(int64_t x, int64_t y)
std::string ProtobufDebugString(const P &message)
Literal literal
Definition: optimization.cc:85
ColIndex representative
#define SOLVER_LOG(logger,...)
Definition: util/logging.h:63
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:41