OR-Tools  8.2
cp_model_presolve.cc
Go to the documentation of this file.
1 // Copyright 2010-2018 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 <sys/stat.h>
17 
18 #include <algorithm>
19 #include <cstdlib>
20 #include <deque>
21 #include <map>
22 #include <memory>
23 #include <numeric>
24 #include <set>
25 #include <string>
26 #include <utility>
27 #include <vector>
28 
29 #include "absl/container/flat_hash_map.h"
30 #include "absl/container/flat_hash_set.h"
31 #include "absl/random/random.h"
32 #include "absl/strings/str_join.h"
33 #include "ortools/base/hash.h"
35 #include "ortools/base/logging.h"
36 #include "ortools/base/map_util.h"
37 #include "ortools/base/mathutil.h"
38 #include "ortools/base/stl_util.h"
40 #include "ortools/sat/circuit.h"
49 #include "ortools/sat/probing.h"
50 #include "ortools/sat/sat_base.h"
54 
55 namespace operations_research {
56 namespace sat {
57 
58 bool CpModelPresolver::RemoveConstraint(ConstraintProto* ct) {
59  ct->Clear();
60  return true;
61 }
62 
64  // Remove all empty constraints. Note that we need to remap the interval
65  // references.
66  std::vector<int> interval_mapping(context_->working_model->constraints_size(),
67  -1);
68  int new_num_constraints = 0;
69  const int old_num_non_empty_constraints =
70  context_->working_model->constraints_size();
71  for (int c = 0; c < old_num_non_empty_constraints; ++c) {
72  const auto type = context_->working_model->constraints(c).constraint_case();
73  if (type == ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) continue;
74  if (type == ConstraintProto::ConstraintCase::kInterval) {
75  interval_mapping[c] = new_num_constraints;
76  }
77  context_->working_model->mutable_constraints(new_num_constraints++)
78  ->Swap(context_->working_model->mutable_constraints(c));
79  }
80  context_->working_model->mutable_constraints()->DeleteSubrange(
81  new_num_constraints, old_num_non_empty_constraints - new_num_constraints);
82  for (ConstraintProto& ct_ref :
83  *context_->working_model->mutable_constraints()) {
85  [&interval_mapping](int* ref) {
86  *ref = interval_mapping[*ref];
87  CHECK_NE(-1, *ref);
88  },
89  &ct_ref);
90  }
91 }
92 
93 bool CpModelPresolver::PresolveEnforcementLiteral(ConstraintProto* ct) {
94  if (context_->ModelIsUnsat()) return false;
95  if (!HasEnforcementLiteral(*ct)) return false;
96 
97  int new_size = 0;
98  const int old_size = ct->enforcement_literal().size();
99  for (const int literal : ct->enforcement_literal()) {
100  if (context_->LiteralIsTrue(literal)) {
101  // We can remove a literal at true.
102  context_->UpdateRuleStats("true enforcement literal");
103  continue;
104  }
105 
106  if (context_->LiteralIsFalse(literal)) {
107  context_->UpdateRuleStats("false enforcement literal");
108  return RemoveConstraint(ct);
109  }
110 
111  if (context_->VariableIsUniqueAndRemovable(literal)) {
112  // We can simply set it to false and ignore the constraint in this case.
113  context_->UpdateRuleStats("enforcement literal not used");
114  CHECK(context_->SetLiteralToFalse(literal));
115  return RemoveConstraint(ct);
116  }
117 
118  // If the literal only appear in the objective, we might be able to fix it
119  // to false. TODO(user): generalize if the literal always appear with the
120  // same polarity.
122  const int64 obj_coeff =
124  if (RefIsPositive(literal) == (obj_coeff > 0)) {
125  // It is just more advantageous to set it to false!
126  context_->UpdateRuleStats("enforcement literal with unique direction");
127  CHECK(context_->SetLiteralToFalse(literal));
128  return RemoveConstraint(ct);
129  }
130  }
131 
132  ct->set_enforcement_literal(new_size++, literal);
133  }
134  ct->mutable_enforcement_literal()->Truncate(new_size);
135  return new_size != old_size;
136 }
137 
138 bool CpModelPresolver::PresolveBoolXor(ConstraintProto* ct) {
139  if (context_->ModelIsUnsat()) return false;
140  if (HasEnforcementLiteral(*ct)) return false;
141 
142  int new_size = 0;
143  bool changed = false;
144  int num_true_literals = 0;
145  int true_literal = kint32min;
146  for (const int literal : ct->bool_xor().literals()) {
147  // TODO(user): More generally, if a variable appear in only bool xor
148  // constraints, we can simply eliminate it using linear algebra on Z/2Z.
149  // This should solve in polynomial time the parity-learning*.fzn problems
150  // for instance. This seems low priority, but it is also easy to do. Even
151  // better would be to have a dedicated propagator with all bool_xor
152  // constraints that do the necessary linear algebra.
153  if (context_->VariableIsUniqueAndRemovable(literal)) {
154  context_->UpdateRuleStats("TODO bool_xor: remove constraint");
155  }
156 
157  if (context_->LiteralIsFalse(literal)) {
158  context_->UpdateRuleStats("bool_xor: remove false literal");
159  changed = true;
160  continue;
161  } else if (context_->LiteralIsTrue(literal)) {
162  true_literal = literal; // Keep if we need to put one back.
163  num_true_literals++;
164  continue;
165  }
166 
167  ct->mutable_bool_xor()->set_literals(new_size++, literal);
168  }
169  if (new_size == 1) {
170  context_->UpdateRuleStats("TODO bool_xor: one active literal");
171  } else if (new_size == 2) {
172  context_->UpdateRuleStats("TODO bool_xor: two active literals");
173  }
174  if (num_true_literals % 2 == 1) {
175  CHECK_NE(true_literal, kint32min);
176  ct->mutable_bool_xor()->set_literals(new_size++, true_literal);
177  }
178  if (num_true_literals > 1) {
179  context_->UpdateRuleStats("bool_xor: remove even number of true literals");
180  changed = true;
181  }
182  ct->mutable_bool_xor()->mutable_literals()->Truncate(new_size);
183  return changed;
184 }
185 
186 bool CpModelPresolver::PresolveBoolOr(ConstraintProto* ct) {
187  if (context_->ModelIsUnsat()) return false;
188 
189  // Move the enforcement literal inside the clause if any. Note that we do not
190  // mark this as a change since the literal in the constraint are the same.
191  if (HasEnforcementLiteral(*ct)) {
192  context_->UpdateRuleStats("bool_or: removed enforcement literal");
193  for (const int literal : ct->enforcement_literal()) {
194  ct->mutable_bool_or()->add_literals(NegatedRef(literal));
195  }
196  ct->clear_enforcement_literal();
197  }
198 
199  // Inspects the literals and deal with fixed ones.
200  bool changed = false;
201  context_->tmp_literals.clear();
202  context_->tmp_literal_set.clear();
203  for (const int literal : ct->bool_or().literals()) {
204  if (context_->LiteralIsFalse(literal)) {
205  changed = true;
206  continue;
207  }
208  if (context_->LiteralIsTrue(literal)) {
209  context_->UpdateRuleStats("bool_or: always true");
210  return RemoveConstraint(ct);
211  }
212  // We can just set the variable to true in this case since it is not
213  // used in any other constraint (note that we artificially bump the
214  // objective var usage by 1).
215  if (context_->VariableIsUniqueAndRemovable(literal)) {
216  context_->UpdateRuleStats("bool_or: singleton");
217  if (!context_->SetLiteralToTrue(literal)) return true;
218  return RemoveConstraint(ct);
219  }
220  if (context_->tmp_literal_set.contains(NegatedRef(literal))) {
221  context_->UpdateRuleStats("bool_or: always true");
222  return RemoveConstraint(ct);
223  }
224 
225  if (context_->tmp_literal_set.contains(literal)) {
226  changed = true;
227  } else {
228  context_->tmp_literal_set.insert(literal);
229  context_->tmp_literals.push_back(literal);
230  }
231  }
232  context_->tmp_literal_set.clear();
233 
234  if (context_->tmp_literals.empty()) {
235  context_->UpdateRuleStats("bool_or: empty");
236  return context_->NotifyThatModelIsUnsat();
237  }
238  if (context_->tmp_literals.size() == 1) {
239  context_->UpdateRuleStats("bool_or: only one literal");
240  if (!context_->SetLiteralToTrue(context_->tmp_literals[0])) return true;
241  return RemoveConstraint(ct);
242  }
243  if (context_->tmp_literals.size() == 2) {
244  // For consistency, we move all "implication" into half-reified bool_and.
245  // TODO(user): merge by enforcement literal and detect implication cycles.
246  context_->UpdateRuleStats("bool_or: implications");
247  ct->add_enforcement_literal(NegatedRef(context_->tmp_literals[0]));
248  ct->mutable_bool_and()->add_literals(context_->tmp_literals[1]);
249  return changed;
250  }
251 
252  if (changed) {
253  context_->UpdateRuleStats("bool_or: fixed literals");
254  ct->mutable_bool_or()->mutable_literals()->Clear();
255  for (const int lit : context_->tmp_literals) {
256  ct->mutable_bool_or()->add_literals(lit);
257  }
258  }
259  return changed;
260 }
261 
262 ABSL_MUST_USE_RESULT bool CpModelPresolver::MarkConstraintAsFalse(
263  ConstraintProto* ct) {
264  if (HasEnforcementLiteral(*ct)) {
265  // Change the constraint to a bool_or.
266  ct->mutable_bool_or()->clear_literals();
267  for (const int lit : ct->enforcement_literal()) {
268  ct->mutable_bool_or()->add_literals(NegatedRef(lit));
269  }
270  ct->clear_enforcement_literal();
271  PresolveBoolOr(ct);
272  return true;
273  } else {
274  return context_->NotifyThatModelIsUnsat();
275  }
276 }
277 
278 bool CpModelPresolver::PresolveBoolAnd(ConstraintProto* ct) {
279  if (context_->ModelIsUnsat()) return false;
280 
281  if (!HasEnforcementLiteral(*ct)) {
282  context_->UpdateRuleStats("bool_and: non-reified.");
283  for (const int literal : ct->bool_and().literals()) {
284  if (!context_->SetLiteralToTrue(literal)) return true;
285  }
286  return RemoveConstraint(ct);
287  }
288 
289  bool changed = false;
290  context_->tmp_literals.clear();
291  for (const int literal : ct->bool_and().literals()) {
292  if (context_->LiteralIsFalse(literal)) {
293  context_->UpdateRuleStats("bool_and: always false");
294  return MarkConstraintAsFalse(ct);
295  }
296  if (context_->LiteralIsTrue(literal)) {
297  changed = true;
298  continue;
299  }
300  if (context_->VariableIsUniqueAndRemovable(literal)) {
301  changed = true;
302  if (!context_->SetLiteralToTrue(literal)) return true;
303  continue;
304  }
305  context_->tmp_literals.push_back(literal);
306  }
307 
308  // Note that this is not the same behavior as a bool_or:
309  // - bool_or means "at least one", so it is false if empty.
310  // - bool_and means "all literals inside true", so it is true if empty.
311  if (context_->tmp_literals.empty()) return RemoveConstraint(ct);
312 
313  if (changed) {
314  ct->mutable_bool_and()->mutable_literals()->Clear();
315  for (const int lit : context_->tmp_literals) {
316  ct->mutable_bool_and()->add_literals(lit);
317  }
318  context_->UpdateRuleStats("bool_and: fixed literals");
319  }
320 
321  // If a variable can move freely in one direction except for this constraint,
322  // we can make it an equality.
323  //
324  // TODO(user): also consider literal on the other side of the =>.
325  if (ct->enforcement_literal().size() == 1 &&
326  ct->bool_and().literals().size() == 1) {
327  const int enforcement = ct->enforcement_literal(0);
328  if (context_->VariableWithCostIsUniqueAndRemovable(enforcement)) {
329  int var = PositiveRef(enforcement);
330  int64 obj_coeff = gtl::FindOrDie(context_->ObjectiveMap(), var);
331  if (!RefIsPositive(enforcement)) obj_coeff = -obj_coeff;
332 
333  // The other case where the constraint is redundant is treated elsewhere.
334  if (obj_coeff < 0) {
335  context_->UpdateRuleStats("bool_and: dual equality.");
336  context_->StoreBooleanEqualityRelation(enforcement,
337  ct->bool_and().literals(0));
338  }
339  }
340  }
341 
342  return changed;
343 }
344 
345 bool CpModelPresolver::PresolveAtMostOrExactlyOne(ConstraintProto* ct) {
346  const bool is_at_most_one =
347  ct->constraint_case() == ConstraintProto::kAtMostOne;
348  const std::string name = is_at_most_one ? "at_most_one: " : "exactly_one: ";
349  auto* literals = is_at_most_one
350  ? ct->mutable_at_most_one()->mutable_literals()
351  : ct->mutable_exactly_one()->mutable_literals();
352 
353  // Deal with duplicate variable reference.
354  context_->tmp_literal_set.clear();
355  for (const int literal : *literals) {
356  if (context_->tmp_literal_set.contains(literal)) {
357  if (!context_->SetLiteralToFalse(literal)) return true;
358  context_->UpdateRuleStats(absl::StrCat(name, "duplicate literals"));
359  }
360  if (context_->tmp_literal_set.contains(NegatedRef(literal))) {
361  int num_positive = 0;
362  int num_negative = 0;
363  for (const int other : *literals) {
364  if (PositiveRef(other) != PositiveRef(literal)) {
365  if (!context_->SetLiteralToFalse(other)) return true;
366  context_->UpdateRuleStats(absl::StrCat(name, "x and not(x)"));
367  } else {
368  if (other == literal) {
369  ++num_positive;
370  } else {
371  ++num_negative;
372  }
373  }
374  }
375 
376  // This is tricky for the case where the at most one reduce to (lit,
377  // not(lit), not(lit)) for instance.
378  if (num_positive > 1 && !context_->SetLiteralToFalse(literal)) {
379  return true;
380  }
381  if (num_negative > 1 && !context_->SetLiteralToTrue(literal)) {
382  return true;
383  }
384  return RemoveConstraint(ct);
385  }
386  context_->tmp_literal_set.insert(literal);
387  }
388 
389  // Remove fixed variables.
390  bool changed = false;
391  context_->tmp_literals.clear();
392  for (const int literal : *literals) {
393  if (context_->LiteralIsTrue(literal)) {
394  context_->UpdateRuleStats(absl::StrCat(name, "satisfied"));
395  for (const int other : *literals) {
396  if (other != literal) {
397  if (!context_->SetLiteralToFalse(other)) return true;
398  }
399  }
400  return RemoveConstraint(ct);
401  }
402 
403  if (context_->LiteralIsFalse(literal)) {
404  changed = true;
405  continue;
406  }
407 
408  // TODO(user): Most situation are already dealt with by the dual presolve
409  // code in var_domination.cc, so maybe we don't need it here.
410  if (context_->VariableIsUniqueAndRemovable(literal) ||
412  context_->UpdateRuleStats("TODO [exactly/at_most]_one: singleton");
413  }
414 
415  context_->tmp_literals.push_back(literal);
416  }
417 
418  if (changed) {
419  literals->Clear();
420  for (const int lit : context_->tmp_literals) {
421  literals->Add(lit);
422  }
423  context_->UpdateRuleStats(absl::StrCat(name, "removed literals"));
424  }
425  return changed;
426 }
427 
428 bool CpModelPresolver::PresolveAtMostOne(ConstraintProto* ct) {
429  if (context_->ModelIsUnsat()) return false;
431  const bool changed = PresolveAtMostOrExactlyOne(ct);
432  if (ct->constraint_case() != ConstraintProto::kAtMostOne) return changed;
433 
434  // Size zero: ok.
435  const auto& literals = ct->at_most_one().literals();
436  if (literals.empty()) {
437  context_->UpdateRuleStats("at_most_one: empty or all false");
438  return RemoveConstraint(ct);
439  }
440 
441  // Size one: always satisfied.
442  if (literals.size() == 1) {
443  context_->UpdateRuleStats("at_most_one: size one");
444  return RemoveConstraint(ct);
445  }
446 
447  return changed;
448 }
449 
450 bool CpModelPresolver::PresolveExactlyOne(ConstraintProto* ct) {
451  if (context_->ModelIsUnsat()) return false;
453  const bool changed = PresolveAtMostOrExactlyOne(ct);
454  if (ct->constraint_case() != ConstraintProto::kExactlyOne) return changed;
455 
456  // Size zero: UNSAT.
457  const auto& literals = ct->exactly_one().literals();
458  if (literals.empty()) {
459  return context_->NotifyThatModelIsUnsat("exactly_one: empty or all false");
460  }
461 
462  // Size one: fix variable.
463  if (literals.size() == 1) {
464  context_->UpdateRuleStats("exactly_one: size one");
465  if (!context_->SetLiteralToTrue(literals[0])) return false;
466  return RemoveConstraint(ct);
467  }
468 
469  // Size two: Equivalence.
470  if (literals.size() == 2) {
471  context_->UpdateRuleStats("exactly_one: size two");
472  context_->StoreBooleanEqualityRelation(literals[0],
473  NegatedRef(literals[1]));
474  return RemoveConstraint(ct);
475  }
476 
477  return changed;
478 }
479 
480 bool CpModelPresolver::PresolveIntMax(ConstraintProto* ct) {
481  if (context_->ModelIsUnsat()) return false;
482  if (ct->int_max().vars().empty()) {
483  context_->UpdateRuleStats("int_max: no variables!");
484  return MarkConstraintAsFalse(ct);
485  }
486  const int target_ref = ct->int_max().target();
487 
488  // Pass 1, compute the infered min of the target, and remove duplicates.
489  int64 infered_min = kint64min;
490  int64 infered_max = kint64min;
491  bool contains_target_ref = false;
492  std::set<int> used_ref;
493  int new_size = 0;
494  for (const int ref : ct->int_max().vars()) {
495  if (ref == target_ref) contains_target_ref = true;
496  if (gtl::ContainsKey(used_ref, ref)) continue;
497  if (gtl::ContainsKey(used_ref, NegatedRef(ref)) ||
498  ref == NegatedRef(target_ref)) {
499  infered_min = std::max(infered_min, int64{0});
500  }
501  used_ref.insert(ref);
502  ct->mutable_int_max()->set_vars(new_size++, ref);
503  infered_min = std::max(infered_min, context_->MinOf(ref));
504  infered_max = std::max(infered_max, context_->MaxOf(ref));
505  }
506  if (new_size < ct->int_max().vars_size()) {
507  context_->UpdateRuleStats("int_max: removed dup");
508  }
509  ct->mutable_int_max()->mutable_vars()->Truncate(new_size);
510  if (contains_target_ref) {
511  context_->UpdateRuleStats("int_max: x = max(x, ...)");
512  for (const int ref : ct->int_max().vars()) {
513  if (ref == target_ref) continue;
514  ConstraintProto* new_ct = context_->working_model->add_constraints();
515  *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
516  auto* arg = new_ct->mutable_linear();
517  arg->add_vars(target_ref);
518  arg->add_coeffs(1);
519  arg->add_vars(ref);
520  arg->add_coeffs(-1);
521  arg->add_domain(0);
522  arg->add_domain(kint64max);
523  }
524  return RemoveConstraint(ct);
525  }
526 
527  // Compute the infered target_domain.
528  Domain infered_domain;
529  for (const int ref : ct->int_max().vars()) {
530  infered_domain = infered_domain.UnionWith(
531  context_->DomainOf(ref).IntersectionWith({infered_min, infered_max}));
532  }
533 
534  // Update the target domain.
535  bool domain_reduced = false;
536  if (!HasEnforcementLiteral(*ct)) {
537  if (!context_->IntersectDomainWith(target_ref, infered_domain,
538  &domain_reduced)) {
539  return true;
540  }
541  }
542 
543  // If the target is only used here and if
544  // infered_domain ∩ [kint64min, target_ub] ⊂ target_domain
545  // then the constraint is really max(...) <= target_ub and we can simplify it.
546  if (context_->VariableIsUniqueAndRemovable(target_ref)) {
547  const Domain& target_domain = context_->DomainOf(target_ref);
548  if (infered_domain.IntersectionWith(Domain(kint64min, target_domain.Max()))
549  .IsIncludedIn(target_domain)) {
550  if (infered_domain.Max() <= target_domain.Max()) {
551  // The constraint is always satisfiable.
552  context_->UpdateRuleStats("int_max: always true");
553  } else if (ct->enforcement_literal().empty()) {
554  // The constraint just restrict the upper bound of its variable.
555  for (const int ref : ct->int_max().vars()) {
556  context_->UpdateRuleStats("int_max: lower than constant");
557  if (!context_->IntersectDomainWith(
558  ref, Domain(kint64min, target_domain.Max()))) {
559  return false;
560  }
561  }
562  } else {
563  // We simply transform this into n reified constraints
564  // enforcement => [var_i <= target_domain.Max()].
565  context_->UpdateRuleStats("int_max: reified lower than constant");
566  for (const int ref : ct->int_max().vars()) {
567  ConstraintProto* new_ct = context_->working_model->add_constraints();
568  *(new_ct->mutable_enforcement_literal()) = ct->enforcement_literal();
569  ct->mutable_linear()->add_vars(ref);
570  ct->mutable_linear()->add_coeffs(1);
571  ct->mutable_linear()->add_domain(kint64min);
572  ct->mutable_linear()->add_domain(target_domain.Max());
573  }
574  }
575 
576  // In all cases we delete the original constraint.
577  context_->MarkVariableAsRemoved(target_ref);
578  *(context_->mapping_model->add_constraints()) = *ct;
579  return RemoveConstraint(ct);
580  }
581  }
582 
583  // Pass 2, update the argument domains. Filter them eventually.
584  new_size = 0;
585  const int size = ct->int_max().vars_size();
586  const int64 target_max = context_->MaxOf(target_ref);
587  for (const int ref : ct->int_max().vars()) {
588  if (!HasEnforcementLiteral(*ct)) {
589  if (!context_->IntersectDomainWith(ref, Domain(kint64min, target_max),
590  &domain_reduced)) {
591  return true;
592  }
593  }
594  if (context_->MaxOf(ref) >= infered_min) {
595  ct->mutable_int_max()->set_vars(new_size++, ref);
596  }
597  }
598  if (domain_reduced) {
599  context_->UpdateRuleStats("int_max: reduced domains");
600  }
601 
602  bool modified = false;
603  if (new_size < size) {
604  context_->UpdateRuleStats("int_max: removed variables");
605  ct->mutable_int_max()->mutable_vars()->Truncate(new_size);
606  modified = true;
607  }
608 
609  if (new_size == 0) {
610  context_->UpdateRuleStats("int_max: no variables!");
611  return MarkConstraintAsFalse(ct);
612  }
613  if (new_size == 1) {
614  // Convert to an equality. Note that we create a new constraint otherwise it
615  // might not be processed again.
616  context_->UpdateRuleStats("int_max: converted to equality");
617  ConstraintProto* new_ct = context_->working_model->add_constraints();
618  *new_ct = *ct; // copy name and potential reification.
619  auto* arg = new_ct->mutable_linear();
620  arg->add_vars(target_ref);
621  arg->add_coeffs(1);
622  arg->add_vars(ct->int_max().vars(0));
623  arg->add_coeffs(-1);
624  arg->add_domain(0);
625  arg->add_domain(0);
627  return RemoveConstraint(ct);
628  }
629  return modified;
630 }
631 
632 bool CpModelPresolver::PresolveLinMin(ConstraintProto* ct) {
633  if (context_->ModelIsUnsat()) return false;
634  // Convert to lin_max and presolve lin_max.
635  const auto copy = ct->lin_min();
636  SetToNegatedLinearExpression(copy.target(),
637  ct->mutable_lin_max()->mutable_target());
638  for (const LinearExpressionProto& expr : copy.exprs()) {
639  LinearExpressionProto* const new_expr = ct->mutable_lin_max()->add_exprs();
640  SetToNegatedLinearExpression(expr, new_expr);
641  }
642  return PresolveLinMax(ct);
643 }
644 
645 bool CpModelPresolver::PresolveLinMax(ConstraintProto* ct) {
646  if (context_->ModelIsUnsat()) return false;
647  if (ct->lin_max().exprs().empty()) {
648  context_->UpdateRuleStats("lin_max: no exprs");
649  return MarkConstraintAsFalse(ct);
650  }
651 
652  // Canonicalize all involved expression.
653  //
654  // TODO(user): If we start to have many constraints like this, we should
655  // use reflexion (see cp_model_util) to do that generically.
656  bool changed = CanonicalizeLinearExpression(
657  *ct, ct->mutable_lin_max()->mutable_target());
658  for (LinearExpressionProto& exp : *(ct->mutable_lin_max()->mutable_exprs())) {
659  changed |= CanonicalizeLinearExpression(*ct, &exp);
660  }
661 
662  // TODO(user): Remove duplicate expressions. This might be expensive.
663 
664  // Pass 1, Compute the infered min of the target.
665  int64 infered_min = context_->MinOf(ct->lin_max().target());
666  for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
667  // TODO(user): Check if the expressions contain target.
668 
669  // TODO(user): Check if the negated expression is already present and
670  // reduce inferred domain if so.
671 
672  infered_min = std::max(infered_min, context_->MinOf(expr));
673  }
674 
675  // Pass 2, Filter the expressions which are smaller than inferred min.
676  int new_size = 0;
677  for (int i = 0; i < ct->lin_max().exprs_size(); ++i) {
678  const LinearExpressionProto& expr = ct->lin_max().exprs(i);
679  if (context_->MaxOf(expr) >= infered_min) {
680  *ct->mutable_lin_max()->mutable_exprs(new_size) = expr;
681  new_size++;
682  }
683  }
684 
685  if (new_size < ct->lin_max().exprs_size()) {
686  context_->UpdateRuleStats("lin_max: Removed exprs");
687  ct->mutable_lin_max()->mutable_exprs()->DeleteSubrange(
688  new_size, ct->lin_max().exprs_size() - new_size);
689  return true;
690  }
691 
692  return changed;
693 }
694 
695 bool CpModelPresolver::PresolveIntAbs(ConstraintProto* ct) {
696  CHECK_EQ(ct->enforcement_literal_size(), 0);
697  if (context_->ModelIsUnsat()) return false;
698  const int target_ref = ct->int_max().target();
699  const int var = PositiveRef(ct->int_max().vars(0));
700 
701  // Propagate from the variable domain to the target variable.
702  const Domain var_domain = context_->DomainOf(var);
703  const Domain new_target_domain = var_domain.UnionWith(var_domain.Negation())
705  if (!context_->DomainOf(target_ref).IsIncludedIn(new_target_domain)) {
706  if (!context_->IntersectDomainWith(target_ref, new_target_domain)) {
707  return true;
708  }
709  context_->UpdateRuleStats("int_abs: propagate domain x to abs(x)");
710  }
711 
712  // Propagate from target domain to variable.
713  const Domain target_domain = context_->DomainOf(target_ref);
714  const Domain new_var_domain =
715  target_domain.UnionWith(target_domain.Negation());
716  if (!context_->DomainOf(var).IsIncludedIn(new_var_domain)) {
717  if (!context_->IntersectDomainWith(var, new_var_domain)) {
718  return true;
719  }
720  context_->UpdateRuleStats("int_abs: propagate domain abs(x) to x");
721  }
722 
723  if (context_->MinOf(var) >= 0 && !context_->IsFixed(var)) {
724  context_->UpdateRuleStats("int_abs: converted to equality");
725  ConstraintProto* new_ct = context_->working_model->add_constraints();
726  new_ct->set_name(ct->name());
727  auto* arg = new_ct->mutable_linear();
728  arg->add_vars(target_ref);
729  arg->add_coeffs(1);
730  arg->add_vars(var);
731  arg->add_coeffs(-1);
732  arg->add_domain(0);
733  arg->add_domain(0);
735  return RemoveConstraint(ct);
736  }
737 
738  if (context_->MaxOf(var) <= 0 && !context_->IsFixed(var)) {
739  context_->UpdateRuleStats("int_abs: converted to equality");
740  ConstraintProto* new_ct = context_->working_model->add_constraints();
741  new_ct->set_name(ct->name());
742  auto* arg = new_ct->mutable_linear();
743  arg->add_vars(target_ref);
744  arg->add_coeffs(1);
745  arg->add_vars(var);
746  arg->add_coeffs(1);
747  arg->add_domain(0);
748  arg->add_domain(0);
750  return RemoveConstraint(ct);
751  }
752 
753  // Remove the abs constraint if the target is removable or fixed, as domains
754  // have been propagated.
755  if (context_->VariableIsUniqueAndRemovable(target_ref) ||
756  context_->IsFixed(target_ref)) {
757  if (!context_->IsFixed(target_ref)) {
758  context_->MarkVariableAsRemoved(target_ref);
759  *context_->mapping_model->add_constraints() = *ct;
760  }
761  context_->UpdateRuleStats("int_abs: remove constraint");
762  return RemoveConstraint(ct);
763  }
764 
765  if (context_->StoreAbsRelation(target_ref, var)) {
766  context_->UpdateRuleStats("int_abs: store abs(x) == y");
767  }
768 
769  return false;
770 }
771 
772 bool CpModelPresolver::PresolveIntMin(ConstraintProto* ct) {
773  if (context_->ModelIsUnsat()) return false;
774 
775  const auto copy = ct->int_min();
776  ct->mutable_int_max()->set_target(NegatedRef(copy.target()));
777  for (const int ref : copy.vars()) {
778  ct->mutable_int_max()->add_vars(NegatedRef(ref));
779  }
780  return PresolveIntMax(ct);
781 }
782 
783 bool CpModelPresolver::PresolveIntProd(ConstraintProto* ct) {
784  if (context_->ModelIsUnsat()) return false;
785  if (HasEnforcementLiteral(*ct)) return false;
786 
787  if (ct->int_prod().vars().empty()) {
788  if (!context_->IntersectDomainWith(ct->int_prod().target(), Domain(1))) {
789  return false;
790  }
791  context_->UpdateRuleStats("int_prod: empty_product");
792  return RemoveConstraint(ct);
793  }
794  bool changed = false;
795 
796  // Replace any affine relation without offset.
797  // TODO(user): Also remove constant rhs variables.
798  int64 constant = 1;
799  for (int i = 0; i < ct->int_prod().vars().size(); ++i) {
800  const int ref = ct->int_prod().vars(i);
801  const AffineRelation::Relation& r = context_->GetAffineRelation(ref);
802  if (r.representative != ref && r.offset == 0) {
803  changed = true;
804  ct->mutable_int_prod()->set_vars(i, r.representative);
805  constant *= r.coeff;
806  }
807  }
808 
809  // TODO(user): Probably better to add a fixed variable to the product
810  // instead in this case. But we do need to support product with more than
811  // two variables properly for that.
812  //
813  // TODO(user): We might do that too early since the other presolve step below
814  // might simplify the constraint in such a way that there is no need to create
815  // a new variable!
816  if (constant != 1) {
817  context_->UpdateRuleStats("int_prod: extracted product by constant.");
818 
819  const int old_target = ct->int_prod().target();
820  const int new_target = context_->working_model->variables_size();
821 
822  IntegerVariableProto* var_proto = context_->working_model->add_variables();
824  context_->DomainOf(old_target).InverseMultiplicationBy(constant),
825  var_proto);
826  context_->InitializeNewDomains();
827  if (context_->ModelIsUnsat()) return false;
828 
829  ct->mutable_int_prod()->set_target(new_target);
830  if (context_->IsFixed(new_target)) {
831  // We need to fix old_target too.
832  if (!context_->IntersectDomainWith(
833  old_target,
834  context_->DomainOf(new_target).MultiplicationBy(constant))) {
835  return false;
836  }
837  } else {
838  if (!context_->StoreAffineRelation(old_target, new_target, constant, 0)) {
839  // We cannot store the affine relation because the old target seems
840  // to already be in affine relation with another variable. This is rare
841  // and we need to add a new constraint in that case.
842  ConstraintProto* new_ct = context_->working_model->add_constraints();
843  LinearConstraintProto* lin = new_ct->mutable_linear();
844  lin->add_vars(old_target);
845  lin->add_coeffs(1);
846  lin->add_vars(new_target);
847  lin->add_coeffs(-constant);
848  lin->add_domain(0);
849  lin->add_domain(0);
851  }
852  }
853  }
854 
855  // Restrict the target domain if possible.
856  Domain implied(1);
857  for (const int ref : ct->int_prod().vars()) {
858  implied = implied.ContinuousMultiplicationBy(context_->DomainOf(ref));
859  }
860  bool modified = false;
861  if (!context_->IntersectDomainWith(ct->int_prod().target(), implied,
862  &modified)) {
863  return false;
864  }
865  if (modified) {
866  context_->UpdateRuleStats("int_prod: reduced target domain.");
867  }
868 
869  if (ct->int_prod().vars_size() == 2) {
870  int a = ct->int_prod().vars(0);
871  int b = ct->int_prod().vars(1);
872  const int product = ct->int_prod().target();
873 
874  if (context_->IsFixed(b)) std::swap(a, b);
875  if (context_->IsFixed(a)) {
876  const int64 value_a = context_->MinOf(a);
877  if (value_a == 0) { // Fix target to 0.
878  if (!context_->IntersectDomainWith(product, Domain(0, 0))) {
879  return false;
880  }
881  context_->UpdateRuleStats("int_prod: fix target to zero.");
882  return RemoveConstraint(ct);
883  } else if (b != product) {
884  ConstraintProto* const lin = context_->working_model->add_constraints();
885  lin->mutable_linear()->add_vars(b);
886  lin->mutable_linear()->add_coeffs(value_a);
887  lin->mutable_linear()->add_vars(product);
888  lin->mutable_linear()->add_coeffs(-1);
889  lin->mutable_linear()->add_domain(0);
890  lin->mutable_linear()->add_domain(0);
892  context_->UpdateRuleStats("int_prod: linearize product by constant.");
893  return RemoveConstraint(ct);
894  } else if (value_a != 1) {
895  if (!context_->IntersectDomainWith(product, Domain(0, 0))) {
896  return false;
897  }
898  context_->UpdateRuleStats("int_prod: fix variable to zero.");
899  return RemoveConstraint(ct);
900  } else {
901  context_->UpdateRuleStats("int_prod: remove identity.");
902  return RemoveConstraint(ct);
903  }
904  } else if (a == b && a == product) { // x = x * x, only true for {0, 1}.
905  if (!context_->IntersectDomainWith(product, Domain(0, 1))) {
906  return false;
907  }
908  context_->UpdateRuleStats("int_prod: fix variable to zero or one.");
909  return RemoveConstraint(ct);
910  }
911  }
912 
913  // For now, we only presolve the case where all variables are Booleans.
914  const int target_ref = ct->int_prod().target();
915  if (!RefIsPositive(target_ref)) return changed;
916  for (const int var : ct->int_prod().vars()) {
917  if (!RefIsPositive(var)) return changed;
918  if (context_->MinOf(var) < 0) return changed;
919  if (context_->MaxOf(var) > 1) return changed;
920  }
921 
922  // This is a bool constraint!
923  if (!context_->IntersectDomainWith(target_ref, Domain(0, 1))) {
924  return false;
925  }
926  context_->UpdateRuleStats("int_prod: all Boolean.");
927  {
928  ConstraintProto* new_ct = context_->working_model->add_constraints();
929  new_ct->add_enforcement_literal(target_ref);
930  auto* arg = new_ct->mutable_bool_and();
931  for (const int var : ct->int_prod().vars()) {
932  arg->add_literals(var);
933  }
934  }
935  {
936  ConstraintProto* new_ct = context_->working_model->add_constraints();
937  auto* arg = new_ct->mutable_bool_or();
938  arg->add_literals(target_ref);
939  for (const int var : ct->int_prod().vars()) {
940  arg->add_literals(NegatedRef(var));
941  }
942  }
944  return RemoveConstraint(ct);
945 }
946 
947 bool CpModelPresolver::PresolveIntDiv(ConstraintProto* ct) {
948  if (context_->ModelIsUnsat()) return false;
949 
950  // For now, we only presolve the case where the divisor is constant.
951  const int target = ct->int_div().target();
952  const int ref_x = ct->int_div().vars(0);
953  const int ref_div = ct->int_div().vars(1);
954  if (!RefIsPositive(target) || !RefIsPositive(ref_x) ||
955  !RefIsPositive(ref_div) || context_->DomainIsEmpty(ref_div) ||
956  !context_->IsFixed(ref_div)) {
957  return false;
958  }
959 
960  const int64 divisor = context_->MinOf(ref_div);
961  if (divisor == 1) {
962  LinearConstraintProto* const lin =
963  context_->working_model->add_constraints()->mutable_linear();
964  lin->add_vars(ref_x);
965  lin->add_coeffs(1);
966  lin->add_vars(target);
967  lin->add_coeffs(-1);
968  lin->add_domain(0);
969  lin->add_domain(0);
971  context_->UpdateRuleStats("int_div: rewrite to equality");
972  return RemoveConstraint(ct);
973  }
974  bool domain_modified = false;
975  if (context_->IntersectDomainWith(
976  target, context_->DomainOf(ref_x).DivisionBy(divisor),
977  &domain_modified)) {
978  if (domain_modified) {
979  context_->UpdateRuleStats(
980  "int_div: updated domain of target in target = X / cte");
981  }
982  } else {
983  // Model is unsat.
984  return false;
985  }
986 
987  // Linearize if everything is positive.
988  // TODO(user,user): Deal with other cases where there is no change of
989  // sign.We can also deal with target = cte, div variable.
990 
991  if (context_->MinOf(target) >= 0 && context_->MinOf(ref_x) >= 0 &&
992  divisor > 1) {
993  LinearConstraintProto* const lin =
994  context_->working_model->add_constraints()->mutable_linear();
995  lin->add_vars(ref_x);
996  lin->add_coeffs(1);
997  lin->add_vars(target);
998  lin->add_coeffs(-divisor);
999  lin->add_domain(0);
1000  lin->add_domain(divisor - 1);
1002  context_->UpdateRuleStats(
1003  "int_div: linearize positive division with a constant divisor");
1004  return RemoveConstraint(ct);
1005  }
1006 
1007  // TODO(user): reduce the domain of X by introducing an
1008  // InverseDivisionOfSortedDisjointIntervals().
1009  return false;
1010 }
1011 
1012 bool CpModelPresolver::ExploitEquivalenceRelations(int c, ConstraintProto* ct) {
1013  bool changed = false;
1014 
1015  // Optim: Special case for the linear constraint. We just remap the
1016  // enforcement literals, the normal variables will be replaced by their
1017  // representative in CanonicalizeLinear().
1018  if (ct->constraint_case() == ConstraintProto::ConstraintCase::kLinear) {
1019  for (int& ref : *ct->mutable_enforcement_literal()) {
1020  const int rep = this->context_->GetLiteralRepresentative(ref);
1021  if (rep != ref) {
1022  changed = true;
1023  ref = rep;
1024  }
1025  }
1026  return changed;
1027  }
1028 
1029  // Optim: This extra loop is a lot faster than reparsing the variable from the
1030  // proto when there is nothing to do, which is quite often.
1031  bool work_to_do = false;
1032  for (const int var : context_->ConstraintToVars(c)) {
1033  const AffineRelation::Relation r = context_->GetAffineRelation(var);
1034  if (r.representative != var) {
1035  work_to_do = true;
1036  break;
1037  }
1038  }
1039  if (!work_to_do) return false;
1040 
1041  // Remap equal and negated variables to their representative.
1043  [&changed, this](int* ref) {
1044  const int rep = context_->GetVariableRepresentative(*ref);
1045  if (rep != *ref) {
1046  changed = true;
1047  *ref = rep;
1048  }
1049  },
1050  ct);
1051 
1052  // Remap literal and negated literal to their representative.
1054  [&changed, this](int* ref) {
1055  const int rep = this->context_->GetLiteralRepresentative(*ref);
1056  if (rep != *ref) {
1057  changed = true;
1058  *ref = rep;
1059  }
1060  },
1061  ct);
1062  return changed;
1063 }
1064 
1065 void CpModelPresolver::DivideLinearByGcd(ConstraintProto* ct) {
1066  if (context_->ModelIsUnsat()) return;
1067 
1068  // Compute the GCD of all coefficients.
1069  int64 gcd = 0;
1070  const int num_vars = ct->linear().vars().size();
1071  for (int i = 0; i < num_vars; ++i) {
1072  const int64 magnitude = std::abs(ct->linear().coeffs(i));
1073  gcd = MathUtil::GCD64(gcd, magnitude);
1074  if (gcd == 1) break;
1075  }
1076  if (gcd > 1) {
1077  context_->UpdateRuleStats("linear: divide by GCD");
1078  for (int i = 0; i < num_vars; ++i) {
1079  ct->mutable_linear()->set_coeffs(i, ct->linear().coeffs(i) / gcd);
1080  }
1081  const Domain rhs = ReadDomainFromProto(ct->linear());
1082  FillDomainInProto(rhs.InverseMultiplicationBy(gcd), ct->mutable_linear());
1083  if (ct->linear().domain_size() == 0) {
1084  return (void)MarkConstraintAsFalse(ct);
1085  }
1086  }
1087 }
1088 
1089 void CpModelPresolver::PresolveLinearEqualityModuloTwo(ConstraintProto* ct) {
1090  if (!ct->enforcement_literal().empty()) return;
1091  if (ct->linear().domain().size() != 2) return;
1092  if (ct->linear().domain(0) != ct->linear().domain(1)) return;
1093  if (context_->ModelIsUnsat()) return;
1094 
1095  // Any equality must be true modulo n.
1096  // The case modulo 2 is interesting if the non-zero terms are Booleans.
1097  std::vector<int> literals;
1098  for (int i = 0; i < ct->linear().vars().size(); ++i) {
1099  const int64 coeff = ct->linear().coeffs(i);
1100  const int ref = ct->linear().vars(i);
1101  if (coeff % 2 == 0) continue;
1102  if (!context_->CanBeUsedAsLiteral(ref)) return;
1103  literals.push_back(PositiveRef(ref));
1104  if (literals.size() > 2) return;
1105  }
1106  if (literals.size() == 1) {
1107  const int64 rhs = std::abs(ct->linear().domain(0));
1108  context_->UpdateRuleStats("linear: only one odd Boolean in equality");
1109  if (!context_->IntersectDomainWith(literals[0], Domain(rhs % 2))) return;
1110  } else if (literals.size() == 2) {
1111  const int64 rhs = std::abs(ct->linear().domain(0));
1112  context_->UpdateRuleStats("linear: only two odd Booleans in equality");
1113  if (rhs % 2) {
1114  context_->StoreBooleanEqualityRelation(literals[0],
1115  NegatedRef(literals[1]));
1116  } else {
1117  context_->StoreBooleanEqualityRelation(literals[0], literals[1]);
1118  }
1119  }
1120 }
1121 
1122 template <typename ProtoWithVarsAndCoeffs>
1123 bool CpModelPresolver::CanonicalizeLinearExpressionInternal(
1124  const ConstraintProto& ct, ProtoWithVarsAndCoeffs* proto, int64* offset) {
1125  // First regroup the terms on the same variables and sum the fixed ones.
1126  //
1127  // TODO(user): Add a quick pass to skip most of the work below if the
1128  // constraint is already in canonical form?
1129  tmp_terms_.clear();
1130  int64 sum_of_fixed_terms = 0;
1131  bool remapped = false;
1132  const int old_size = proto->vars().size();
1133  DCHECK_EQ(old_size, proto->coeffs().size());
1134  for (int i = 0; i < old_size; ++i) {
1135  const int ref = proto->vars(i);
1136  const int var = PositiveRef(ref);
1137  const int64 coeff =
1138  RefIsPositive(ref) ? proto->coeffs(i) : -proto->coeffs(i);
1139  if (coeff == 0) continue;
1140 
1141  if (context_->IsFixed(var)) {
1142  sum_of_fixed_terms += coeff * context_->MinOf(var);
1143  continue;
1144  }
1145 
1146  // TODO(user): Avoid the quadratic loop for the corner case of many
1147  // enforcement literal (this should be pretty rare though).
1148  bool removed = false;
1149  for (const int enf : ct.enforcement_literal()) {
1150  if (var == PositiveRef(enf)) {
1151  if (RefIsPositive(enf)) {
1152  // If the constraint is enforced, we can assume the variable is at 1.
1153  sum_of_fixed_terms += coeff;
1154  } else {
1155  // We can assume the variable is at zero.
1156  }
1157  removed = true;
1158  break;
1159  }
1160  }
1161  if (removed) {
1162  context_->UpdateRuleStats("linear: enforcement literal in expression");
1163  continue;
1164  }
1165 
1166  const AffineRelation::Relation r = context_->GetAffineRelation(var);
1167  if (r.representative != var) {
1168  remapped = true;
1169  sum_of_fixed_terms += coeff * r.offset;
1170  }
1171  tmp_terms_.push_back({r.representative, coeff * r.coeff});
1172  }
1173  proto->clear_vars();
1174  proto->clear_coeffs();
1175  std::sort(tmp_terms_.begin(), tmp_terms_.end());
1176  int current_var = 0;
1177  int64 current_coeff = 0;
1178  for (const auto entry : tmp_terms_) {
1179  CHECK(RefIsPositive(entry.first));
1180  if (entry.first == current_var) {
1181  current_coeff += entry.second;
1182  } else {
1183  if (current_coeff != 0) {
1184  proto->add_vars(current_var);
1185  proto->add_coeffs(current_coeff);
1186  }
1187  current_var = entry.first;
1188  current_coeff = entry.second;
1189  }
1190  }
1191  if (current_coeff != 0) {
1192  proto->add_vars(current_var);
1193  proto->add_coeffs(current_coeff);
1194  }
1195  if (remapped) {
1196  context_->UpdateRuleStats("linear: remapped using affine relations");
1197  }
1198  if (proto->vars().size() < old_size) {
1199  context_->UpdateRuleStats("linear: fixed or dup variables");
1200  }
1201  *offset = sum_of_fixed_terms;
1202  return remapped || proto->vars().size() < old_size;
1203 }
1204 
1205 bool CpModelPresolver::CanonicalizeLinearExpression(
1206  const ConstraintProto& ct, LinearExpressionProto* exp) {
1207  int64 offset = 0;
1208  const bool result = CanonicalizeLinearExpressionInternal(ct, exp, &offset);
1209  exp->set_offset(exp->offset() + offset);
1210  return result;
1211 }
1212 
1213 bool CpModelPresolver::CanonicalizeLinear(ConstraintProto* ct) {
1214  if (ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1215  context_->ModelIsUnsat()) {
1216  return false;
1217  }
1218  int64 offset = 0;
1219  const bool result =
1220  CanonicalizeLinearExpressionInternal(*ct, ct->mutable_linear(), &offset);
1221  if (offset != 0) {
1223  ReadDomainFromProto(ct->linear()).AdditionWith(Domain(-offset)),
1224  ct->mutable_linear());
1225  }
1226  DivideLinearByGcd(ct);
1227  return result;
1228 }
1229 
1230 bool CpModelPresolver::RemoveSingletonInLinear(ConstraintProto* ct) {
1231  if (ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1232  context_->ModelIsUnsat()) {
1233  return false;
1234  }
1235 
1236  std::set<int> index_to_erase;
1237  const int num_vars = ct->linear().vars().size();
1238  Domain rhs = ReadDomainFromProto(ct->linear());
1239 
1240  // First pass. Process singleton column that are not in the objective. Note
1241  // that for postsolve, it is important that we process them in the same order
1242  // in which they will be removed.
1243  for (int i = 0; i < num_vars; ++i) {
1244  const int var = ct->linear().vars(i);
1245  const int64 coeff = ct->linear().coeffs(i);
1247  if (context_->VariableIsUniqueAndRemovable(var)) {
1248  bool exact;
1249  const auto term_domain =
1250  context_->DomainOf(var).MultiplicationBy(-coeff, &exact);
1251  if (!exact) continue;
1252 
1253  // We do not do that if the domain of rhs becomes too complex.
1254  const Domain new_rhs = rhs.AdditionWith(term_domain);
1255  if (new_rhs.NumIntervals() > 100) continue;
1256 
1257  // Note that we can't do that if we loose information in the
1258  // multiplication above because the new domain might not be as strict
1259  // as the initial constraint otherwise. TODO(user): because of the
1260  // addition, it might be possible to cover more cases though.
1261  context_->UpdateRuleStats("linear: singleton column");
1262  index_to_erase.insert(i);
1263  rhs = new_rhs;
1264  continue;
1265  }
1266  }
1267 
1268  // If we didn't find any, look for the one appearing in the objective.
1269  if (index_to_erase.empty()) {
1270  // Note that we only do that if we have a non-reified equality.
1271  if (context_->params().presolve_substitution_level() <= 0) return false;
1272  if (!ct->enforcement_literal().empty()) return false;
1273 
1274  // If it is possible to do so, note that we can transform constraint into
1275  // equalities in PropagateDomainsInLinear().
1276  if (rhs.Min() != rhs.Max()) return false;
1277 
1278  for (int i = 0; i < num_vars; ++i) {
1279  const int var = ct->linear().vars(i);
1280  const int64 coeff = ct->linear().coeffs(i);
1282 
1283  // If the variable appear only in the objective and we have an equality,
1284  // we can transfer the cost to the rest of the linear expression, and
1285  // remove that variable.
1286  //
1287  // Note that is similar to the substitution code in PresolveLinear() but
1288  // it doesn't require the variable to be implied free since we do not
1289  // remove the constraints afterwards, just the variable.
1290  if (!context_->VariableWithCostIsUniqueAndRemovable(var)) continue;
1291  DCHECK(context_->ObjectiveMap().contains(var));
1292 
1293  // We only support substitution that does not require to multiply the
1294  // objective by some factor.
1295  //
1296  // TODO(user): If the objective is a single variable, we can actually
1297  // "absorb" any factor into the objective scaling.
1298  const int64 objective_coeff =
1299  gtl::FindOrDie(context_->ObjectiveMap(), var);
1300  CHECK_NE(coeff, 0);
1301  if (objective_coeff % coeff != 0) continue;
1302 
1303  // We do not do that if the domain of rhs becomes too complex.
1304  bool exact;
1305  const auto term_domain =
1306  context_->DomainOf(var).MultiplicationBy(-coeff, &exact);
1307  if (!exact) continue;
1308  const Domain new_rhs = rhs.AdditionWith(term_domain);
1309  if (new_rhs.NumIntervals() > 100) continue;
1310 
1311  // Special case: If the objective was a single variable, we can transfer
1312  // the domain of var to the objective, and just completely remove this
1313  // equality constraint like it is done in ExpandObjective().
1314  if (context_->ObjectiveMap().size() == 1) {
1315  if (!context_->IntersectDomainWith(
1317  objective_coeff))) {
1318  return true;
1319  }
1320 
1321  // The intersection above might fix var, in which case, we just abort.
1322  if (context_->IsFixed(var)) continue;
1323 
1324  // This makes sure the domain of var is propagated back to the
1325  // objective.
1326  if (!context_->CanonicalizeObjective()) {
1327  return context_->NotifyThatModelIsUnsat();
1328  }
1329 
1330  // Normally, CanonicalizeObjective() shouldn't remove var because
1331  // we work on a linear constraint that has been canonicalized. We keep
1332  // the test here in case this ever happen so we are notified.
1333  if (!context_->ObjectiveMap().contains(var)) {
1334  LOG(WARNING) << "This was not supposed to happen and the presolve "
1335  "could be improved.";
1336  continue;
1337  }
1338  context_->UpdateRuleStats("linear: singleton column define objective.");
1339  context_->SubstituteVariableInObjective(var, coeff, *ct);
1340  context_->MarkVariableAsRemoved(var);
1341  *(context_->mapping_model->add_constraints()) = *ct;
1342  return RemoveConstraint(ct);
1343  }
1344 
1345  // Update the objective and remove the variable from its equality
1346  // constraint by expanding its rhs. This might fail if the new linear
1347  // objective expression can lead to overflow.
1348  if (!context_->SubstituteVariableInObjective(var, coeff, *ct)) continue;
1349 
1350  context_->UpdateRuleStats(
1351  "linear: singleton column in equality and in objective.");
1352  rhs = new_rhs;
1353  index_to_erase.insert(i);
1354  break;
1355  }
1356  }
1357  if (index_to_erase.empty()) return false;
1358 
1359  // TODO(user): we could add the constraint to mapping_model only once
1360  // instead of adding a reduced version of it each time a new singleton
1361  // variable appear in the same constraint later. That would work but would
1362  // also force the postsolve to take search decisions...
1363  *(context_->mapping_model->add_constraints()) = *ct;
1364 
1365  int new_size = 0;
1366  for (int i = 0; i < num_vars; ++i) {
1367  if (index_to_erase.count(i)) {
1368  context_->MarkVariableAsRemoved(ct->linear().vars(i));
1369  continue;
1370  }
1371  ct->mutable_linear()->set_coeffs(new_size, ct->linear().coeffs(i));
1372  ct->mutable_linear()->set_vars(new_size, ct->linear().vars(i));
1373  ++new_size;
1374  }
1375  ct->mutable_linear()->mutable_vars()->Truncate(new_size);
1376  ct->mutable_linear()->mutable_coeffs()->Truncate(new_size);
1377  FillDomainInProto(rhs, ct->mutable_linear());
1378  DivideLinearByGcd(ct);
1379  return true;
1380 }
1381 
1382 bool CpModelPresolver::PresolveSmallLinear(ConstraintProto* ct) {
1383  if (ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1384  context_->ModelIsUnsat()) {
1385  return false;
1386  }
1387 
1388  // Empty constraint?
1389  if (ct->linear().vars().empty()) {
1390  context_->UpdateRuleStats("linear: empty");
1391  const Domain rhs = ReadDomainFromProto(ct->linear());
1392  if (rhs.Contains(0)) {
1393  return RemoveConstraint(ct);
1394  } else {
1395  return MarkConstraintAsFalse(ct);
1396  }
1397  }
1398 
1399  // If the constraint is literal => x in domain and x = abs(abs_arg), we can
1400  // replace x by abs_arg and hopefully remove the variable x later.
1401  int abs_arg;
1402  if (ct->linear().vars_size() == 1 && ct->enforcement_literal_size() > 0 &&
1403  ct->linear().coeffs(0) == 1 &&
1404  context_->GetAbsRelation(ct->linear().vars(0), &abs_arg)) {
1405  // TODO(user): Deal with coeff = -1, here or during canonicalization.
1406  context_->UpdateRuleStats("linear: remove abs from abs(x) in domain");
1407  const Domain implied_abs_target_domain =
1408  ReadDomainFromProto(ct->linear())
1410  .IntersectionWith(context_->DomainOf(ct->linear().vars(0)));
1411 
1412  if (implied_abs_target_domain.IsEmpty()) {
1413  return MarkConstraintAsFalse(ct);
1414  }
1415 
1416  const Domain new_abs_var_domain =
1417  implied_abs_target_domain
1418  .UnionWith(implied_abs_target_domain.Negation())
1419  .IntersectionWith(context_->DomainOf(abs_arg));
1420 
1421  if (new_abs_var_domain.IsEmpty()) {
1422  return MarkConstraintAsFalse(ct);
1423  }
1424 
1425  ConstraintProto* new_ct = context_->working_model->add_constraints();
1426  new_ct->set_name(ct->name());
1427  for (const int literal : ct->enforcement_literal()) {
1428  new_ct->add_enforcement_literal(literal);
1429  }
1430  auto* arg = new_ct->mutable_linear();
1431  arg->add_vars(abs_arg);
1432  arg->add_coeffs(1);
1433  FillDomainInProto(new_abs_var_domain, new_ct->mutable_linear());
1435  return RemoveConstraint(ct);
1436  }
1437 
1438  if (HasEnforcementLiteral(*ct)) {
1439  if (ct->enforcement_literal_size() != 1 || ct->linear().vars_size() != 1 ||
1440  (ct->linear().coeffs(0) != 1 && ct->linear().coeffs(0) == -1)) {
1441  return false;
1442  }
1443 
1444  const int literal = ct->enforcement_literal(0);
1445  const LinearConstraintProto& linear = ct->linear();
1446  const int ref = linear.vars(0);
1447  const int var = PositiveRef(ref);
1448  const int64 coeff =
1449  RefIsPositive(ref) ? ct->linear().coeffs(0) : -ct->linear().coeffs(0);
1450 
1451  if (linear.domain_size() == 2 && linear.domain(0) == linear.domain(1)) {
1452  const int64 value = RefIsPositive(ref) ? linear.domain(0) * coeff
1453  : -linear.domain(0) * coeff;
1454  if (context_->StoreLiteralImpliesVarEqValue(literal, var, value)) {
1455  // The domain is not actually modified, but we want to rescan the
1456  // constraints linked to this variable. See TODO below.
1457  context_->modified_domains.Set(var);
1458  }
1459  } else {
1460  const Domain complement = context_->DomainOf(ref).IntersectionWith(
1461  ReadDomainFromProto(linear).Complement());
1462  if (complement.Size() != 1) return false;
1463  const int64 value = RefIsPositive(ref) ? complement.Min() * coeff
1464  : -complement.Min() * coeff;
1465  if (context_->StoreLiteralImpliesVarNEqValue(literal, var, value)) {
1466  // The domain is not actually modified, but we want to rescan the
1467  // constraints linked to this variable. See TODO below.
1468  context_->modified_domains.Set(var);
1469  }
1470  }
1471 
1472  // TODO(user): if we have l1 <=> x == value && l2 => x == value, we
1473  // could rewrite the second constraint into l2 => l1.
1475  return false;
1476  }
1477 
1478  // Size one constraint?
1479  if (ct->linear().vars().size() == 1) {
1480  const int64 coeff = RefIsPositive(ct->linear().vars(0))
1481  ? ct->linear().coeffs(0)
1482  : -ct->linear().coeffs(0);
1483  context_->UpdateRuleStats("linear: size one");
1484  const int var = PositiveRef(ct->linear().vars(0));
1485  const Domain rhs = ReadDomainFromProto(ct->linear());
1486  if (!context_->IntersectDomainWith(var,
1487  rhs.InverseMultiplicationBy(coeff))) {
1488  return true;
1489  }
1490  return RemoveConstraint(ct);
1491  }
1492 
1493  // Detect affine relation.
1494  //
1495  // TODO(user): it might be better to first add only the affine relation with
1496  // a coefficient of magnitude 1, and later the one with larger coeffs.
1497  const LinearConstraintProto& arg = ct->linear();
1498  if (arg.vars_size() == 2) {
1499  const Domain rhs = ReadDomainFromProto(ct->linear());
1500  const int64 rhs_min = rhs.Min();
1501  const int64 rhs_max = rhs.Max();
1502  if (rhs_min == rhs_max) {
1503  const int v1 = arg.vars(0);
1504  const int v2 = arg.vars(1);
1505  const int64 coeff1 = arg.coeffs(0);
1506  const int64 coeff2 = arg.coeffs(1);
1507  bool added = false;
1508  if (coeff1 == 1) {
1509  added = context_->StoreAffineRelation(v1, v2, -coeff2, rhs_max);
1510  } else if (coeff2 == 1) {
1511  added = context_->StoreAffineRelation(v2, v1, -coeff1, rhs_max);
1512  } else if (coeff1 == -1) {
1513  added = context_->StoreAffineRelation(v1, v2, coeff2, -rhs_max);
1514  } else if (coeff2 == -1) {
1515  added = context_->StoreAffineRelation(v2, v1, coeff1, -rhs_max);
1516  }
1517  if (added) return RemoveConstraint(ct);
1518  }
1519  }
1520 
1521  return false;
1522 }
1523 
1524 namespace {
1525 
1526 // Return true if the given domain only restrict the values with an upper bound.
1527 bool IsLeConstraint(const Domain& domain, const Domain& all_values) {
1528  return all_values.IntersectionWith(Domain(kint64min, domain.Max()))
1529  .IsIncludedIn(domain);
1530 }
1531 
1532 // Same as IsLeConstraint() but in the other direction.
1533 bool IsGeConstraint(const Domain& domain, const Domain& all_values) {
1534  return all_values.IntersectionWith(Domain(domain.Min(), kint64max))
1535  .IsIncludedIn(domain);
1536 }
1537 
1538 // In the equation terms + coeff * var_domain \included rhs, returns true if can
1539 // we always fix rhs to its min value for any value in terms. It is okay to
1540 // not be as generic as possible here.
1541 bool RhsCanBeFixedToMin(int64 coeff, const Domain& var_domain,
1542  const Domain& terms, const Domain& rhs) {
1543  if (var_domain.NumIntervals() != 1) return false;
1544  if (std::abs(coeff) != 1) return false;
1545 
1546  // If for all values in terms, there is one value below rhs.Min(), then
1547  // because we add only one integer interval, if there is a feasible value, it
1548  // can be at rhs.Min().
1549  //
1550  // TODO(user): generalize to larger coeff magnitude if rhs is also a multiple
1551  // or if terms is a multiple.
1552  if (coeff == 1 && terms.Max() + var_domain.Min() <= rhs.Min()) {
1553  return true;
1554  }
1555  if (coeff == -1 && terms.Max() - var_domain.Max() <= rhs.Min()) {
1556  return true;
1557  }
1558  return false;
1559 }
1560 
1561 bool RhsCanBeFixedToMax(int64 coeff, const Domain& var_domain,
1562  const Domain& terms, const Domain& rhs) {
1563  if (var_domain.NumIntervals() != 1) return false;
1564  if (std::abs(coeff) != 1) return false;
1565 
1566  if (coeff == 1 && terms.Min() + var_domain.Max() >= rhs.Max()) {
1567  return true;
1568  }
1569  if (coeff == -1 && terms.Min() - var_domain.Min() >= rhs.Max()) {
1570  return true;
1571  }
1572  return false;
1573 }
1574 
1575 // Remove from to_clear any entry not in current.
1576 void TakeIntersectionWith(const absl::flat_hash_set<int>& current,
1577  absl::flat_hash_set<int>* to_clear) {
1578  std::vector<int> new_set;
1579  for (const int c : *to_clear) {
1580  if (current.contains(c)) new_set.push_back(c);
1581  }
1582  to_clear->clear();
1583  for (const int c : new_set) to_clear->insert(c);
1584 }
1585 
1586 } // namespace
1587 
1588 bool CpModelPresolver::PropagateDomainsInLinear(int c, ConstraintProto* ct) {
1589  if (ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1590  context_->ModelIsUnsat()) {
1591  return false;
1592  }
1593 
1594  // Compute the implied rhs bounds from the variable ones.
1595  auto& term_domains = context_->tmp_term_domains;
1596  auto& left_domains = context_->tmp_left_domains;
1597  const int num_vars = ct->linear().vars_size();
1598  term_domains.resize(num_vars + 1);
1599  left_domains.resize(num_vars + 1);
1600  left_domains[0] = Domain(0);
1601  for (int i = 0; i < num_vars; ++i) {
1602  const int var = ct->linear().vars(i);
1603  const int64 coeff = ct->linear().coeffs(i);
1605  term_domains[i] = context_->DomainOf(var).MultiplicationBy(coeff);
1606  left_domains[i + 1] =
1607  left_domains[i].AdditionWith(term_domains[i]).RelaxIfTooComplex();
1608  }
1609  const Domain& implied_rhs = left_domains[num_vars];
1610 
1611  // Abort if trivial.
1612  const Domain old_rhs = ReadDomainFromProto(ct->linear());
1613  if (implied_rhs.IsIncludedIn(old_rhs)) {
1614  context_->UpdateRuleStats("linear: always true");
1615  return RemoveConstraint(ct);
1616  }
1617 
1618  // Incorporate the implied rhs information.
1619  Domain rhs = old_rhs.SimplifyUsingImpliedDomain(implied_rhs);
1620  if (rhs.IsEmpty()) {
1621  context_->UpdateRuleStats("linear: infeasible");
1622  return MarkConstraintAsFalse(ct);
1623  }
1624  if (rhs != old_rhs) {
1625  context_->UpdateRuleStats("linear: simplified rhs");
1626  }
1627  FillDomainInProto(rhs, ct->mutable_linear());
1628 
1629  // Detect if it is always good for a term of this constraint to move towards
1630  // its lower (resp. upper) bound. This is the same as saying that this
1631  // constraint only bound in one direction.
1632  bool is_le_constraint = IsLeConstraint(rhs, implied_rhs);
1633  bool is_ge_constraint = IsGeConstraint(rhs, implied_rhs);
1634 
1635  // Propagate the variable bounds.
1636  if (ct->enforcement_literal().size() > 1) return false;
1637 
1638  bool new_bounds = false;
1639  bool recanonicalize = false;
1640  Domain negated_rhs = rhs.Negation();
1641  Domain right_domain(0);
1642  Domain new_domain;
1643  Domain implied_term_domain;
1644  term_domains[num_vars] = Domain(0);
1645  for (int i = num_vars - 1; i >= 0; --i) {
1646  const int var = ct->linear().vars(i);
1647  const int64 var_coeff = ct->linear().coeffs(i);
1648  right_domain =
1649  right_domain.AdditionWith(term_domains[i + 1]).RelaxIfTooComplex();
1650  implied_term_domain = left_domains[i].AdditionWith(right_domain);
1651  new_domain = implied_term_domain.AdditionWith(negated_rhs)
1652  .InverseMultiplicationBy(-var_coeff);
1653 
1654  if (ct->enforcement_literal().empty()) {
1655  // Push the new domain.
1656  if (!context_->IntersectDomainWith(var, new_domain, &new_bounds)) {
1657  return true;
1658  }
1659  } else if (ct->enforcement_literal().size() == 1) {
1660  // We cannot push the new domain, but we can add some deduction.
1662  if (!context_->DomainOfVarIsIncludedIn(var, new_domain)) {
1663  context_->deductions.AddDeduction(ct->enforcement_literal(0), var,
1664  new_domain);
1665  }
1666  }
1667 
1668  if (context_->IsFixed(var)) {
1669  // This will make sure we remove that fixed variable from the constraint.
1670  recanonicalize = true;
1671  continue;
1672  }
1673 
1674  if (is_le_constraint || is_ge_constraint) {
1675  CHECK_NE(is_le_constraint, is_ge_constraint);
1676  if ((var_coeff > 0) == is_ge_constraint) {
1677  context_->var_to_lb_only_constraints[var].insert(c);
1678  } else {
1679  context_->var_to_ub_only_constraints[var].insert(c);
1680  }
1681 
1682  // Simple dual fixing: If for any feasible solution, any solution with var
1683  // higher (resp. lower) is also valid, then we can fix that variable to
1684  // its bound if it also moves the objective in the good direction.
1685  //
1686  // A bit tricky. If a linear constraint was detected to not block a
1687  // variable in one direction, this shouldn't change later (expect in the
1688  // tightening code below, and we do take care of it). However variable can
1689  // appear in new constraints.
1690  if (!context_->keep_all_feasible_solutions) {
1691  const bool is_in_objective =
1692  context_->VarToConstraints(var).contains(-1);
1693  const int size =
1694  context_->VarToConstraints(var).size() - (is_in_objective ? 1 : 0);
1695  const int64 obj_coeff =
1696  is_in_objective ? gtl::FindOrDie(context_->ObjectiveMap(), var) : 0;
1697 
1698  // We cannot fix anything if the domain of the objective is excluding
1699  // some objective values.
1700  if (obj_coeff != 0 && context_->ObjectiveDomainIsConstraining()) {
1701  continue;
1702  }
1703 
1704  if (obj_coeff <= 0 &&
1705  context_->var_to_lb_only_constraints[var].size() >= size) {
1706  TakeIntersectionWith(context_->VarToConstraints(var),
1707  &(context_->var_to_lb_only_constraints[var]));
1708  if (context_->var_to_lb_only_constraints[var].size() >= size) {
1709  if (!context_->IntersectDomainWith(var,
1710  Domain(context_->MaxOf(var)))) {
1711  return false;
1712  }
1713  context_->UpdateRuleStats("linear: dual fixing");
1714  recanonicalize = true;
1715  continue;
1716  }
1717  }
1718  if (obj_coeff >= 0 &&
1719  context_->var_to_ub_only_constraints[var].size() >= size) {
1720  TakeIntersectionWith(context_->VarToConstraints(var),
1721  &(context_->var_to_ub_only_constraints[var]));
1722  if (context_->var_to_ub_only_constraints[var].size() >= size) {
1723  if (!context_->IntersectDomainWith(var,
1724  Domain(context_->MinOf(var)))) {
1725  return false;
1726  }
1727  context_->UpdateRuleStats("linear: dual fixing");
1728  recanonicalize = true;
1729  continue;
1730  }
1731  }
1732  }
1733  }
1734 
1735  // The other transformations below require a non-reified constraint.
1736  if (!ct->enforcement_literal().empty()) continue;
1737 
1738  // Given a variable that only appear in one constraint and in the
1739  // objective, for any feasible solution, it will be always better to move
1740  // this singleton variable as much as possible towards its good objective
1741  // direction. Sometimes, we can detect that we will always be able to do
1742  // this until the only constraint of this singleton variable is tight.
1743  //
1744  // When this happens, we can make the constraint an equality. Note that it
1745  // might not always be good to restrict constraint like this, but in this
1746  // case, the RemoveSingletonInLinear() code should be able to remove this
1747  // variable altogether.
1748  if (rhs.Min() != rhs.Max() &&
1750  const int64 obj_coeff = gtl::FindOrDie(context_->ObjectiveMap(), var);
1751  const bool same_sign = (var_coeff > 0) == (obj_coeff > 0);
1752  bool fixed = false;
1753  if (same_sign && RhsCanBeFixedToMin(var_coeff, context_->DomainOf(var),
1754  implied_term_domain, rhs)) {
1755  rhs = Domain(rhs.Min());
1756  fixed = true;
1757  }
1758  if (!same_sign && RhsCanBeFixedToMax(var_coeff, context_->DomainOf(var),
1759  implied_term_domain, rhs)) {
1760  rhs = Domain(rhs.Max());
1761  fixed = true;
1762  }
1763  if (fixed) {
1764  context_->UpdateRuleStats("linear: tightened into equality");
1765  FillDomainInProto(rhs, ct->mutable_linear());
1766  negated_rhs = rhs.Negation();
1767 
1768  // Restart the loop.
1769  i = num_vars;
1770  right_domain = Domain(0);
1771 
1772  // An equality is a >= (or <=) constraint iff all its term are fixed.
1773  // Since we restart the loop, we will detect that.
1774  is_le_constraint = false;
1775  is_ge_constraint = false;
1776  for (const int var : ct->linear().vars()) {
1777  context_->var_to_lb_only_constraints[var].erase(c);
1778  context_->var_to_ub_only_constraints[var].erase(c);
1779  }
1780  continue;
1781  }
1782  }
1783 
1784  // Can we perform some substitution?
1785  //
1786  // TODO(user): there is no guarantee we will not miss some since we might
1787  // not reprocess a constraint once other have been deleted.
1788 
1789  // Skip affine constraint. It is more efficient to substitute them lazily
1790  // when we process other constraints. Note that if we relax the fact that
1791  // we substitute only equalities, we can deal with inequality of size 2
1792  // here.
1793  if (ct->linear().vars().size() <= 2) continue;
1794 
1795  // TODO(user): We actually do not need a strict equality when
1796  // keep_all_feasible_solutions is false, but that simplifies things as the
1797  // SubstituteVariable() function cannot fail this way.
1798  if (rhs.Min() != rhs.Max()) continue;
1799 
1800  // Only consider "implied free" variables. Note that the coefficient of
1801  // magnitude 1 is important otherwise we can't easily remove the
1802  // constraint since the fact that the sum of the other terms must be a
1803  // multiple of coeff will not be enforced anymore.
1804  if (context_->DomainOf(var) != new_domain) continue;
1805  if (std::abs(var_coeff) != 1) continue;
1806  if (context_->params().presolve_substitution_level() <= 0) continue;
1807 
1808  // NOTE: The mapping doesn't allow us to remove a variable if
1809  // 'keep_all_feasible_solutions' is true.
1810  if (context_->keep_all_feasible_solutions) continue;
1811 
1812  bool is_in_objective = false;
1813  if (context_->VarToConstraints(var).contains(-1)) {
1814  is_in_objective = true;
1815  DCHECK(context_->ObjectiveMap().contains(var));
1816  }
1817 
1818  // Only consider low degree columns.
1819  int col_size = context_->VarToConstraints(var).size();
1820  if (is_in_objective) col_size--;
1821  const int row_size = ct->linear().vars_size();
1822 
1823  // This is actually an upper bound on the number of entries added since
1824  // some of them might already be present.
1825  const int num_entries_added = (row_size - 1) * (col_size - 1);
1826  const int num_entries_removed = col_size + row_size - 1;
1827 
1828  if (num_entries_added > num_entries_removed) {
1829  continue;
1830  }
1831 
1832  // Check pre-conditions on all the constraints in which this variable
1833  // appear. Basically they must all be linear.
1834  std::vector<int> others;
1835  bool abort = false;
1836  for (const int c : context_->VarToConstraints(var)) {
1837  if (c == kObjectiveConstraint) continue;
1838  if (c == kAffineRelationConstraint) {
1839  abort = true;
1840  break;
1841  }
1842  if (context_->working_model->mutable_constraints(c) == ct) continue;
1843  if (context_->working_model->constraints(c).constraint_case() !=
1844  ConstraintProto::ConstraintCase::kLinear) {
1845  abort = true;
1846  break;
1847  }
1848  for (const int ref :
1849  context_->working_model->constraints(c).enforcement_literal()) {
1850  if (PositiveRef(ref) == var) {
1851  abort = true;
1852  break;
1853  }
1854  }
1855  others.push_back(c);
1856  }
1857  if (abort) continue;
1858 
1859  // Do the actual substitution.
1860  for (const int c : others) {
1861  // TODO(user): In some corner cases, this might create integer overflow
1862  // issues. The danger is limited since the range of the linear
1863  // expression used in the definition do not exceed the domain of the
1864  // variable we substitute.
1865  SubstituteVariable(var, var_coeff, *ct,
1866  context_->working_model->mutable_constraints(c));
1867 
1868  // TODO(user): We should re-enqueue these constraints for presolve.
1869  context_->UpdateConstraintVariableUsage(c);
1870  }
1871 
1872  // Substitute in objective.
1873  if (is_in_objective) {
1874  context_->SubstituteVariableInObjective(var, var_coeff, *ct);
1875  }
1876 
1877  context_->UpdateRuleStats(
1878  absl::StrCat("linear: variable substitution ", others.size()));
1879 
1880  // The variable now only appear in its definition and we can remove it
1881  // because it was implied free.
1882  //
1883  // Tricky: If the linear constraint contains other variables that are only
1884  // used here, then the postsolve needs more info. We do need to indicate
1885  // that whatever the value of those other variables, we will have a way to
1886  // assign var. We do that by putting it fist.
1887  CHECK_EQ(context_->VarToConstraints(var).size(), 1);
1888  context_->MarkVariableAsRemoved(var);
1889  const int ct_index = context_->mapping_model->constraints().size();
1890  *context_->mapping_model->add_constraints() = *ct;
1891  LinearConstraintProto* mapping_linear_ct =
1892  context_->mapping_model->mutable_constraints(ct_index)
1893  ->mutable_linear();
1894  std::swap(mapping_linear_ct->mutable_vars()->at(0),
1895  mapping_linear_ct->mutable_vars()->at(i));
1896  std::swap(mapping_linear_ct->mutable_coeffs()->at(0),
1897  mapping_linear_ct->mutable_coeffs()->at(i));
1898  return RemoveConstraint(ct);
1899  }
1900  if (new_bounds) {
1901  context_->UpdateRuleStats("linear: reduced variable domains");
1902  }
1903  if (recanonicalize) return CanonicalizeLinear(ct);
1904  return false;
1905 }
1906 
1907 // Identify Boolean variable that makes the constraint always true when set to
1908 // true or false. Moves such literal to the constraint enforcement literals
1909 // list.
1910 //
1911 // We also generalize this to integer variable at one of their bound.
1912 //
1913 // This operation is similar to coefficient strengthening in the MIP world.
1914 void CpModelPresolver::ExtractEnforcementLiteralFromLinearConstraint(
1915  int ct_index, ConstraintProto* ct) {
1916  if (ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1917  context_->ModelIsUnsat()) {
1918  return;
1919  }
1920 
1921  const LinearConstraintProto& arg = ct->linear();
1922  const int num_vars = arg.vars_size();
1923 
1924  // No need to process size one constraints, they will be presolved separately.
1925  // We also do not want to split them in two.
1926  if (num_vars <= 1) return;
1927 
1928  int64 min_sum = 0;
1929  int64 max_sum = 0;
1930  int64 max_coeff_magnitude = 0;
1931  for (int i = 0; i < num_vars; ++i) {
1932  const int ref = arg.vars(i);
1933  const int64 coeff = arg.coeffs(i);
1934  const int64 term_a = coeff * context_->MinOf(ref);
1935  const int64 term_b = coeff * context_->MaxOf(ref);
1936  max_coeff_magnitude = std::max(max_coeff_magnitude, std::abs(coeff));
1937  min_sum += std::min(term_a, term_b);
1938  max_sum += std::max(term_a, term_b);
1939  }
1940 
1941  // We can only extract enforcement literals if the maximum coefficient
1942  // magnitude is large enough. Note that we handle complex domain.
1943  //
1944  // TODO(user): Depending on how we split below, the threshold are not the
1945  // same. This is maybe not too important, we just don't split as often as we
1946  // could, but it is still unclear if splitting is good.
1947  const auto& domain = ct->linear().domain();
1948  const int64 ub_threshold = domain[domain.size() - 2] - min_sum;
1949  const int64 lb_threshold = max_sum - domain[1];
1950  const Domain rhs_domain = ReadDomainFromProto(ct->linear());
1951  if (max_coeff_magnitude < std::max(ub_threshold, lb_threshold)) return;
1952 
1953  // We need the constraint to be only bounded on one side in order to extract
1954  // enforcement literal.
1955  //
1956  // If it is boxed and we know that some coefficient are big enough (see test
1957  // above), then we split the constraint in two. That might not seems always
1958  // good, but for the CP propagation engine, we don't loose anything by doing
1959  // so, and for the LP we will regroup the constraints if they still have the
1960  // exact same coeff after the presolve.
1961  //
1962  // TODO(user): Creating two new constraints and removing the current one might
1963  // not be the most efficient, but it simplify the presolve code by not having
1964  // to do anything special to trigger a new presolving of these constraints.
1965  // Try to improve if this becomes a problem.
1966  //
1967  // TODO(user): At the end of the presolve we should probably remerge any
1968  // identical linear constraints. That also cover the corner cases where
1969  // constraints are just redundant...
1970  const bool lower_bounded = min_sum < rhs_domain.Min();
1971  const bool upper_bounded = max_sum > rhs_domain.Max();
1972  if (!lower_bounded && !upper_bounded) return;
1973  if (lower_bounded && upper_bounded) {
1974  context_->UpdateRuleStats("linear: split boxed constraint");
1975  ConstraintProto* new_ct1 = context_->working_model->add_constraints();
1976  *new_ct1 = *ct;
1977  if (!ct->name().empty()) {
1978  new_ct1->set_name(absl::StrCat(ct->name(), " (part 1)"));
1979  }
1980  FillDomainInProto(Domain(min_sum, rhs_domain.Max()),
1981  new_ct1->mutable_linear());
1982 
1983  ConstraintProto* new_ct2 = context_->working_model->add_constraints();
1984  *new_ct2 = *ct;
1985  if (!ct->name().empty()) {
1986  new_ct2->set_name(absl::StrCat(ct->name(), " (part 2)"));
1987  }
1988  FillDomainInProto(rhs_domain.UnionWith(Domain(rhs_domain.Max(), max_sum)),
1989  new_ct2->mutable_linear());
1990 
1992  return (void)RemoveConstraint(ct);
1993  }
1994 
1995  // Any coefficient greater than this will cause the constraint to be trivially
1996  // satisfied when the variable move away from its bound. Note that as we
1997  // remove coefficient, the threshold do not change!
1998  const int64 threshold = lower_bounded ? ub_threshold : lb_threshold;
1999 
2000  // Do we only extract Booleans?
2001  const bool only_booleans =
2002  !context_->params().presolve_extract_integer_enforcement();
2003 
2004  // To avoid a quadratic loop, we will rewrite the linear expression at the
2005  // same time as we extract enforcement literals.
2006  int new_size = 0;
2007  int64 rhs_offset = 0;
2008  bool some_integer_encoding_were_extracted = false;
2009  LinearConstraintProto* mutable_arg = ct->mutable_linear();
2010  for (int i = 0; i < arg.vars_size(); ++i) {
2011  int ref = arg.vars(i);
2012  int64 coeff = arg.coeffs(i);
2013  if (coeff < 0) {
2014  ref = NegatedRef(ref);
2015  coeff = -coeff;
2016  }
2017 
2018  const bool is_boolean = context_->CanBeUsedAsLiteral(ref);
2019  if (context_->IsFixed(ref) || coeff < threshold ||
2020  (only_booleans && !is_boolean)) {
2021  // We keep this term.
2022  mutable_arg->set_vars(new_size, mutable_arg->vars(i));
2023  mutable_arg->set_coeffs(new_size, mutable_arg->coeffs(i));
2024  ++new_size;
2025  continue;
2026  }
2027 
2028  if (is_boolean) {
2029  context_->UpdateRuleStats("linear: extracted enforcement literal");
2030  } else {
2031  some_integer_encoding_were_extracted = true;
2032  context_->UpdateRuleStats(
2033  "linear: extracted integer enforcement literal");
2034  }
2035  if (lower_bounded) {
2036  ct->add_enforcement_literal(is_boolean
2037  ? NegatedRef(ref)
2038  : context_->GetOrCreateVarValueEncoding(
2039  ref, context_->MinOf(ref)));
2040  rhs_offset -= coeff * context_->MinOf(ref);
2041  } else {
2042  ct->add_enforcement_literal(is_boolean
2043  ? ref
2044  : context_->GetOrCreateVarValueEncoding(
2045  ref, context_->MaxOf(ref)));
2046  rhs_offset -= coeff * context_->MaxOf(ref);
2047  }
2048  }
2049  mutable_arg->mutable_vars()->Truncate(new_size);
2050  mutable_arg->mutable_coeffs()->Truncate(new_size);
2051  FillDomainInProto(rhs_domain.AdditionWith(Domain(rhs_offset)), mutable_arg);
2052  if (some_integer_encoding_were_extracted) {
2054  context_->UpdateConstraintVariableUsage(ct_index);
2055  }
2056 }
2057 
2058 void CpModelPresolver::ExtractAtMostOneFromLinear(ConstraintProto* ct) {
2059  if (context_->ModelIsUnsat()) return;
2060  if (HasEnforcementLiteral(*ct)) return;
2061  const Domain rhs = ReadDomainFromProto(ct->linear());
2062 
2063  const LinearConstraintProto& arg = ct->linear();
2064  const int num_vars = arg.vars_size();
2065  int64 min_sum = 0;
2066  int64 max_sum = 0;
2067  for (int i = 0; i < num_vars; ++i) {
2068  const int ref = arg.vars(i);
2069  const int64 coeff = arg.coeffs(i);
2070  const int64 term_a = coeff * context_->MinOf(ref);
2071  const int64 term_b = coeff * context_->MaxOf(ref);
2072  min_sum += std::min(term_a, term_b);
2073  max_sum += std::max(term_a, term_b);
2074  }
2075  for (const int type : {0, 1}) {
2076  std::vector<int> at_most_one;
2077  for (int i = 0; i < num_vars; ++i) {
2078  const int ref = arg.vars(i);
2079  const int64 coeff = arg.coeffs(i);
2080  if (context_->MinOf(ref) != 0) continue;
2081  if (context_->MaxOf(ref) != 1) continue;
2082 
2083  if (type == 0) {
2084  // TODO(user): we could add one more Boolean with a lower coeff as long
2085  // as we have lower_coeff + min_of_other_coeff > rhs.Max().
2086  if (min_sum + 2 * std::abs(coeff) > rhs.Max()) {
2087  at_most_one.push_back(coeff > 0 ? ref : NegatedRef(ref));
2088  }
2089  } else {
2090  if (max_sum - 2 * std::abs(coeff) < rhs.Min()) {
2091  at_most_one.push_back(coeff > 0 ? NegatedRef(ref) : ref);
2092  }
2093  }
2094  }
2095  if (at_most_one.size() > 1) {
2096  if (type == 0) {
2097  context_->UpdateRuleStats("linear: extracted at most one (max).");
2098  } else {
2099  context_->UpdateRuleStats("linear: extracted at most one (min).");
2100  }
2101  ConstraintProto* new_ct = context_->working_model->add_constraints();
2102  new_ct->set_name(ct->name());
2103  for (const int ref : at_most_one) {
2104  new_ct->mutable_at_most_one()->add_literals(ref);
2105  }
2107  }
2108  }
2109 }
2110 
2111 // Convert some linear constraint involving only Booleans to their Boolean
2112 // form.
2113 bool CpModelPresolver::PresolveLinearOnBooleans(ConstraintProto* ct) {
2114  if (ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
2115  context_->ModelIsUnsat()) {
2116  return false;
2117  }
2118 
2119  const LinearConstraintProto& arg = ct->linear();
2120  const int num_vars = arg.vars_size();
2121  int64 min_coeff = kint64max;
2122  int64 max_coeff = 0;
2123  int64 min_sum = 0;
2124  int64 max_sum = 0;
2125  for (int i = 0; i < num_vars; ++i) {
2126  // We assume we already ran PresolveLinear().
2127  const int var = arg.vars(i);
2128  const int64 coeff = arg.coeffs(i);
2130  CHECK_NE(coeff, 0);
2131  if (context_->MinOf(var) != 0) return false;
2132  if (context_->MaxOf(var) != 1) return false;
2133 
2134  if (coeff > 0) {
2135  max_sum += coeff;
2136  min_coeff = std::min(min_coeff, coeff);
2137  max_coeff = std::max(max_coeff, coeff);
2138  } else {
2139  // We replace the Boolean ref, by a ref to its negation (1 - x).
2140  min_sum += coeff;
2141  min_coeff = std::min(min_coeff, -coeff);
2142  max_coeff = std::max(max_coeff, -coeff);
2143  }
2144  }
2145  CHECK_LE(min_coeff, max_coeff);
2146 
2147  // Detect trivially true/false constraints. Note that this is not necessarily
2148  // detected by PresolveLinear(). We do that here because we assume below
2149  // that this cannot happen.
2150  //
2151  // TODO(user): this could be generalized to constraint not containing only
2152  // Booleans.
2153  const Domain rhs_domain = ReadDomainFromProto(arg);
2154  if ((!rhs_domain.Contains(min_sum) &&
2155  min_sum + min_coeff > rhs_domain.Max()) ||
2156  (!rhs_domain.Contains(max_sum) &&
2157  max_sum - min_coeff < rhs_domain.Min())) {
2158  context_->UpdateRuleStats("linear: all booleans and trivially false");
2159  return MarkConstraintAsFalse(ct);
2160  }
2161  if (Domain(min_sum, max_sum).IsIncludedIn(rhs_domain)) {
2162  context_->UpdateRuleStats("linear: all booleans and trivially true");
2163  return RemoveConstraint(ct);
2164  }
2165 
2166  // Detect clauses, reified ands, at_most_one.
2167  //
2168  // TODO(user): split a == 1 constraint or similar into a clause and an at
2169  // most one constraint?
2170  DCHECK(!rhs_domain.IsEmpty());
2171  if (min_sum + min_coeff > rhs_domain.Max()) {
2172  // All Boolean are false if the reified literal is true.
2173  context_->UpdateRuleStats("linear: negative reified and");
2174  const auto copy = arg;
2175  ct->mutable_bool_and()->clear_literals();
2176  for (int i = 0; i < num_vars; ++i) {
2177  ct->mutable_bool_and()->add_literals(
2178  copy.coeffs(i) > 0 ? NegatedRef(copy.vars(i)) : copy.vars(i));
2179  }
2180  return PresolveBoolAnd(ct);
2181  } else if (max_sum - min_coeff < rhs_domain.Min()) {
2182  // All Boolean are true if the reified literal is true.
2183  context_->UpdateRuleStats("linear: positive reified and");
2184  const auto copy = arg;
2185  ct->mutable_bool_and()->clear_literals();
2186  for (int i = 0; i < num_vars; ++i) {
2187  ct->mutable_bool_and()->add_literals(
2188  copy.coeffs(i) > 0 ? copy.vars(i) : NegatedRef(copy.vars(i)));
2189  }
2190  return PresolveBoolAnd(ct);
2191  } else if (min_sum + min_coeff >= rhs_domain.Min() &&
2192  rhs_domain.front().end >= max_sum) {
2193  // At least one Boolean is true.
2194  context_->UpdateRuleStats("linear: positive clause");
2195  const auto copy = arg;
2196  ct->mutable_bool_or()->clear_literals();
2197  for (int i = 0; i < num_vars; ++i) {
2198  ct->mutable_bool_or()->add_literals(
2199  copy.coeffs(i) > 0 ? copy.vars(i) : NegatedRef(copy.vars(i)));
2200  }
2201  return PresolveBoolOr(ct);
2202  } else if (max_sum - min_coeff <= rhs_domain.Max() &&
2203  rhs_domain.back().start <= min_sum) {
2204  // At least one Boolean is false.
2205  context_->UpdateRuleStats("linear: negative clause");
2206  const auto copy = arg;
2207  ct->mutable_bool_or()->clear_literals();
2208  for (int i = 0; i < num_vars; ++i) {
2209  ct->mutable_bool_or()->add_literals(
2210  copy.coeffs(i) > 0 ? NegatedRef(copy.vars(i)) : copy.vars(i));
2211  }
2212  return PresolveBoolOr(ct);
2213  } else if (!HasEnforcementLiteral(*ct) &&
2214  min_sum + max_coeff <= rhs_domain.Max() &&
2215  min_sum + 2 * min_coeff > rhs_domain.Max() &&
2216  rhs_domain.back().start <= min_sum) {
2217  // At most one Boolean is true.
2218  context_->UpdateRuleStats("linear: positive at most one");
2219  const auto copy = arg;
2220  ct->mutable_at_most_one()->clear_literals();
2221  for (int i = 0; i < num_vars; ++i) {
2222  ct->mutable_at_most_one()->add_literals(
2223  copy.coeffs(i) > 0 ? copy.vars(i) : NegatedRef(copy.vars(i)));
2224  }
2225  return true;
2226  } else if (!HasEnforcementLiteral(*ct) &&
2227  max_sum - max_coeff >= rhs_domain.Min() &&
2228  max_sum - 2 * min_coeff < rhs_domain.Min() &&
2229  rhs_domain.front().end >= max_sum) {
2230  // At most one Boolean is false.
2231  context_->UpdateRuleStats("linear: negative at most one");
2232  const auto copy = arg;
2233  ct->mutable_at_most_one()->clear_literals();
2234  for (int i = 0; i < num_vars; ++i) {
2235  ct->mutable_at_most_one()->add_literals(
2236  copy.coeffs(i) > 0 ? NegatedRef(copy.vars(i)) : copy.vars(i));
2237  }
2238  return true;
2239  } else if (!HasEnforcementLiteral(*ct) && rhs_domain.NumIntervals() == 1 &&
2240  min_sum < rhs_domain.Min() &&
2241  min_sum + min_coeff >= rhs_domain.Min() &&
2242  min_sum + 2 * min_coeff > rhs_domain.Max() &&
2243  min_sum + max_coeff <= rhs_domain.Max()) {
2244  context_->UpdateRuleStats("linear: positive equal one");
2245  ConstraintProto* exactly_one = context_->working_model->add_constraints();
2246  exactly_one->set_name(ct->name());
2247  for (int i = 0; i < num_vars; ++i) {
2248  exactly_one->mutable_exactly_one()->add_literals(
2249  arg.coeffs(i) > 0 ? arg.vars(i) : NegatedRef(arg.vars(i)));
2250  }
2252  return RemoveConstraint(ct);
2253  } else if (!HasEnforcementLiteral(*ct) && rhs_domain.NumIntervals() == 1 &&
2254  max_sum > rhs_domain.Max() &&
2255  max_sum - min_coeff <= rhs_domain.Max() &&
2256  max_sum - 2 * min_coeff < rhs_domain.Min() &&
2257  max_sum - max_coeff >= rhs_domain.Min()) {
2258  context_->UpdateRuleStats("linear: negative equal one");
2259  ConstraintProto* exactly_one = context_->working_model->add_constraints();
2260  exactly_one->set_name(ct->name());
2261  for (int i = 0; i < num_vars; ++i) {
2262  exactly_one->mutable_exactly_one()->add_literals(
2263  arg.coeffs(i) > 0 ? NegatedRef(arg.vars(i)) : arg.vars(i));
2264  }
2266  return RemoveConstraint(ct);
2267  }
2268 
2269  // Expand small expression into clause.
2270  //
2271  // TODO(user): This is bad from a LP relaxation perspective. Do not do that
2272  // now? On another hand it is good for the SAT presolving.
2273  if (num_vars > 3) return false;
2274  context_->UpdateRuleStats("linear: small Boolean expression");
2275 
2276  // Enumerate all possible value of the Booleans and add a clause if constraint
2277  // is false. TODO(user): the encoding could be made better in some cases.
2278  const int max_mask = (1 << arg.vars_size());
2279  for (int mask = 0; mask < max_mask; ++mask) {
2280  int64 value = 0;
2281  for (int i = 0; i < num_vars; ++i) {
2282  if ((mask >> i) & 1) value += arg.coeffs(i);
2283  }
2284  if (rhs_domain.Contains(value)) continue;
2285 
2286  // Add a new clause to exclude this bad assignment.
2287  ConstraintProto* new_ct = context_->working_model->add_constraints();
2288  auto* new_arg = new_ct->mutable_bool_or();
2289  if (HasEnforcementLiteral(*ct)) {
2290  *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
2291  }
2292  for (int i = 0; i < num_vars; ++i) {
2293  new_arg->add_literals(((mask >> i) & 1) ? NegatedRef(arg.vars(i))
2294  : arg.vars(i));
2295  }
2296  }
2297 
2299  return RemoveConstraint(ct);
2300 }
2301 
2302 namespace {
2303 
2304 void AddLinearExpression(int64 coeff, const LinearExpressionProto& exp,
2305  LinearConstraintProto* linear_ct) {
2306  const int size = exp.vars().size();
2307  for (int i = 0; i < size; ++i) {
2308  linear_ct->add_vars(exp.vars(i));
2309  linear_ct->add_coeffs(coeff * exp.coeffs(i));
2310  }
2311  if (exp.offset() != 0) {
2313  .AdditionWith(Domain(-coeff * exp.offset())),
2314  linear_ct);
2315  }
2316 }
2317 
2318 } // namespace
2319 
2320 bool CpModelPresolver::PresolveInterval(int c, ConstraintProto* ct) {
2321  if (context_->ModelIsUnsat()) return false;
2322 
2323  if (ct->enforcement_literal().empty() && !ct->interval().has_start_view()) {
2324  bool changed = false;
2325  const int start = ct->interval().start();
2326  const int end = ct->interval().end();
2327  const int size = ct->interval().size();
2328  const Domain start_domain = context_->DomainOf(start);
2329  const Domain end_domain = context_->DomainOf(end);
2330  const Domain size_domain = context_->DomainOf(size);
2331  // Size can't be negative.
2332  if (!context_->IntersectDomainWith(size, Domain(0, context_->MaxOf(size)),
2333  &changed)) {
2334  return false;
2335  }
2336  if (!context_->IntersectDomainWith(
2337  end, start_domain.AdditionWith(size_domain), &changed)) {
2338  return false;
2339  }
2340  if (!context_->IntersectDomainWith(
2341  start, end_domain.AdditionWith(size_domain.Negation()), &changed)) {
2342  return false;
2343  }
2344  if (!context_->IntersectDomainWith(
2345  size, end_domain.AdditionWith(start_domain.Negation()), &changed)) {
2346  return false;
2347  }
2348  if (changed) {
2349  context_->UpdateRuleStats("interval: reduced domains");
2350  }
2351  }
2352 
2353  if (context_->IntervalUsage(c) == 0) {
2354  if (!ct->interval().has_start_view()) {
2355  // Convert to linear.
2356  const int start = ct->interval().start();
2357  const int end = ct->interval().end();
2358  const int size = ct->interval().size();
2359  ConstraintProto* new_ct = context_->working_model->add_constraints();
2360  *(new_ct->mutable_enforcement_literal()) = ct->enforcement_literal();
2361  new_ct->mutable_linear()->add_domain(0);
2362  new_ct->mutable_linear()->add_domain(0);
2363  new_ct->mutable_linear()->add_vars(start);
2364  new_ct->mutable_linear()->add_coeffs(1);
2365  new_ct->mutable_linear()->add_vars(size);
2366  new_ct->mutable_linear()->add_coeffs(1);
2367  new_ct->mutable_linear()->add_vars(end);
2368  new_ct->mutable_linear()->add_coeffs(-1);
2370  }
2371  context_->UpdateRuleStats("interval: unused, converted to linear");
2372  return RemoveConstraint(ct);
2373  }
2374 
2375  // TODO(user): Note that the conversion is not perfect for optional intervals.
2376  // because for a fixed size optional interval with a different start and end
2377  // variable, because of the optionality we will not be able to detect the
2378  // affine relation between start and end. So we will no remove a variable like
2379  // we do for non-optional fixed size intervals.
2380  if (context_->params().convert_intervals()) {
2381  bool changed = false;
2382  IntervalConstraintProto* interval = ct->mutable_interval();
2383  if (!ct->interval().has_start_view()) {
2384  changed = true;
2385 
2386  // Fill the view fields.
2387  interval->mutable_start_view()->add_vars(interval->start());
2388  interval->mutable_start_view()->add_coeffs(1);
2389  interval->mutable_start_view()->set_offset(0);
2390  interval->mutable_size_view()->add_vars(interval->size());
2391  interval->mutable_size_view()->add_coeffs(1);
2392  interval->mutable_size_view()->set_offset(0);
2393  interval->mutable_end_view()->add_vars(interval->end());
2394  interval->mutable_end_view()->add_coeffs(1);
2395  interval->mutable_end_view()->set_offset(0);
2396 
2397  // Create a new linear constraint to detect affine relation and propagate
2398  // the domain properly.
2399  //
2400  // Note(user): Aving an extra constraint is not super clean, but reusing
2401  // the code is a must.
2402  ConstraintProto* new_ct = context_->working_model->add_constraints();
2403  *(new_ct->mutable_enforcement_literal()) = ct->enforcement_literal();
2404  new_ct->mutable_linear()->add_domain(0);
2405  new_ct->mutable_linear()->add_domain(0);
2406  AddLinearExpression(1, interval->start_view(), new_ct->mutable_linear());
2407  AddLinearExpression(1, interval->size_view(), new_ct->mutable_linear());
2408  AddLinearExpression(-1, interval->end_view(), new_ct->mutable_linear());
2410 
2411  // Set the old fields to their default. Not really needed.
2412  interval->set_start(0);
2413  interval->set_size(0);
2414  interval->set_end(0);
2415  }
2416 
2417  changed |=
2418  CanonicalizeLinearExpression(*ct, interval->mutable_start_view());
2419  changed |= CanonicalizeLinearExpression(*ct, interval->mutable_size_view());
2420  changed |= CanonicalizeLinearExpression(*ct, interval->mutable_end_view());
2421  return changed;
2422  }
2423 
2424  // If the interval is of fixed size, we can add the corresponsing affine
2425  // relation to our pool.
2426  //
2427  // TODO(user): This will currently add another linear relation to the proto
2428  // in addition to the interval at the end of the presolve though.
2429  if (/* DISABLES CODE */ (false) && ct->enforcement_literal().empty() &&
2430  context_->IsFixed(ct->interval().size())) {
2431  context_->StoreAffineRelation(ct->interval().end(), ct->interval().start(),
2432  1, context_->MinOf(ct->interval().size()));
2433  }
2434 
2435  // This never change the constraint-variable graph.
2436  return false;
2437 }
2438 
2439 bool CpModelPresolver::PresolveElement(ConstraintProto* ct) {
2440  if (context_->ModelIsUnsat()) return false;
2441 
2442  const int index_ref = ct->element().index();
2443  const int target_ref = ct->element().target();
2444 
2445  // TODO(user): think about this once we do have such constraint.
2446  if (HasEnforcementLiteral(*ct)) return false;
2447 
2448  bool all_constants = true;
2449  absl::flat_hash_set<int64> constant_set;
2450  bool all_included_in_target_domain = true;
2451 
2452  {
2453  bool reduced_index_domain = false;
2454  if (!context_->IntersectDomainWith(index_ref,
2455  Domain(0, ct->element().vars_size() - 1),
2456  &reduced_index_domain)) {
2457  return false;
2458  }
2459 
2460  // Filter impossible index values if index == +/- target
2461  //
2462  // Note that this must be done before the unique_index/target rule.
2463  if (PositiveRef(target_ref) == PositiveRef(index_ref)) {
2464  std::vector<int64> possible_indices;
2465  const Domain& index_domain = context_->DomainOf(index_ref);
2466  for (const ClosedInterval& interval : index_domain) {
2467  for (int64 index_value = interval.start; index_value <= interval.end;
2468  ++index_value) {
2469  const int ref = ct->element().vars(index_value);
2470  const int64 target_value =
2471  target_ref == index_ref ? index_value : -index_value;
2472  if (context_->DomainContains(ref, target_value)) {
2473  possible_indices.push_back(target_value);
2474  }
2475  }
2476  }
2477  if (possible_indices.size() < index_domain.Size()) {
2478  if (!context_->IntersectDomainWith(
2479  index_ref, Domain::FromValues(possible_indices))) {
2480  return true;
2481  }
2482  context_->UpdateRuleStats(
2483  "element: reduced index domain when target equals index");
2484  }
2485  }
2486 
2487  // Filter possible index values. Accumulate variable domains to build
2488  // a possible target domain.
2489  Domain infered_domain;
2490  const Domain& initial_index_domain = context_->DomainOf(index_ref);
2491  const Domain& target_domain = context_->DomainOf(target_ref);
2492  std::vector<int64> possible_indices;
2493  for (const ClosedInterval interval : initial_index_domain) {
2494  for (int value = interval.start; value <= interval.end; ++value) {
2495  CHECK_GE(value, 0);
2496  CHECK_LT(value, ct->element().vars_size());
2497  const int ref = ct->element().vars(value);
2498  const Domain& domain = context_->DomainOf(ref);
2499  if (domain.IntersectionWith(target_domain).IsEmpty()) continue;
2500  possible_indices.push_back(value);
2501  if (domain.IsFixed()) {
2502  constant_set.insert(domain.Min());
2503  } else {
2504  all_constants = false;
2505  }
2506  if (!domain.IsIncludedIn(target_domain)) {
2507  all_included_in_target_domain = false;
2508  }
2509  infered_domain = infered_domain.UnionWith(domain);
2510  }
2511  }
2512  if (possible_indices.size() < initial_index_domain.Size()) {
2513  if (!context_->IntersectDomainWith(
2514  index_ref, Domain::FromValues(possible_indices))) {
2515  return true;
2516  }
2517  context_->UpdateRuleStats("element: reduced index domain");
2518  }
2519  bool domain_modified = false;
2520  if (!context_->IntersectDomainWith(target_ref, infered_domain,
2521  &domain_modified)) {
2522  return true;
2523  }
2524  if (domain_modified) {
2525  context_->UpdateRuleStats("element: reduced target domain");
2526  }
2527  }
2528 
2529  // If the index is fixed, this is a equality constraint.
2530  if (context_->IsFixed(index_ref)) {
2531  const int var = ct->element().vars(context_->MinOf(index_ref));
2532  if (var != target_ref) {
2533  LinearConstraintProto* const lin =
2534  context_->working_model->add_constraints()->mutable_linear();
2535  lin->add_vars(var);
2536  lin->add_coeffs(-1);
2537  lin->add_vars(target_ref);
2538  lin->add_coeffs(1);
2539  lin->add_domain(0);
2540  lin->add_domain(0);
2542  }
2543  context_->UpdateRuleStats("element: fixed index");
2544  return RemoveConstraint(ct);
2545  }
2546 
2547  // If the accessible part of the array is made of a single constant value,
2548  // then we do not care about the index. And, because of the previous target
2549  // domain reduction, the target is also fixed.
2550  if (all_constants && constant_set.size() == 1) {
2551  CHECK(context_->IsFixed(target_ref));
2552  context_->UpdateRuleStats("element: one value array");
2553  return RemoveConstraint(ct);
2554  }
2555 
2556  // Special case when the index is boolean, and the array does not contain
2557  // variables.
2558  if (context_->MinOf(index_ref) == 0 && context_->MaxOf(index_ref) == 1 &&
2559  all_constants) {
2560  const int64 v0 = context_->MinOf(ct->element().vars(0));
2561  const int64 v1 = context_->MinOf(ct->element().vars(1));
2562 
2563  LinearConstraintProto* const lin =
2564  context_->working_model->add_constraints()->mutable_linear();
2565  lin->add_vars(target_ref);
2566  lin->add_coeffs(1);
2567  lin->add_vars(index_ref);
2568  lin->add_coeffs(v0 - v1);
2569  lin->add_domain(v0);
2570  lin->add_domain(v0);
2572  context_->UpdateRuleStats("element: linearize constant element of size 2");
2573  return RemoveConstraint(ct);
2574  }
2575 
2576  // If the index has a canonical affine representative, rewrite the element.
2577  const AffineRelation::Relation r_index =
2578  context_->GetAffineRelation(index_ref);
2579  if (r_index.representative != index_ref) {
2580  // Checks the domains are synchronized.
2581  if (context_->DomainOf(r_index.representative).Size() >
2582  context_->DomainOf(index_ref).Size()) {
2583  // Postpone, we will come back later when domains are synchronized.
2584  return true;
2585  }
2586  const int r_ref = r_index.representative;
2587  const int64 r_min = context_->MinOf(r_ref);
2588  const int64 r_max = context_->MaxOf(r_ref);
2589  const int array_size = ct->element().vars_size();
2590  if (r_min != 0) {
2591  context_->UpdateRuleStats("TODO element: representative has bad domain");
2592  } else if (r_index.offset >= 0 && r_index.offset < array_size &&
2593  r_index.offset + r_max * r_index.coeff >= 0 &&
2594  r_index.offset + r_max * r_index.coeff < array_size) {
2595  // This will happen eventually when domains are synchronized.
2596  ElementConstraintProto* const element =
2597  context_->working_model->add_constraints()->mutable_element();
2598  for (int64 v = 0; v <= r_max; ++v) {
2599  const int64 scaled_index = v * r_index.coeff + r_index.offset;
2600  CHECK_GE(scaled_index, 0);
2601  CHECK_LT(scaled_index, array_size);
2602  element->add_vars(ct->element().vars(scaled_index));
2603  }
2604  element->set_index(r_ref);
2605  element->set_target(target_ref);
2606 
2607  if (r_index.coeff == 1) {
2608  context_->UpdateRuleStats("element: shifed index ");
2609  } else {
2610  context_->UpdateRuleStats("element: scaled index");
2611  }
2613  return RemoveConstraint(ct);
2614  }
2615  }
2616 
2617  const bool unique_index = context_->VariableIsUniqueAndRemovable(index_ref) ||
2618  context_->IsFixed(index_ref);
2619  if (all_constants && unique_index) {
2620  // This constraint is just here to reduce the domain of the target! We can
2621  // add it to the mapping_model to reconstruct the index value during
2622  // postsolve and get rid of it now.
2623  context_->UpdateRuleStats("element: trivial target domain reduction");
2624  context_->MarkVariableAsRemoved(index_ref);
2625  *(context_->mapping_model->add_constraints()) = *ct;
2626  return RemoveConstraint(ct);
2627  }
2628 
2629  const bool unique_target =
2630  context_->VariableIsUniqueAndRemovable(target_ref) ||
2631  context_->IsFixed(target_ref);
2632  if (all_included_in_target_domain && unique_target) {
2633  context_->UpdateRuleStats("element: trivial index domain reduction");
2634  context_->MarkVariableAsRemoved(target_ref);
2635  *(context_->mapping_model->add_constraints()) = *ct;
2636  return RemoveConstraint(ct);
2637  }
2638 
2639  if (unique_target && !context_->IsFixed(target_ref)) {
2640  context_->UpdateRuleStats("TODO element: target not used elsewhere");
2641  }
2642  if (unique_index) {
2643  context_->UpdateRuleStats("TODO element: index not used elsewhere");
2644  }
2645 
2646  return false;
2647 }
2648 
2649 bool CpModelPresolver::PresolveTable(ConstraintProto* ct) {
2650  if (context_->ModelIsUnsat()) return false;
2651  if (HasEnforcementLiteral(*ct)) return false;
2652  if (ct->table().vars().empty()) {
2653  context_->UpdateRuleStats("table: empty constraint");
2654  return RemoveConstraint(ct);
2655  }
2656 
2657  // Filter the unreachable tuples.
2658  //
2659  // TODO(user): this is not supper efficient. Optimize if needed.
2660  const int num_vars = ct->table().vars_size();
2661  const int num_tuples = ct->table().values_size() / num_vars;
2662  std::vector<int64> tuple(num_vars);
2663  std::vector<std::vector<int64>> new_tuples;
2664  new_tuples.reserve(num_tuples);
2665  std::vector<absl::flat_hash_set<int64>> new_domains(num_vars);
2666  std::vector<AffineRelation::Relation> affine_relations;
2667 
2668  absl::flat_hash_set<int> visited;
2669  for (const int ref : ct->table().vars()) {
2670  if (visited.contains(PositiveRef(ref))) {
2671  context_->UpdateRuleStats("TODO table: duplicate variables");
2672  } else {
2673  visited.insert(PositiveRef(ref));
2674  }
2675  }
2676 
2677  bool modified_variables = false;
2678  for (int v = 0; v < num_vars; ++v) {
2679  const int ref = ct->table().vars(v);
2680  AffineRelation::Relation r = context_->GetAffineRelation(ref);
2681  affine_relations.push_back(r);
2682  if (r.representative != ref) {
2683  modified_variables = true;
2684  }
2685  }
2686 
2687  for (int i = 0; i < num_tuples; ++i) {
2688  bool delete_row = false;
2689  std::string tmp;
2690  for (int j = 0; j < num_vars; ++j) {
2691  const int ref = ct->table().vars(j);
2692  int64 v = ct->table().values(i * num_vars + j);
2693  const AffineRelation::Relation& r = affine_relations[j];
2694  if (r.representative != ref) {
2695  const int64 inverse_value = (v - r.offset) / r.coeff;
2696  if (inverse_value * r.coeff + r.offset != v) {
2697  // Bad rounding.
2698  delete_row = true;
2699  break;
2700  }
2701  v = inverse_value;
2702  }
2703  tuple[j] = v;
2704  if (!context_->DomainContains(r.representative, v)) {
2705  delete_row = true;
2706  break;
2707  }
2708  }
2709  if (delete_row) continue;
2710  new_tuples.push_back(tuple);
2711  for (int j = 0; j < num_vars; ++j) {
2712  const int64 v = tuple[j];
2713  new_domains[j].insert(v);
2714  }
2715  }
2716  gtl::STLSortAndRemoveDuplicates(&new_tuples);
2717 
2718  // Update the list of tuples if needed.
2719  if (new_tuples.size() < num_tuples || modified_variables) {
2720  ct->mutable_table()->clear_values();
2721  for (const std::vector<int64>& t : new_tuples) {
2722  for (const int64 v : t) {
2723  ct->mutable_table()->add_values(v);
2724  }
2725  }
2726  if (new_tuples.size() < num_tuples) {
2727  context_->UpdateRuleStats("table: removed rows");
2728  }
2729  }
2730 
2731  if (modified_variables) {
2732  for (int j = 0; j < num_vars; ++j) {
2733  const AffineRelation::Relation& r = affine_relations[j];
2734  if (r.representative != ct->table().vars(j)) {
2735  ct->mutable_table()->set_vars(j, r.representative);
2736  }
2737  }
2738  context_->UpdateRuleStats(
2739  "table: replace variable by canonical affine one");
2740  }
2741 
2742  // Nothing more to do for negated tables.
2743  if (ct->table().negated()) return modified_variables;
2744 
2745  // Filter the variable domains.
2746  bool changed = false;
2747  for (int j = 0; j < num_vars; ++j) {
2748  const int ref = ct->table().vars(j);
2749  if (!context_->IntersectDomainWith(
2750  PositiveRef(ref),
2751  Domain::FromValues(std::vector<int64>(new_domains[j].begin(),
2752  new_domains[j].end())),
2753  &changed)) {
2754  return true;
2755  }
2756  }
2757  if (changed) {
2758  context_->UpdateRuleStats("table: reduced variable domains");
2759  }
2760  if (num_vars == 1) {
2761  // Now that we properly update the domain, we can remove the constraint.
2762  context_->UpdateRuleStats("table: only one column!");
2763  return RemoveConstraint(ct);
2764  }
2765 
2766  // Check that the table is not complete or just here to exclude a few tuples.
2767  double prod = 1.0;
2768  for (int j = 0; j < num_vars; ++j) prod *= new_domains[j].size();
2769  if (prod == new_tuples.size()) {
2770  context_->UpdateRuleStats("table: all tuples!");
2771  return RemoveConstraint(ct);
2772  }
2773 
2774  // Convert to the negated table if we gain a lot of entries by doing so.
2775  // Note however that currently the negated table do not propagate as much as
2776  // it could.
2777  if (new_tuples.size() > 0.7 * prod) {
2778  // Enumerate all tuples.
2779  std::vector<std::vector<int64>> var_to_values(num_vars);
2780  for (int j = 0; j < num_vars; ++j) {
2781  var_to_values[j].assign(new_domains[j].begin(), new_domains[j].end());
2782  }
2783  std::vector<std::vector<int64>> all_tuples(prod);
2784  for (int i = 0; i < prod; ++i) {
2785  all_tuples[i].resize(num_vars);
2786  int index = i;
2787  for (int j = 0; j < num_vars; ++j) {
2788  all_tuples[i][j] = var_to_values[j][index % var_to_values[j].size()];
2789  index /= var_to_values[j].size();
2790  }
2791  }
2792  gtl::STLSortAndRemoveDuplicates(&all_tuples);
2793 
2794  // Compute the complement of new_tuples.
2795  std::vector<std::vector<int64>> diff(prod - new_tuples.size());
2796  std::set_difference(all_tuples.begin(), all_tuples.end(),
2797  new_tuples.begin(), new_tuples.end(), diff.begin());
2798 
2799  // Negate the constraint.
2800  ct->mutable_table()->set_negated(!ct->table().negated());
2801  ct->mutable_table()->clear_values();
2802  for (const std::vector<int64>& t : diff) {
2803  for (const int64 v : t) ct->mutable_table()->add_values(v);
2804  }
2805  context_->UpdateRuleStats("table: negated");
2806  }
2807  return modified_variables;
2808 }
2809 
2810 bool CpModelPresolver::PresolveAllDiff(ConstraintProto* ct) {
2811  if (context_->ModelIsUnsat()) return false;
2812  if (HasEnforcementLiteral(*ct)) return false;
2813 
2814  AllDifferentConstraintProto& all_diff = *ct->mutable_all_diff();
2815 
2816  bool constraint_has_changed = false;
2817  for (;;) {
2818  const int size = all_diff.vars_size();
2819  if (size == 0) {
2820  context_->UpdateRuleStats("all_diff: empty constraint");
2821  return RemoveConstraint(ct);
2822  }
2823  if (size == 1) {
2824  context_->UpdateRuleStats("all_diff: only one variable");
2825  return RemoveConstraint(ct);
2826  }
2827 
2828  bool something_was_propagated = false;
2829  std::vector<int> new_variables;
2830  for (int i = 0; i < size; ++i) {
2831  if (!context_->IsFixed(all_diff.vars(i))) {
2832  new_variables.push_back(all_diff.vars(i));
2833  continue;
2834  }
2835 
2836  const int64 value = context_->MinOf(all_diff.vars(i));
2837  bool propagated = false;
2838  for (int j = 0; j < size; ++j) {
2839  if (i == j) continue;
2840  if (context_->DomainContains(all_diff.vars(j), value)) {
2841  if (!context_->IntersectDomainWith(all_diff.vars(j),
2842  Domain(value).Complement())) {
2843  return true;
2844  }
2845  propagated = true;
2846  }
2847  }
2848  if (propagated) {
2849  context_->UpdateRuleStats("all_diff: propagated fixed variables");
2850  something_was_propagated = true;
2851  }
2852  }
2853 
2854  std::sort(new_variables.begin(), new_variables.end());
2855  for (int i = 1; i < new_variables.size(); ++i) {
2856  if (new_variables[i] == new_variables[i - 1]) {
2857  return context_->NotifyThatModelIsUnsat(
2858  "Duplicate variable in all_diff");
2859  }
2860  }
2861 
2862  if (new_variables.size() < all_diff.vars_size()) {
2863  all_diff.mutable_vars()->Clear();
2864  for (const int var : new_variables) {
2865  all_diff.add_vars(var);
2866  }
2867  context_->UpdateRuleStats("all_diff: removed fixed variables");
2868  something_was_propagated = true;
2869  constraint_has_changed = true;
2870  if (new_variables.size() <= 1) continue;
2871  }
2872 
2873  // Propagate mandatory value if the all diff is actually a permutation.
2874  CHECK_GE(all_diff.vars_size(), 2);
2875  Domain domain = context_->DomainOf(all_diff.vars(0));
2876  for (int i = 1; i < all_diff.vars_size(); ++i) {
2877  domain = domain.UnionWith(context_->DomainOf(all_diff.vars(i)));
2878  }
2879  if (all_diff.vars_size() == domain.Size()) {
2880  absl::flat_hash_map<int64, std::vector<int>> value_to_refs;
2881  for (const int ref : all_diff.vars()) {
2882  for (const ClosedInterval& interval : context_->DomainOf(ref)) {
2883  for (int64 v = interval.start; v <= interval.end; ++v) {
2884  value_to_refs[v].push_back(ref);
2885  }
2886  }
2887  }
2888  bool propagated = false;
2889  for (const auto& it : value_to_refs) {
2890  if (it.second.size() == 1 &&
2891  context_->DomainOf(it.second.front()).Size() > 1) {
2892  const int ref = it.second.front();
2893  if (!context_->IntersectDomainWith(ref, Domain(it.first))) {
2894  return true;
2895  }
2896  propagated = true;
2897  }
2898  }
2899  if (propagated) {
2900  context_->UpdateRuleStats(
2901  "all_diff: propagated mandatory values in permutation");
2902  something_was_propagated = true;
2903  }
2904  }
2905  if (!something_was_propagated) break;
2906  }
2907 
2908  return constraint_has_changed;
2909 }
2910 
2911 namespace {
2912 
2913 // Returns the sorted list of literals for given bool_or or at_most_one
2914 // constraint.
2915 std::vector<int> GetLiteralsFromSetPPCConstraint(const ConstraintProto& ct) {
2916  std::vector<int> sorted_literals;
2917  if (ct.constraint_case() == ConstraintProto::kAtMostOne) {
2918  for (const int literal : ct.at_most_one().literals()) {
2919  sorted_literals.push_back(literal);
2920  }
2921  } else if (ct.constraint_case() == ConstraintProto::kBoolOr) {
2922  for (const int literal : ct.bool_or().literals()) {
2923  sorted_literals.push_back(literal);
2924  }
2925  } else if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
2926  for (const int literal : ct.exactly_one().literals()) {
2927  sorted_literals.push_back(literal);
2928  }
2929  }
2930  std::sort(sorted_literals.begin(), sorted_literals.end());
2931  return sorted_literals;
2932 }
2933 
2934 // Add the constraint (lhs => rhs) to the given proto. The hash map lhs ->
2935 // bool_and constraint index is used to merge implications with the same lhs.
2936 void AddImplication(int lhs, int rhs, CpModelProto* proto,
2937  absl::flat_hash_map<int, int>* ref_to_bool_and) {
2938  if (ref_to_bool_and->contains(lhs)) {
2939  const int ct_index = (*ref_to_bool_and)[lhs];
2940  proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(rhs);
2941  } else if (ref_to_bool_and->contains(NegatedRef(rhs))) {
2942  const int ct_index = (*ref_to_bool_and)[NegatedRef(rhs)];
2943  proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(
2944  NegatedRef(lhs));
2945  } else {
2946  (*ref_to_bool_and)[lhs] = proto->constraints_size();
2947  ConstraintProto* ct = proto->add_constraints();
2948  ct->add_enforcement_literal(lhs);
2949  ct->mutable_bool_and()->add_literals(rhs);
2950  }
2951 }
2952 
2953 template <typename ClauseContainer>
2954 void ExtractClauses(bool use_bool_and, const ClauseContainer& container,
2955  CpModelProto* proto) {
2956  // We regroup the "implication" into bool_and to have a more consise proto and
2957  // also for nicer information about the number of binary clauses.
2958  //
2959  // Important: however, we do not do that for the model used during presolving
2960  // since the order of the constraints might be important there depending on
2961  // how we perform the postsolve.
2962  absl::flat_hash_map<int, int> ref_to_bool_and;
2963  for (int i = 0; i < container.NumClauses(); ++i) {
2964  const std::vector<Literal>& clause = container.Clause(i);
2965  if (clause.empty()) continue;
2966 
2967  // bool_and.
2968  if (use_bool_and && clause.size() == 2) {
2969  const int a = clause[0].IsPositive()
2970  ? clause[0].Variable().value()
2971  : NegatedRef(clause[0].Variable().value());
2972  const int b = clause[1].IsPositive()
2973  ? clause[1].Variable().value()
2974  : NegatedRef(clause[1].Variable().value());
2975  AddImplication(NegatedRef(a), b, proto, &ref_to_bool_and);
2976  continue;
2977  }
2978 
2979  // bool_or.
2980  ConstraintProto* ct = proto->add_constraints();
2981  for (const Literal l : clause) {
2982  if (l.IsPositive()) {
2983  ct->mutable_bool_or()->add_literals(l.Variable().value());
2984  } else {
2985  ct->mutable_bool_or()->add_literals(NegatedRef(l.Variable().value()));
2986  }
2987  }
2988  }
2989 }
2990 
2991 } // namespace
2992 
2993 int64 CpModelPresolver::StartMin(
2994  const IntervalConstraintProto& interval) const {
2995  if (interval.has_start_view()) return context_->MinOf(interval.start_view());
2996  return context_->MinOf(interval.start());
2997 }
2998 
2999 int64 CpModelPresolver::EndMax(const IntervalConstraintProto& interval) const {
3000  if (interval.has_end_view()) return context_->MaxOf(interval.end_view());
3001  return context_->MaxOf(interval.end());
3002 }
3003 
3004 int64 CpModelPresolver::SizeMin(const IntervalConstraintProto& interval) const {
3005  if (interval.has_size_view()) return context_->MinOf(interval.size_view());
3006  return context_->MinOf(interval.size());
3007 }
3008 
3009 int64 CpModelPresolver::SizeMax(const IntervalConstraintProto& interval) const {
3010  if (interval.has_size_view()) return context_->MaxOf(interval.size_view());
3011  return context_->MaxOf(interval.size());
3012 }
3013 
3014 bool CpModelPresolver::PresolveNoOverlap(ConstraintProto* ct) {
3015  if (context_->ModelIsUnsat()) return false;
3016  const NoOverlapConstraintProto& proto = ct->no_overlap();
3017  const int initial_num_intervals = proto.intervals_size();
3018 
3019  // Filter absent intervals.
3020  int new_size = 0;
3021  for (int i = 0; i < proto.intervals_size(); ++i) {
3022  const int interval_index = proto.intervals(i);
3023  if (context_->working_model->constraints(interval_index)
3024  .constraint_case() ==
3025  ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
3026  continue;
3027  }
3028  ct->mutable_no_overlap()->set_intervals(new_size++, interval_index);
3029  }
3030  ct->mutable_no_overlap()->mutable_intervals()->Truncate(new_size);
3031 
3032  // Sort by start min.
3033  std::sort(
3034  ct->mutable_no_overlap()->mutable_intervals()->begin(),
3035  ct->mutable_no_overlap()->mutable_intervals()->end(),
3036  [this](int i1, int i2) {
3037  return StartMin(context_->working_model->constraints(i1).interval()) <
3038  StartMin(context_->working_model->constraints(i2).interval());
3039  });
3040 
3041  // Remove intervals that cannot overlap any others.
3042  //
3043  // TODO(user): We might also want to split this constraints into many
3044  // independent no overlap constraints.
3045  int64 end_max_so_far = kint64min;
3046  new_size = 0;
3047  for (int i = 0; i < proto.intervals_size(); ++i) {
3048  const int interval_index = proto.intervals(i);
3049  const IntervalConstraintProto& interval =
3050  context_->working_model->constraints(interval_index).interval();
3051  const int64 end_max_of_previous_intervals = end_max_so_far;
3052  end_max_so_far = std::max(end_max_so_far, EndMax(interval));
3053  if (StartMin(interval) >= end_max_of_previous_intervals &&
3054  (i + 1 == proto.intervals_size() ||
3055  end_max_so_far <= StartMin(context_->working_model
3056  ->constraints(proto.intervals(i + 1))
3057  .interval()))) {
3058  context_->UpdateRuleStats("no_overlap: removed redundant intervals");
3059  continue;
3060  }
3061  ct->mutable_no_overlap()->set_intervals(new_size++, interval_index);
3062  }
3063  ct->mutable_no_overlap()->mutable_intervals()->Truncate(new_size);
3064 
3065  if (proto.intervals_size() == 1) {
3066  context_->UpdateRuleStats("no_overlap: only one interval");
3067  return RemoveConstraint(ct);
3068  }
3069  if (proto.intervals().empty()) {
3070  context_->UpdateRuleStats("no_overlap: no intervals");
3071  return RemoveConstraint(ct);
3072  }
3073 
3074  return new_size < initial_num_intervals;
3075 }
3076 
3077 bool CpModelPresolver::PresolveCumulative(ConstraintProto* ct) {
3078  if (context_->ModelIsUnsat()) return false;
3079 
3080  const CumulativeConstraintProto& proto = ct->cumulative();
3081 
3082  // Filter absent intervals.
3083  int new_size = 0;
3084  bool changed = false;
3085  int num_zero_demand_removed = 0;
3086  for (int i = 0; i < proto.intervals_size(); ++i) {
3087  if (context_->working_model->constraints(proto.intervals(i))
3088  .constraint_case() ==
3089  ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
3090  continue;
3091  }
3092 
3093  const int demand_ref = proto.demands(i);
3094  const int64 demand_max = context_->MaxOf(demand_ref);
3095  if (demand_max == 0) {
3096  num_zero_demand_removed++;
3097  continue;
3098  }
3099 
3100  ct->mutable_cumulative()->set_intervals(new_size, proto.intervals(i));
3101  ct->mutable_cumulative()->set_demands(new_size, proto.demands(i));
3102  new_size++;
3103  }
3104  if (new_size < proto.intervals_size()) {
3105  changed = true;
3106  ct->mutable_cumulative()->mutable_intervals()->Truncate(new_size);
3107  ct->mutable_cumulative()->mutable_demands()->Truncate(new_size);
3108  }
3109 
3110  if (num_zero_demand_removed > 0) {
3111  context_->UpdateRuleStats("cumulative: removed intervals with no demands");
3112  }
3113 
3114  if (new_size == 0) {
3115  context_->UpdateRuleStats("cumulative: no intervals");
3116  return RemoveConstraint(ct);
3117  }
3118 
3119  if (HasEnforcementLiteral(*ct)) return changed;
3120  if (!context_->IsFixed(proto.capacity())) return changed;
3121  const int64 capacity = context_->MinOf(proto.capacity());
3122 
3123  const int num_intervals = proto.intervals_size();
3124  bool with_start_view = false;
3125  std::vector<int> start_refs(num_intervals, -1);
3126 
3127  int num_duration_one = 0;
3128  int num_greater_half_capacity = 0;
3129 
3130  bool has_optional_interval = false;
3131  for (int i = 0; i < num_intervals; ++i) {
3132  // TODO(user): adapt in the presence of optional intervals.
3133  const ConstraintProto& ct =
3134  context_->working_model->constraints(proto.intervals(i));
3135  if (!ct.enforcement_literal().empty()) has_optional_interval = true;
3136  const IntervalConstraintProto& interval = ct.interval();
3137  if (interval.has_start_view()) with_start_view = true;
3138  start_refs[i] = interval.start();
3139  const int demand_ref = proto.demands(i);
3140  if (SizeMin(interval) == 1 && SizeMax(interval) == 1) {
3141  num_duration_one++;
3142  }
3143  if (SizeMin(interval) == 0) {
3144  // The behavior for zero-duration interval is currently not the same in
3145  // the no-overlap and the cumulative constraint.
3146  return changed;
3147  }
3148  const int64 demand_min = context_->MinOf(demand_ref);
3149  const int64 demand_max = context_->MaxOf(demand_ref);
3150  if (demand_min > capacity / 2) {
3151  num_greater_half_capacity++;
3152  }
3153  if (demand_min > capacity) {
3154  context_->UpdateRuleStats("cumulative: demand_min exceeds capacity");
3155  if (ct.enforcement_literal().empty()) {
3156  return context_->NotifyThatModelIsUnsat();
3157  } else {
3158  CHECK_EQ(ct.enforcement_literal().size(), 1);
3159  if (!context_->SetLiteralToFalse(ct.enforcement_literal(0))) {
3160  return true;
3161  }
3162  }
3163  return changed;
3164  } else if (demand_max > capacity) {
3165  if (ct.enforcement_literal().empty()) {
3166  context_->UpdateRuleStats("cumulative: demand_max exceeds capacity.");
3167  if (!context_->IntersectDomainWith(demand_ref,
3168  Domain(kint64min, capacity))) {
3169  return true;
3170  }
3171  } else {
3172  // TODO(user): we abort because we cannot convert this to a no_overlap
3173  // for instance.
3174  context_->UpdateRuleStats(
3175  "cumulative: demand_max of optional interval exceeds capacity.");
3176  return changed;
3177  }
3178  }
3179  }
3180 
3181  if (num_greater_half_capacity == num_intervals) {
3182  if (num_duration_one == num_intervals && !has_optional_interval &&
3183  !with_start_view) {
3184  context_->UpdateRuleStats("cumulative: convert to all_different");
3185  ConstraintProto* new_ct = context_->working_model->add_constraints();
3186  auto* arg = new_ct->mutable_all_diff();
3187  for (const int var : start_refs) arg->add_vars(var);
3189  return RemoveConstraint(ct);
3190  } else {
3191  context_->UpdateRuleStats("cumulative: convert to no_overlap");
3192  ConstraintProto* new_ct = context_->working_model->add_constraints();
3193  auto* arg = new_ct->mutable_no_overlap();
3194  for (const int interval : proto.intervals()) {
3195  arg->add_intervals(interval);
3196  }
3198  return RemoveConstraint(ct);
3199  }
3200  }
3201 
3202  return changed;
3203 }
3204 
3205 bool CpModelPresolver::PresolveRoutes(ConstraintProto* ct) {
3206  if (context_->ModelIsUnsat()) return false;
3207  if (HasEnforcementLiteral(*ct)) return false;
3208  RoutesConstraintProto& proto = *ct->mutable_routes();
3209 
3210  int new_size = 0;
3211  const int num_arcs = proto.literals_size();
3212  for (int i = 0; i < num_arcs; ++i) {
3213  const int ref = proto.literals(i);
3214  const int tail = proto.tails(i);
3215  const int head = proto.heads(i);
3216  if (context_->LiteralIsFalse(ref)) {
3217  context_->UpdateRuleStats("routes: removed false arcs");
3218  continue;
3219  }
3220  proto.set_literals(new_size, ref);
3221  proto.set_tails(new_size, tail);
3222  proto.set_heads(new_size, head);
3223  ++new_size;
3224  }
3225  if (new_size < num_arcs) {
3226  proto.mutable_literals()->Truncate(new_size);
3227  proto.mutable_tails()->Truncate(new_size);
3228  proto.mutable_heads()->Truncate(new_size);
3229  return true;
3230  }
3231  return false;
3232 }
3233 
3234 bool CpModelPresolver::PresolveCircuit(ConstraintProto* ct) {
3235  if (context_->ModelIsUnsat()) return false;
3236  if (HasEnforcementLiteral(*ct)) return false;
3237  CircuitConstraintProto& proto = *ct->mutable_circuit();
3238 
3239  // The indexing might not be dense, so fix that first.
3240  ReindexArcs(ct->mutable_circuit()->mutable_tails(),
3241  ct->mutable_circuit()->mutable_heads());
3242 
3243  // Convert the flat structure to a graph, note that we includes all the arcs
3244  // here (even if they are at false).
3245  std::vector<std::vector<int>> incoming_arcs;
3246  std::vector<std::vector<int>> outgoing_arcs;
3247  int num_nodes = 0;
3248  const int num_arcs = proto.literals_size();
3249  for (int i = 0; i < num_arcs; ++i) {
3250  const int ref = proto.literals(i);
3251  const int tail = proto.tails(i);
3252  const int head = proto.heads(i);
3253  num_nodes = std::max(num_nodes, std::max(tail, head) + 1);
3254  if (std::max(tail, head) >= incoming_arcs.size()) {
3255  incoming_arcs.resize(std::max(tail, head) + 1);
3256  outgoing_arcs.resize(std::max(tail, head) + 1);
3257  }
3258  incoming_arcs[head].push_back(ref);
3259  outgoing_arcs[tail].push_back(ref);
3260  }
3261 
3262  // Note that it is important to reach the fixed point here:
3263  // One arc at true, then all other arc at false. This is because we rely
3264  // on this in case the circuit is fully specified below.
3265  //
3266  // TODO(user): Use a better complexity if needed.
3267  bool loop_again = true;
3268  int num_fixed_at_true = 0;
3269  while (loop_again) {
3270  loop_again = false;
3271  for (const auto* node_to_refs : {&incoming_arcs, &outgoing_arcs}) {
3272  for (const std::vector<int>& refs : *node_to_refs) {
3273  if (refs.size() == 1) {
3274  if (!context_->LiteralIsTrue(refs.front())) {
3275  ++num_fixed_at_true;
3276  if (!context_->SetLiteralToTrue(refs.front())) return true;
3277  }
3278  continue;
3279  }
3280 
3281  // At most one true, so if there is one, mark all the other to false.
3282  int num_true = 0;
3283  int true_ref;
3284  for (const int ref : refs) {
3285  if (context_->LiteralIsTrue(ref)) {
3286  ++num_true;
3287  true_ref = ref;
3288  break;
3289  }
3290  }
3291  if (num_true > 1) {
3292  return context_->NotifyThatModelIsUnsat();
3293  }
3294  if (num_true == 1) {
3295  for (const int ref : refs) {
3296  if (ref != true_ref) {
3297  if (!context_->IsFixed(ref)) {
3298  context_->UpdateRuleStats("circuit: set literal to false.");
3299  loop_again = true;
3300  }
3301  if (!context_->SetLiteralToFalse(ref)) return true;
3302  }
3303  }
3304  }
3305  }
3306  }
3307  }
3308  if (num_fixed_at_true > 0) {
3309  context_->UpdateRuleStats("circuit: fixed singleton arcs.");
3310  }
3311 
3312  // Remove false arcs.
3313  int new_size = 0;
3314  int num_true = 0;
3315  int circuit_start = -1;
3316  std::vector<int> next(num_nodes, -1);
3317  std::vector<int> new_in_degree(num_nodes, 0);
3318  std::vector<int> new_out_degree(num_nodes, 0);
3319  for (int i = 0; i < num_arcs; ++i) {
3320  const int ref = proto.literals(i);
3321  if (context_->LiteralIsFalse(ref)) continue;
3322  if (context_->LiteralIsTrue(ref)) {
3323  if (next[proto.tails(i)] != -1) {
3324  return context_->NotifyThatModelIsUnsat();
3325  }
3326  next[proto.tails(i)] = proto.heads(i);
3327  if (proto.tails(i) != proto.heads(i)) {
3328  circuit_start = proto.tails(i);
3329  }
3330  ++num_true;
3331  }
3332  ++new_out_degree[proto.tails(i)];
3333  ++new_in_degree[proto.heads(i)];
3334  proto.set_tails(new_size, proto.tails(i));
3335  proto.set_heads(new_size, proto.heads(i));
3336  proto.set_literals(new_size, proto.literals(i));
3337  ++new_size;
3338  }
3339 
3340  // Detect infeasibility due to a node having no more incoming or outgoing arc.
3341  // This is a bit tricky because for now the meaning of the constraint says
3342  // that all nodes that appear in at least one of the arcs must be in the
3343  // circuit or have a self-arc. So if any such node ends up with an incoming or
3344  // outgoing degree of zero once we remove false arcs then the constraint is
3345  // infeasible!
3346  for (int i = 0; i < num_nodes; ++i) {
3347  // Skip initially ignored node.
3348  if (incoming_arcs[i].empty() && outgoing_arcs[i].empty()) continue;
3349 
3350  if (new_in_degree[i] == 0 || new_out_degree[i] == 0) {
3351  return context_->NotifyThatModelIsUnsat();
3352  }
3353  }
3354 
3355  // Test if a subcircuit is already present.
3356  if (circuit_start != -1) {
3357  std::vector<bool> visited(num_nodes, false);
3358  int current = circuit_start;
3359  while (current != -1 && !visited[current]) {
3360  visited[current] = true;
3361  current = next[current];
3362  }
3363  if (current == circuit_start) {
3364  // We have a sub-circuit! mark all other arc false except self-loop not in
3365  // circuit.
3366  for (int i = 0; i < num_arcs; ++i) {
3367  if (visited[proto.tails(i)]) continue;
3368  if (proto.tails(i) == proto.heads(i)) {
3369  if (!context_->SetLiteralToTrue(proto.literals(i))) return true;
3370  } else {
3371  if (!context_->SetLiteralToFalse(proto.literals(i))) return true;
3372  }
3373  }
3374  context_->UpdateRuleStats("circuit: fully specified.");
3375  return RemoveConstraint(ct);
3376  }
3377  } else {
3378  // All self loop?
3379  if (num_true == new_size) {
3380  context_->UpdateRuleStats("circuit: empty circuit.");
3381  return RemoveConstraint(ct);
3382  }
3383  }
3384 
3385  // Look for in/out-degree of two, this will imply that one of the indicator
3386  // Boolean is equal to the negation of the other.
3387  for (int i = 0; i < num_nodes; ++i) {
3388  for (const std::vector<int>* arc_literals :
3389  {&incoming_arcs[i], &outgoing_arcs[i]}) {
3390  std::vector<int> literals;
3391  for (const int ref : *arc_literals) {
3392  if (context_->LiteralIsFalse(ref)) continue;
3393  if (context_->LiteralIsTrue(ref)) {
3394  literals.clear();
3395  break;
3396  }
3397  literals.push_back(ref);
3398  }
3399  if (literals.size() == 2 && literals[0] != NegatedRef(literals[1])) {
3400  context_->UpdateRuleStats("circuit: degree 2");
3401  context_->StoreBooleanEqualityRelation(literals[0],
3402  NegatedRef(literals[1]));
3403  }
3404  }
3405  }
3406 
3407  // Truncate the circuit and return.
3408  if (new_size < num_arcs) {
3409  proto.mutable_tails()->Truncate(new_size);
3410  proto.mutable_heads()->Truncate(new_size);
3411  proto.mutable_literals()->Truncate(new_size);
3412  context_->UpdateRuleStats("circuit: removed false arcs.");
3413  return true;
3414  }
3415  return false;
3416 }
3417 
3418 bool CpModelPresolver::PresolveAutomaton(ConstraintProto* ct) {
3419  if (context_->ModelIsUnsat()) return false;
3420  if (HasEnforcementLiteral(*ct)) return false;
3421  AutomatonConstraintProto& proto = *ct->mutable_automaton();
3422  if (proto.vars_size() == 0 || proto.transition_label_size() == 0) {
3423  return false;
3424  }
3425 
3426  bool all_affine = true;
3427  std::vector<AffineRelation::Relation> affine_relations;
3428  for (int v = 0; v < proto.vars_size(); ++v) {
3429  const int var = ct->automaton().vars(v);
3430  AffineRelation::Relation r = context_->GetAffineRelation(PositiveRef(var));
3431  affine_relations.push_back(r);
3432  if (r.representative == var) {
3433  all_affine = false;
3434  break;
3435  }
3436  if (v > 0 && (r.coeff != affine_relations[v - 1].coeff ||
3437  r.offset != affine_relations[v - 1].offset)) {
3438  all_affine = false;
3439  break;
3440  }
3441  }
3442 
3443  if (all_affine) { // Unscale labels.
3444  for (int v = 0; v < proto.vars_size(); ++v) {
3445  proto.set_vars(v, affine_relations[v].representative);
3446  }
3447  const AffineRelation::Relation rep = affine_relations.front();
3448  int new_size = 0;
3449  for (int t = 0; t < proto.transition_tail_size(); ++t) {
3450  const int64 label = proto.transition_label(t);
3451  int64 inverse_label = (label - rep.offset) / rep.coeff;
3452  if (inverse_label * rep.coeff + rep.offset == label) {
3453  if (new_size != t) {
3454  proto.set_transition_tail(new_size, proto.transition_tail(t));
3455  proto.set_transition_head(new_size, proto.transition_head(t));
3456  }
3457  proto.set_transition_label(new_size, inverse_label);
3458  new_size++;
3459  }
3460  }
3461  if (new_size < proto.transition_tail_size()) {
3462  proto.mutable_transition_tail()->Truncate(new_size);
3463  proto.mutable_transition_label()->Truncate(new_size);
3464  proto.mutable_transition_head()->Truncate(new_size);
3465  context_->UpdateRuleStats("automaton: remove invalid transitions");
3466  }
3467  context_->UpdateRuleStats("automaton: unscale all affine labels");
3468  return true;
3469  }
3470 
3471  Domain hull = context_->DomainOf(proto.vars(0));
3472  for (int v = 1; v < proto.vars_size(); ++v) {
3473  hull = hull.UnionWith(context_->DomainOf(proto.vars(v)));
3474  }
3475 
3476  int new_size = 0;
3477  for (int t = 0; t < proto.transition_tail_size(); ++t) {
3478  const int64 label = proto.transition_label(t);
3479  if (hull.Contains(label)) {
3480  if (new_size != t) {
3481  proto.set_transition_tail(new_size, proto.transition_tail(t));
3482  proto.set_transition_label(new_size, label);
3483  proto.set_transition_head(new_size, proto.transition_head(t));
3484  }
3485  new_size++;
3486  }
3487  }
3488  if (new_size < proto.transition_tail_size()) {
3489  proto.mutable_transition_tail()->Truncate(new_size);
3490  proto.mutable_transition_label()->Truncate(new_size);
3491  proto.mutable_transition_head()->Truncate(new_size);
3492  context_->UpdateRuleStats("automaton: remove invalid transitions");
3493  return false;
3494  }
3495 
3496  const int n = proto.vars_size();
3497  const std::vector<int> vars = {proto.vars().begin(), proto.vars().end()};
3498 
3499  // Compute the set of reachable state at each time point.
3500  std::vector<std::set<int64>> reachable_states(n + 1);
3501  reachable_states[0].insert(proto.starting_state());
3502  reachable_states[n] = {proto.final_states().begin(),
3503  proto.final_states().end()};
3504 
3505  // Forward.
3506  //
3507  // TODO(user): filter using the domain of vars[time] that may not contain
3508  // all the possible transitions.
3509  for (int time = 0; time + 1 < n; ++time) {
3510  for (int t = 0; t < proto.transition_tail_size(); ++t) {
3511  const int64 tail = proto.transition_tail(t);
3512  const int64 label = proto.transition_label(t);
3513  const int64 head = proto.transition_head(t);
3514  if (!gtl::ContainsKey(reachable_states[time], tail)) continue;
3515  if (!context_->DomainContains(vars[time], label)) continue;
3516  reachable_states[time + 1].insert(head);
3517  }
3518  }
3519 
3520  std::vector<std::set<int64>> reached_values(n);
3521 
3522  // Backward.
3523  for (int time = n - 1; time >= 0; --time) {
3524  std::set<int64> new_set;
3525  for (int t = 0; t < proto.transition_tail_size(); ++t) {
3526  const int64 tail = proto.transition_tail(t);
3527  const int64 label = proto.transition_label(t);
3528  const int64 head = proto.transition_head(t);
3529 
3530  if (!gtl::ContainsKey(reachable_states[time], tail)) continue;
3531  if (!context_->DomainContains(vars[time], label)) continue;
3532  if (!gtl::ContainsKey(reachable_states[time + 1], head)) continue;
3533  new_set.insert(tail);
3534  reached_values[time].insert(label);
3535  }
3536  reachable_states[time].swap(new_set);
3537  }
3538 
3539  bool removed_values = false;
3540  for (int time = 0; time < n; ++time) {
3541  if (!context_->IntersectDomainWith(
3542  vars[time],
3544  {reached_values[time].begin(), reached_values[time].end()}),
3545  &removed_values)) {
3546  return false;
3547  }
3548  }
3549  if (removed_values) {
3550  context_->UpdateRuleStats("automaton: reduced variable domains");
3551  }
3552  return false;
3553 }
3554 
3555 bool CpModelPresolver::PresolveReservoir(ConstraintProto* ct) {
3556  if (context_->ModelIsUnsat()) return false;
3557  if (HasEnforcementLiteral(*ct)) return false;
3558 
3559  bool changed = false;
3560 
3561  ReservoirConstraintProto& mutable_reservoir = *ct->mutable_reservoir();
3562  if (mutable_reservoir.actives().empty()) {
3563  const int true_literal = context_->GetOrCreateConstantVar(1);
3564  for (int i = 0; i < mutable_reservoir.times_size(); ++i) {
3565  mutable_reservoir.add_actives(true_literal);
3566  }
3567  changed = true;
3568  }
3569 
3570  const auto& demand_is_null = [&](int i) {
3571  return mutable_reservoir.demands(i) == 0 ||
3572  context_->LiteralIsFalse(mutable_reservoir.actives(i));
3573  };
3574 
3575  // Remove zero demands, and inactive events.
3576  int num_zeros = 0;
3577  for (int i = 0; i < mutable_reservoir.demands_size(); ++i) {
3578  if (demand_is_null(i)) num_zeros++;
3579  }
3580 
3581  if (num_zeros > 0) { // Remove null events
3582  changed = true;
3583  int new_size = 0;
3584  for (int i = 0; i < mutable_reservoir.demands_size(); ++i) {
3585  if (demand_is_null(i)) continue;
3586  mutable_reservoir.set_demands(new_size, mutable_reservoir.demands(i));
3587  mutable_reservoir.set_times(new_size, mutable_reservoir.times(i));
3588  mutable_reservoir.set_actives(new_size, mutable_reservoir.actives(i));
3589  new_size++;
3590  }
3591 
3592  mutable_reservoir.mutable_demands()->Truncate(new_size);
3593  mutable_reservoir.mutable_times()->Truncate(new_size);
3594  mutable_reservoir.mutable_actives()->Truncate(new_size);
3595 
3596  context_->UpdateRuleStats(
3597  "reservoir: remove zero demands or inactive events.");
3598  }
3599 
3600  const int num_events = mutable_reservoir.demands_size();
3601  int64 gcd = mutable_reservoir.demands().empty()
3602  ? 0
3603  : std::abs(mutable_reservoir.demands(0));
3604  int num_positives = 0;
3605  int num_negatives = 0;
3606  int64 sum_of_demands = 0;
3607  int64 max_sum_of_positive_demands = 0;
3608  int64 min_sum_of_negative_demands = 0;
3609  for (int i = 0; i < num_events; ++i) {
3610  const int64 demand = mutable_reservoir.demands(i);
3611  sum_of_demands += demand;
3612  gcd = MathUtil::GCD64(gcd, std::abs(demand));
3613  if (demand > 0) {
3614  num_positives++;
3615  max_sum_of_positive_demands += demand;
3616  } else {
3617  DCHECK_LT(demand, 0);
3618  num_negatives++;
3619  min_sum_of_negative_demands += demand;
3620  }
3621  }
3622 
3623  if (min_sum_of_negative_demands >= mutable_reservoir.min_level() &&
3624  max_sum_of_positive_demands <= mutable_reservoir.max_level()) {
3625  context_->UpdateRuleStats("reservoir: always feasible");
3626  return RemoveConstraint(ct);
3627  }
3628 
3629  if (min_sum_of_negative_demands > mutable_reservoir.max_level() ||
3630  max_sum_of_positive_demands < mutable_reservoir.min_level()) {
3631  context_->UpdateRuleStats("reservoir: trivially infeasible");
3632  return context_->NotifyThatModelIsUnsat();
3633  }
3634 
3635  if (min_sum_of_negative_demands > mutable_reservoir.min_level()) {
3636  mutable_reservoir.set_min_level(min_sum_of_negative_demands);
3637  context_->UpdateRuleStats(
3638  "reservoir: increase min_level to reachable value");
3639  }
3640 
3641  if (max_sum_of_positive_demands < mutable_reservoir.max_level()) {
3642  mutable_reservoir.set_max_level(max_sum_of_positive_demands);
3643  context_->UpdateRuleStats("reservoir: reduce max_level to reachable value");
3644  }
3645 
3646  if (mutable_reservoir.min_level() <= 0 &&
3647  mutable_reservoir.max_level() >= 0 &&
3648  (num_positives == 0 || num_negatives == 0)) {
3649  // If all demands have the same sign, and if the initial state is
3650  // always feasible, we do not care about the order, just the sum.
3651  auto* const sum =
3652  context_->working_model->add_constraints()->mutable_linear();
3653  int64 fixed_contrib = 0;
3654  for (int i = 0; i < mutable_reservoir.demands_size(); ++i) {
3655  const int64 demand = mutable_reservoir.demands(i);
3656  DCHECK_NE(demand, 0);
3657 
3658  const int active = mutable_reservoir.actives(i);
3659  if (RefIsPositive(active)) {
3660  sum->add_vars(active);
3661  sum->add_coeffs(demand);
3662  } else {
3663  sum->add_vars(PositiveRef(active));
3664  sum->add_coeffs(-demand);
3665  fixed_contrib += demand;
3666  }
3667  }
3668  sum->add_domain(mutable_reservoir.min_level() - fixed_contrib);
3669  sum->add_domain(mutable_reservoir.max_level() - fixed_contrib);
3670  context_->UpdateRuleStats("reservoir: converted to linear");
3671  return RemoveConstraint(ct);
3672  }
3673 
3674  if (gcd > 1) {
3675  for (int i = 0; i < mutable_reservoir.demands_size(); ++i) {
3676  mutable_reservoir.set_demands(i, mutable_reservoir.demands(i) / gcd);
3677  }
3678 
3679  // Adjust min and max levels.
3680  // max level is always rounded down.
3681  // min level is always rounded up.
3682  const Domain reduced_domain =
3683  Domain({mutable_reservoir.min_level(), mutable_reservoir.max_level()})
3684  .InverseMultiplicationBy(gcd);
3685  mutable_reservoir.set_min_level(reduced_domain.Min());
3686  mutable_reservoir.set_max_level(reduced_domain.Max());
3687  context_->UpdateRuleStats("reservoir: simplify demands and levels by gcd.");
3688  }
3689 
3690  if (num_positives == 1 && num_negatives > 0) {
3691  context_->UpdateRuleStats(
3692  "TODO reservoir: one producer, multiple consumers.");
3693  }
3694 
3695  absl::flat_hash_set<std::pair<int, int>> time_active_set;
3696  for (int i = 0; i < mutable_reservoir.demands_size(); ++i) {
3697  const std::pair<int, int> key = std::make_pair(
3698  mutable_reservoir.times(i), mutable_reservoir.actives(i));
3699  if (time_active_set.contains(key)) {
3700  context_->UpdateRuleStats("TODO reservoir: merge synchronized events.");
3701  break;
3702  } else {
3703  time_active_set.insert(key);
3704  }
3705  }
3706 
3707  return changed;
3708 }
3709 
3710 // TODO(user): It is probably more efficient to keep all the bool_and in a
3711 // global place during all the presolve, and just output them at the end
3712 // rather than modifying more than once the proto.
3713 void CpModelPresolver::ExtractBoolAnd() {
3714  absl::flat_hash_map<int, int> ref_to_bool_and;
3715  const int num_constraints = context_->working_model->constraints_size();
3716  std::vector<int> to_remove;
3717  for (int c = 0; c < num_constraints; ++c) {
3718  const ConstraintProto& ct = context_->working_model->constraints(c);
3719  if (HasEnforcementLiteral(ct)) continue;
3720 
3721  if (ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr &&
3722  ct.bool_or().literals().size() == 2) {
3723  AddImplication(NegatedRef(ct.bool_or().literals(0)),
3724  ct.bool_or().literals(1), context_->working_model,
3725  &ref_to_bool_and);
3726  to_remove.push_back(c);
3727  continue;
3728  }
3729 
3730  if (ct.constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne &&
3731  ct.at_most_one().literals().size() == 2) {
3732  AddImplication(ct.at_most_one().literals(0),
3733  NegatedRef(ct.at_most_one().literals(1)),
3734  context_->working_model, &ref_to_bool_and);
3735  to_remove.push_back(c);
3736  continue;
3737  }
3738  }
3739 
3741  for (const int c : to_remove) {
3742  ConstraintProto* ct = context_->working_model->mutable_constraints(c);
3743  CHECK(RemoveConstraint(ct));
3744  context_->UpdateConstraintVariableUsage(c);
3745  }
3746 }
3747 
3748 void CpModelPresolver::Probe() {
3749  if (context_->ModelIsUnsat()) return;
3750 
3751  // Update the domain in the current CpModelProto.
3752  for (int i = 0; i < context_->working_model->variables_size(); ++i) {
3753  FillDomainInProto(context_->DomainOf(i),
3754  context_->working_model->mutable_variables(i));
3755  }
3756  const CpModelProto& model_proto = *(context_->working_model);
3757 
3758  // Load the constraints in a local model.
3759  //
3760  // TODO(user): The model we load does not contain affine relations! But
3761  // ideally we should be able to remove all of them once we allow more complex
3762  // constraints to contains linear expression.
3763  //
3764  // TODO(user): remove code duplication with cp_model_solver. Here we also do
3765  // not run the heuristic to decide which variable to fully encode.
3766  //
3767  // TODO(user): Maybe do not load slow to propagate constraints? for instance
3768  // we do not use any linear relaxation here.
3769  Model model;
3770 
3771  // Adapt some of the parameters during this probing phase.
3772  auto* local_param = model.GetOrCreate<SatParameters>();
3773  *local_param = context_->params();
3774  local_param->set_use_implied_bounds(false);
3775 
3776  model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(
3777  context_->time_limit());
3778  model.Register<ModelRandomGenerator>(context_->random());
3779  auto* encoder = model.GetOrCreate<IntegerEncoder>();
3780  encoder->DisableImplicationBetweenLiteral();
3781  auto* mapping = model.GetOrCreate<CpModelMapping>();
3782  mapping->CreateVariables(model_proto, false, &model);
3783  mapping->DetectOptionalVariables(model_proto, &model);
3784  mapping->ExtractEncoding(model_proto, &model);
3785  auto* sat_solver = model.GetOrCreate<SatSolver>();
3786  for (const ConstraintProto& ct : model_proto.constraints()) {
3787  if (mapping->ConstraintIsAlreadyLoaded(&ct)) continue;
3789  if (sat_solver->IsModelUnsat()) {
3790  return (void)context_->NotifyThatModelIsUnsat();
3791  }
3792  }
3793  encoder->AddAllImplicationsBetweenAssociatedLiterals();
3794  if (!sat_solver->Propagate()) {
3795  return (void)context_->NotifyThatModelIsUnsat();
3796  }
3797 
3798  // Probe.
3799  //
3800  // TODO(user): Compute the transitive reduction instead of just the
3801  // equivalences, and use the newly learned binary clauses?
3802  auto* implication_graph = model.GetOrCreate<BinaryImplicationGraph>();
3803  auto* prober = model.GetOrCreate<Prober>();
3804  prober->ProbeBooleanVariables(/*deterministic_time_limit=*/1.0);
3805  context_->time_limit()->AdvanceDeterministicTime(
3806  model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
3807  if (sat_solver->IsModelUnsat() || !implication_graph->DetectEquivalences()) {
3808  return (void)context_->NotifyThatModelIsUnsat();
3809  }
3810 
3811  // Update the presolve context with fixed Boolean variables.
3812  CHECK_EQ(sat_solver->CurrentDecisionLevel(), 0);
3813  for (int i = 0; i < sat_solver->LiteralTrail().Index(); ++i) {
3814  const Literal l = sat_solver->LiteralTrail()[i];
3815  const int var = mapping->GetProtoVariableFromBooleanVariable(l.Variable());
3816  if (var >= 0) {
3817  const int ref = l.IsPositive() ? var : NegatedRef(var);
3818  if (!context_->SetLiteralToTrue(ref)) return;
3819  }
3820  }
3821 
3822  const int num_variables = context_->working_model->variables().size();
3823  auto* integer_trail = model.GetOrCreate<IntegerTrail>();
3824  for (int var = 0; var < num_variables; ++var) {
3825  // Restrict IntegerVariable domain.
3826  // Note that Boolean are already dealt with above.
3827  if (!mapping->IsBoolean(var)) {
3828  if (!context_->IntersectDomainWith(
3829  var,
3830  integer_trail->InitialVariableDomain(mapping->Integer(var)))) {
3831  return;
3832  }
3833  continue;
3834  }
3835 
3836  // Add Boolean equivalence relations.
3837  const Literal l = mapping->Literal(var);
3838  const Literal r = implication_graph->RepresentativeOf(l);
3839  if (r != l) {
3840  const int r_var =
3841  mapping->GetProtoVariableFromBooleanVariable(r.Variable());
3842  CHECK_GE(r_var, 0);
3843  context_->StoreBooleanEqualityRelation(
3844  var, r.IsPositive() ? r_var : NegatedRef(r_var));
3845  }
3846  }
3847 }
3848 
3849 // TODO(user): What to do with the at_most_one/exactly_one constraints?
3850 // currently we do not take them into account here.
3851 void CpModelPresolver::PresolvePureSatPart() {
3852  // TODO(user,user): Reenable some SAT presolve with
3853  // keep_all_feasible_solutions set to true.
3854  if (context_->ModelIsUnsat() || context_->keep_all_feasible_solutions) return;
3855 
3856  const int num_variables = context_->working_model->variables_size();
3857  SatPostsolver sat_postsolver(num_variables);
3858  SatPresolver sat_presolver(&sat_postsolver);
3859  sat_presolver.SetNumVariables(num_variables);
3860  sat_presolver.SetTimeLimit(context_->time_limit());
3861 
3862  SatParameters params = context_->params();
3863 
3864  // The "full solver" postsolve does not support changing the value of a
3865  // variable from the solution of the presolved problem, and we do need this
3866  // for blocked clause. It should be possible to allow for this by adding extra
3867  // variable to the mapping model at presolve and some linking constraints, but
3868  // this is messy.
3869  if (params.cp_model_postsolve_with_full_solver()) {
3870  params.set_presolve_blocked_clause(false);
3871  }
3872 
3873  // TODO(user): BVA takes times and do not seems to help on the minizinc
3874  // benchmarks. That said, it was useful on pure sat problems, so we may
3875  // want to enable it.
3876  params.set_presolve_use_bva(false);
3877  sat_presolver.SetParameters(params);
3878 
3879  // Converts a cp_model literal ref to a sat::Literal used by SatPresolver.
3880  absl::flat_hash_set<int> used_variables;
3881  auto convert = [&used_variables](int ref) {
3882  used_variables.insert(PositiveRef(ref));
3883  if (RefIsPositive(ref)) return Literal(BooleanVariable(ref), true);
3884  return Literal(BooleanVariable(NegatedRef(ref)), false);
3885  };
3886 
3887  // We need all Boolean constraints to be presolved before loading them below.
3888  // Otherwise duplicate literals might result in a wrong outcome.
3889  //
3890  // TODO(user): Be a bit more efficient, and enforce this invariant before we
3891  // reach this point?
3892  for (int c = 0; c < context_->working_model->constraints_size(); ++c) {
3893  const ConstraintProto& ct = context_->working_model->constraints(c);
3894  if (ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr ||
3895  ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolAnd) {
3896  if (PresolveOneConstraint(c)) {
3897  context_->UpdateConstraintVariableUsage(c);
3898  }
3899  if (context_->ModelIsUnsat()) return;
3900  }
3901  }
3902 
3903  // Load all Clauses into the presolver and remove them from the current model.
3904  //
3905  // TODO(user): The removing and adding back of the same clause when nothing
3906  // happens in the presolve "seems" bad. That said, complexity wise, it is
3907  // a lot faster that what happens in the presolve though.
3908  //
3909  // TODO(user): Add the "small" at most one constraints to the SAT presolver by
3910  // expanding them to implications? that could remove a lot of clauses. Do that
3911  // when we are sure we don't load duplicates at_most_one/implications in the
3912  // solver. Ideally, the pure sat presolve could be improved to handle at most
3913  // one, and we could merge this with what the ProcessSetPPC() is doing.
3914  std::vector<Literal> clause;
3915  int num_removed_constraints = 0;
3916  for (int i = 0; i < context_->working_model->constraints_size(); ++i) {
3917  const ConstraintProto& ct = context_->working_model->constraints(i);
3918 
3919  if (ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr) {
3920  ++num_removed_constraints;
3921  clause.clear();
3922  for (const int ref : ct.bool_or().literals()) {
3923  clause.push_back(convert(ref));
3924  }
3925  for (const int ref : ct.enforcement_literal()) {
3926  clause.push_back(convert(ref).Negated());
3927  }
3928  sat_presolver.AddClause(clause);
3929 
3930  context_->working_model->mutable_constraints(i)->Clear();
3931  context_->UpdateConstraintVariableUsage(i);
3932  continue;
3933  }
3934 
3935  if (ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolAnd) {
3936  ++num_removed_constraints;
3937  std::vector<Literal> clause;
3938  for (const int ref : ct.enforcement_literal()) {
3939  clause.push_back(convert(ref).Negated());
3940  }
3941  clause.push_back(Literal(kNoLiteralIndex)); // will be replaced below.
3942  for (const int ref : ct.bool_and().literals()) {
3943  clause.back() = convert(ref);
3944  sat_presolver.AddClause(clause);
3945  }
3946 
3947  context_->working_model->mutable_constraints(i)->Clear();
3948  context_->UpdateConstraintVariableUsage(i);
3949  continue;
3950  }
3951  }
3952 
3953  // Abort early if there was no Boolean constraints.
3954  if (num_removed_constraints == 0) return;
3955 
3956  // Mark the variables appearing elsewhere or in the objective as non-removable
3957  // by the sat presolver.
3958  //
3959  // TODO(user): do not remove variable that appear in the decision heuristic?
3960  // TODO(user): We could go further for variable with only one polarity by
3961  // removing variable from the objective if they can be set to their "low"
3962  // objective value, and also removing enforcement literal that can be set to
3963  // false and don't appear elsewhere.
3964  std::vector<bool> can_be_removed(num_variables, false);
3965  for (int i = 0; i < num_variables; ++i) {
3966  if (context_->VarToConstraints(i).empty()) {
3967  can_be_removed[i] = true;
3968  }
3969 
3970  // Because we might not have reached the presove "fixed point" above, some
3971  // variable in the added clauses might be fixed. We need to indicate this to
3972  // the SAT presolver.
3973  if (used_variables.contains(i) && context_->IsFixed(i)) {
3974  if (context_->LiteralIsTrue(i)) {
3975  sat_presolver.AddClause({convert(i)});
3976  } else {
3977  sat_presolver.AddClause({convert(NegatedRef(i))});
3978  }
3979  }
3980  }
3981 
3982  // Run the presolve for a small number of passes.
3983  // TODO(user): Add probing like we do in the pure sat solver presolve loop?
3984  // TODO(user): Add a time limit, this can be slow on big SAT problem.
3985  const int num_passes = params.presolve_use_bva() ? 4 : 1;
3986  for (int i = 0; i < num_passes; ++i) {
3987  const int old_num_clause = sat_postsolver.NumClauses();
3988  if (!sat_presolver.Presolve(can_be_removed, context_->log_info())) {
3989  VLOG(1) << "UNSAT during SAT presolve.";
3990  return (void)context_->NotifyThatModelIsUnsat();
3991  }
3992  if (old_num_clause == sat_postsolver.NumClauses()) break;
3993  }
3994 
3995  // Add any new variables to our internal structure.
3996  const int new_num_variables = sat_presolver.NumVariables();
3997  if (new_num_variables > context_->working_model->variables_size()) {
3998  VLOG(1) << "New variables added by the SAT presolver.";
3999  for (int i = context_->working_model->variables_size();
4000  i < new_num_variables; ++i) {
4001  IntegerVariableProto* var_proto =
4002  context_->working_model->add_variables();
4003  var_proto->add_domain(0);
4004  var_proto->add_domain(1);
4005  }
4006  context_->InitializeNewDomains();
4007  }
4008 
4009  // Add the presolver clauses back into the model.
4010  ExtractClauses(/*use_bool_and=*/true, sat_presolver, context_->working_model);
4011 
4012  // Update the constraints <-> variables graph.
4014 
4015  // Add the sat_postsolver clauses to mapping_model.
4016  //
4017  // TODO(user): Mark removed variable as removed to detect any potential bugs.
4018  ExtractClauses(/*use_bool_and=*/false, sat_postsolver,
4019  context_->mapping_model);
4020 }
4021 
4022 // TODO(user): The idea behind this was that it is better to have an objective
4023 // as spreaded as possible. However on some problems this have the opposite
4024 // effect. Like on a triangular matrix where each expansion reduced the size
4025 // of the objective by one. Investigate and fix?
4026 void CpModelPresolver::ExpandObjective() {
4027  if (context_->ModelIsUnsat()) return;
4028 
4029  // The objective is already loaded in the constext, but we re-canonicalize
4030  // it with the latest information.
4031  if (!context_->CanonicalizeObjective()) {
4032  (void)context_->NotifyThatModelIsUnsat();
4033  return;
4034  }
4035 
4036  if (context_->time_limit()->LimitReached()) {
4037  context_->WriteObjectiveToProto();
4038  return;
4039  }
4040 
4041  // If the objective is a single variable, then we can usually remove this
4042  // variable if it is only used in one linear equality constraint and we do
4043  // just one expansion. This is because the domain of the variable will be
4044  // transferred to our objective_domain.
4045  int unique_expanded_constraint = -1;
4046  const bool objective_was_a_single_variable =
4047  context_->ObjectiveMap().size() == 1;
4048 
4049  // To avoid a bad complexity, we need to compute the number of relevant
4050  // constraints for each variables.
4051  const int num_variables = context_->working_model->variables_size();
4052  const int num_constraints = context_->working_model->constraints_size();
4053  absl::flat_hash_set<int> relevant_constraints;
4054  std::vector<int> var_to_num_relevant_constraints(num_variables, 0);
4055  for (int ct_index = 0; ct_index < num_constraints; ++ct_index) {
4056  const ConstraintProto& ct = context_->working_model->constraints(ct_index);
4057  // Skip everything that is not a linear equality constraint.
4058  if (!ct.enforcement_literal().empty() ||
4059  ct.constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
4060  ct.linear().domain().size() != 2 ||
4061  ct.linear().domain(0) != ct.linear().domain(1)) {
4062  continue;
4063  }
4064 
4065  relevant_constraints.insert(ct_index);
4066  const int num_terms = ct.linear().vars_size();
4067  for (int i = 0; i < num_terms; ++i) {
4068  var_to_num_relevant_constraints[PositiveRef(ct.linear().vars(i))]++;
4069  }
4070  }
4071 
4072  std::set<int> var_to_process;
4073  for (const auto entry : context_->ObjectiveMap()) {
4074  const int var = entry.first;
4076  if (var_to_num_relevant_constraints[var] != 0) {
4077  var_to_process.insert(var);
4078  }
4079  }
4080 
4081  // We currently never expand a variable more than once.
4082  int num_expansions = 0;
4083  absl::flat_hash_set<int> processed_vars;
4084  std::vector<int> new_vars_in_objective;
4085  while (!relevant_constraints.empty()) {
4086  // Find a not yet expanded var.
4087  int objective_var = -1;
4088  while (!var_to_process.empty()) {
4089  const int var = *var_to_process.begin();
4090  CHECK(!processed_vars.contains(var));
4091  if (var_to_num_relevant_constraints[var] == 0) {
4092  processed_vars.insert(var);
4093  var_to_process.erase(var);
4094  continue;
4095  }
4096  if (!context_->ObjectiveMap().contains(var)) {
4097  // We do not mark it as processed in case it re-appear later.
4098  var_to_process.erase(var);
4099  continue;
4100  }
4101  objective_var = var;
4102  break;
4103  }
4104 
4105  if (objective_var == -1) break;
4106  CHECK(RefIsPositive(objective_var));
4107  processed_vars.insert(objective_var);
4108  var_to_process.erase(objective_var);
4109 
4110  int expanded_linear_index = -1;
4111  int64 objective_coeff_in_expanded_constraint;
4112  int64 size_of_expanded_constraint = 0;
4113  const auto& non_deterministic_list =
4114  context_->VarToConstraints(objective_var);
4115  std::vector<int> constraints_with_objective(non_deterministic_list.begin(),
4116  non_deterministic_list.end());
4117  std::sort(constraints_with_objective.begin(),
4118  constraints_with_objective.end());
4119  for (const int ct_index : constraints_with_objective) {
4120  if (relevant_constraints.count(ct_index) == 0) continue;
4121  const ConstraintProto& ct =
4122  context_->working_model->constraints(ct_index);
4123 
4124  // This constraint is relevant now, but it will never be later because
4125  // it will contain the objective_var which is already processed!
4126  relevant_constraints.erase(ct_index);
4127  const int num_terms = ct.linear().vars_size();
4128  for (int i = 0; i < num_terms; ++i) {
4129  var_to_num_relevant_constraints[PositiveRef(ct.linear().vars(i))]--;
4130  }
4131 
4132  // Find the coefficient of objective_var in this constraint, and perform
4133  // various checks.
4134  //
4135  // TODO(user): This can crash the program if for some reason the linear
4136  // constraint was not canonicalized and contains the objective variable
4137  // twice. Currently this can only happen if the time limit was reached
4138  // before all constraints where processed, but because we abort at the
4139  // beginning of the function when this is the case we should be safe.
4140  // However, it might be more robust to just handle this case properly.
4141  bool is_present = false;
4142  int64 objective_coeff;
4143  for (int i = 0; i < num_terms; ++i) {
4144  const int ref = ct.linear().vars(i);
4145  const int64 coeff = ct.linear().coeffs(i);
4146  if (PositiveRef(ref) == objective_var) {
4147  CHECK(!is_present) << "Duplicate variables not supported.";
4148  is_present = true;
4149  objective_coeff = (ref == objective_var) ? coeff : -coeff;
4150  } else {
4151  // This is not possible since we only consider relevant constraints.
4152  CHECK(!processed_vars.contains(PositiveRef(ref)));
4153  }
4154  }
4155  CHECK(is_present);
4156 
4157  // We use the longest equality we can find.
4158  //
4159  // TODO(user): Deal with objective_coeff with a magnitude greater than
4160  // 1? This will only be possible if we change the objective coeff type
4161  // to double.
4162  if (std::abs(objective_coeff) == 1 &&
4163  num_terms > size_of_expanded_constraint) {
4164  expanded_linear_index = ct_index;
4165  size_of_expanded_constraint = num_terms;
4166  objective_coeff_in_expanded_constraint = objective_coeff;
4167  }
4168  }
4169 
4170  if (expanded_linear_index != -1) {
4171  context_->UpdateRuleStats("objective: expanded objective constraint.");
4172 
4173  // Update the objective map. Note that the division is possible because
4174  // currently we only expand with coeff with a magnitude of 1.
4175  CHECK_EQ(std::abs(objective_coeff_in_expanded_constraint), 1);
4176  const ConstraintProto& ct =
4177  context_->working_model->constraints(expanded_linear_index);
4179  objective_var, objective_coeff_in_expanded_constraint, ct,
4180  &new_vars_in_objective);
4181 
4182  // Add not yet processed new variables.
4183  for (const int var : new_vars_in_objective) {
4184  if (!processed_vars.contains(var)) var_to_process.insert(var);
4185  }
4186 
4187  // If the objective variable wasn't used in other constraints and it can
4188  // be reconstructed whatever the value of the other variables, we can
4189  // remove the constraint.
4190  //
4191  // TODO(user): It should be possible to refactor the code so this is
4192  // automatically done by the linear constraint singleton presolve rule.
4193  if (context_->VarToConstraints(objective_var).size() == 1 &&
4194  !context_->keep_all_feasible_solutions) {
4195  // Compute implied domain on objective_var.
4196  Domain implied_domain = ReadDomainFromProto(ct.linear());
4197  for (int i = 0; i < size_of_expanded_constraint; ++i) {
4198  const int ref = ct.linear().vars(i);
4199  if (PositiveRef(ref) == objective_var) continue;
4200  implied_domain =
4201  implied_domain
4202  .AdditionWith(context_->DomainOf(ref).MultiplicationBy(
4203  -ct.linear().coeffs(i)))
4204  .RelaxIfTooComplex();
4205  }
4206  implied_domain = implied_domain.InverseMultiplicationBy(
4207  objective_coeff_in_expanded_constraint);
4208 
4209  // Remove the constraint if the implied domain is included in the
4210  // domain of the objective_var term.
4211  if (implied_domain.IsIncludedIn(context_->DomainOf(objective_var))) {
4212  context_->UpdateRuleStats("objective: removed objective constraint.");
4213  *(context_->mapping_model->add_constraints()) = ct;
4214  context_->working_model->mutable_constraints(expanded_linear_index)
4215  ->Clear();
4216  context_->UpdateConstraintVariableUsage(expanded_linear_index);
4217  } else {
4218  unique_expanded_constraint = expanded_linear_index;
4219  }
4220  }
4221 
4222  ++num_expansions;
4223  }
4224  }
4225 
4226  // Special case: If we just did one expansion of a single variable, then we
4227  // can remove the expanded constraints if the objective wasn't used elsewhere.
4228  if (num_expansions == 1 && objective_was_a_single_variable &&
4229  unique_expanded_constraint != -1) {
4230  context_->UpdateRuleStats(
4231  "objective: removed unique objective constraint.");
4232  ConstraintProto* mutable_ct = context_->working_model->mutable_constraints(
4233  unique_expanded_constraint);
4234  *(context_->mapping_model->add_constraints()) = *mutable_ct;
4235  mutable_ct->Clear();
4236  context_->UpdateConstraintVariableUsage(unique_expanded_constraint);
4237  }
4238 
4239  // We re-do a canonicalization with the final linear expression.
4240  if (!context_->CanonicalizeObjective()) {
4241  (void)context_->NotifyThatModelIsUnsat();
4242  return;
4243  }
4244  context_->WriteObjectiveToProto();
4245 }
4246 
4247 void CpModelPresolver::MergeNoOverlapConstraints() {
4248  if (context_->ModelIsUnsat()) return;
4249 
4250  const int num_constraints = context_->working_model->constraints_size();
4251  int old_num_no_overlaps = 0;
4252  int old_num_intervals = 0;
4253 
4254  // Extract the no-overlap constraints.
4255  std::vector<int> disjunctive_index;
4256  std::vector<std::vector<Literal>> cliques;
4257  for (int c = 0; c < num_constraints; ++c) {
4258  const ConstraintProto& ct = context_->working_model->constraints(c);
4259  if (ct.constraint_case() != ConstraintProto::ConstraintCase::kNoOverlap) {
4260  continue;
4261  }
4262  std::vector<Literal> clique;
4263  for (const int i : ct.no_overlap().intervals()) {
4264  clique.push_back(Literal(BooleanVariable(i), true));
4265  }
4266  cliques.push_back(clique);
4267  disjunctive_index.push_back(c);
4268 
4269  old_num_no_overlaps++;
4270  old_num_intervals += clique.size();
4271  }
4272  if (old_num_no_overlaps == 0) return;
4273 
4274  // We reuse the max-clique code from sat.
4275  Model local_model;
4276  local_model.GetOrCreate<Trail>()->Resize(num_constraints);
4277  auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
4278  graph->Resize(num_constraints);
4279  for (const std::vector<Literal>& clique : cliques) {
4280  // All variables at false is always a valid solution of the local model,
4281  // so this should never return UNSAT.
4282  CHECK(graph->AddAtMostOne(clique));
4283  }
4284  CHECK(graph->DetectEquivalences());
4285  graph->TransformIntoMaxCliques(
4286  &cliques, context_->params().merge_no_overlap_work_limit());
4287 
4288  // Replace each no-overlap with an extended version, or remove if empty.
4289  int new_num_no_overlaps = 0;
4290  int new_num_intervals = 0;
4291  for (int i = 0; i < cliques.size(); ++i) {
4292  const int ct_index = disjunctive_index[i];
4293  ConstraintProto* ct =
4294  context_->working_model->mutable_constraints(ct_index);
4295  ct->Clear();
4296  if (cliques[i].empty()) continue;
4297  for (const Literal l : cliques[i]) {
4298  CHECK(l.IsPositive());
4299  ct->mutable_no_overlap()->add_intervals(l.Variable().value());
4300  }
4301  new_num_no_overlaps++;
4302  new_num_intervals += cliques[i].size();
4303  }
4304  if (old_num_intervals != new_num_intervals ||
4305  old_num_no_overlaps != new_num_no_overlaps) {
4306  VLOG(1) << absl::StrCat("Merged ", old_num_no_overlaps, " no-overlaps (",
4307  old_num_intervals, " intervals) into ",
4308  new_num_no_overlaps, " no-overlaps (",
4309  new_num_intervals, " intervals).");
4310  context_->UpdateRuleStats("no_overlap: merged constraints");
4311  }
4312 }
4313 
4314 // TODO(user): Should we take into account the exactly_one constraints? note
4315 // that such constraint cannot be extended. If if a literal implies two literals
4316 // at one inside an exactly one constraint then it must be false. Similarly if
4317 // it implies all literals at zero inside the exactly one.
4318 void CpModelPresolver::TransformIntoMaxCliques() {
4319  if (context_->ModelIsUnsat()) return;
4320 
4321  auto convert = [](int ref) {
4322  if (RefIsPositive(ref)) return Literal(BooleanVariable(ref), true);
4323  return Literal(BooleanVariable(NegatedRef(ref)), false);
4324  };
4325  const int num_constraints = context_->working_model->constraints_size();
4326 
4327  // Extract the bool_and and at_most_one constraints.
4328  std::vector<std::vector<Literal>> cliques;
4329 
4330  for (int c = 0; c < num_constraints; ++c) {
4331  ConstraintProto* ct = context_->working_model->mutable_constraints(c);
4332  if (ct->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne) {
4333  std::vector<Literal> clique;
4334  for (const int ref : ct->at_most_one().literals()) {
4335  clique.push_back(convert(ref));
4336  }
4337  cliques.push_back(clique);
4338  if (RemoveConstraint(ct)) {
4339  context_->UpdateConstraintVariableUsage(c);
4340  }
4341  } else if (ct->constraint_case() ==
4342  ConstraintProto::ConstraintCase::kBoolAnd) {
4343  if (ct->enforcement_literal().size() != 1) continue;
4344  const Literal enforcement = convert(ct->enforcement_literal(0));
4345  for (const int ref : ct->bool_and().literals()) {
4346  if (ref == ct->enforcement_literal(0)) continue;
4347  cliques.push_back({enforcement, convert(ref).Negated()});
4348  }
4349  if (RemoveConstraint(ct)) {
4350  context_->UpdateConstraintVariableUsage(c);
4351  }
4352  }
4353  }
4354 
4355  const int num_old_cliques = cliques.size();
4356 
4357  // We reuse the max-clique code from sat.
4358  Model local_model;
4359  const int num_variables = context_->working_model->variables().size();
4360  local_model.GetOrCreate<Trail>()->Resize(num_variables);
4361  auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
4362  graph->Resize(num_variables);
4363  for (const std::vector<Literal>& clique : cliques) {
4364  if (!graph->AddAtMostOne(clique)) {
4365  return (void)context_->NotifyThatModelIsUnsat();
4366  }
4367  }
4368  if (!graph->DetectEquivalences()) {
4369  return (void)context_->NotifyThatModelIsUnsat();
4370  }
4371  graph->TransformIntoMaxCliques(
4372  &cliques, context_->params().merge_at_most_one_work_limit());
4373 
4374  // Add the Boolean variable equivalence detected by DetectEquivalences().
4375  // Those are needed because TransformIntoMaxCliques() will replace all
4376  // variable by its representative.
4377  for (int var = 0; var < num_variables; ++var) {
4378  const Literal l = Literal(BooleanVariable(var), true);
4379  if (graph->RepresentativeOf(l) != l) {
4380  const Literal r = graph->RepresentativeOf(l);
4381  context_->StoreBooleanEqualityRelation(
4382  var, r.IsPositive() ? r.Variable().value()
4383  : NegatedRef(r.Variable().value()));
4384  }
4385  }
4386 
4387  int num_new_cliques = 0;
4388  for (const std::vector<Literal>& clique : cliques) {
4389  if (clique.empty()) continue;
4390  num_new_cliques++;
4391  ConstraintProto* ct = context_->working_model->add_constraints();
4392  for (const Literal literal : clique) {
4393  if (literal.IsPositive()) {
4394  ct->mutable_at_most_one()->add_literals(literal.Variable().value());
4395  } else {
4396  ct->mutable_at_most_one()->add_literals(
4397  NegatedRef(literal.Variable().value()));
4398  }
4399  }
4400 
4401  // Make sure we do not have duplicate variable reference.
4402  PresolveAtMostOne(ct);
4403  }
4405  if (num_new_cliques != num_old_cliques) {
4406  context_->UpdateRuleStats("at_most_one: transformed into max clique.");
4407  }
4408 
4409  if (context_->log_info()) {
4410  LOG(INFO) << "Merged " << num_old_cliques << " into " << num_new_cliques
4411  << " cliques.";
4412  }
4413 }
4414 
4416  if (context_->ModelIsUnsat()) return false;
4417  ConstraintProto* ct = context_->working_model->mutable_constraints(c);
4418 
4419  // Generic presolve to exploit variable/literal equivalence.
4420  if (ExploitEquivalenceRelations(c, ct)) {
4421  context_->UpdateConstraintVariableUsage(c);
4422  }
4423 
4424  // Generic presolve for reified constraint.
4425  if (PresolveEnforcementLiteral(ct)) {
4426  context_->UpdateConstraintVariableUsage(c);
4427  }
4428 
4429  // Call the presolve function for this constraint if any.
4430  switch (ct->constraint_case()) {
4431  case ConstraintProto::ConstraintCase::kBoolOr:
4432  return PresolveBoolOr(ct);
4433  case ConstraintProto::ConstraintCase::kBoolAnd:
4434  return PresolveBoolAnd(ct);
4435  case ConstraintProto::ConstraintCase::kAtMostOne:
4436  return PresolveAtMostOne(ct);
4437  case ConstraintProto::ConstraintCase::kExactlyOne:
4438  return PresolveExactlyOne(ct);
4439  case ConstraintProto::ConstraintCase::kBoolXor:
4440  return PresolveBoolXor(ct);
4441  case ConstraintProto::ConstraintCase::kIntMax:
4442  if (ct->int_max().vars_size() == 2 &&
4443  NegatedRef(ct->int_max().vars(0)) == ct->int_max().vars(1)) {
4444  return PresolveIntAbs(ct);
4445  } else {
4446  return PresolveIntMax(ct);
4447  }
4448  case ConstraintProto::ConstraintCase::kIntMin:
4449  return PresolveIntMin(ct);
4450  case ConstraintProto::ConstraintCase::kLinMax:
4451  return PresolveLinMax(ct);
4452  case ConstraintProto::ConstraintCase::kLinMin:
4453  return PresolveLinMin(ct);
4454  case ConstraintProto::ConstraintCase::kIntProd:
4455  return PresolveIntProd(ct);
4456  case ConstraintProto::ConstraintCase::kIntDiv:
4457  return PresolveIntDiv(ct);
4458  case ConstraintProto::ConstraintCase::kLinear: {
4459  if (CanonicalizeLinear(ct)) {
4460  context_->UpdateConstraintVariableUsage(c);
4461  }
4462  if (PresolveSmallLinear(ct)) {
4463  context_->UpdateConstraintVariableUsage(c);
4464  }
4465  if (PropagateDomainsInLinear(c, ct)) {
4466  context_->UpdateConstraintVariableUsage(c);
4467  }
4468  if (PresolveSmallLinear(ct)) {
4469  context_->UpdateConstraintVariableUsage(c);
4470  }
4471  // We first propagate the domains before calling this presolve rule.
4472  if (RemoveSingletonInLinear(ct)) {
4473  context_->UpdateConstraintVariableUsage(c);
4474 
4475  // There is no need to re-do a propagation here, but the constraint
4476  // size might have been reduced.
4477  if (PresolveSmallLinear(ct)) {
4478  context_->UpdateConstraintVariableUsage(c);
4479  }
4480  }
4481  if (PresolveLinearOnBooleans(ct)) {
4482  context_->UpdateConstraintVariableUsage(c);
4483  }
4484  if (ct->constraint_case() == ConstraintProto::ConstraintCase::kLinear) {
4485  const int old_num_enforcement_literals = ct->enforcement_literal_size();
4486  ExtractEnforcementLiteralFromLinearConstraint(c, ct);
4487  if (ct->constraint_case() ==
4488  ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
4489  context_->UpdateConstraintVariableUsage(c);
4490  return true;
4491  }
4492  if (ct->enforcement_literal_size() > old_num_enforcement_literals &&
4493  PresolveSmallLinear(ct)) {
4494  context_->UpdateConstraintVariableUsage(c);
4495  }
4496  PresolveLinearEqualityModuloTwo(ct);
4497  }
4498  return false;
4499  }
4500  case ConstraintProto::ConstraintCase::kInterval:
4501  return PresolveInterval(c, ct);
4502  case ConstraintProto::ConstraintCase::kElement:
4503  return PresolveElement(ct);
4504  case ConstraintProto::ConstraintCase::kTable:
4505  return PresolveTable(ct);
4506  case ConstraintProto::ConstraintCase::kAllDiff:
4507  return PresolveAllDiff(ct);
4508  case ConstraintProto::ConstraintCase::kNoOverlap:
4509  return PresolveNoOverlap(ct);
4510  case ConstraintProto::ConstraintCase::kCumulative:
4511  return PresolveCumulative(ct);
4512  case ConstraintProto::ConstraintCase::kCircuit:
4513  return PresolveCircuit(ct);
4514  case ConstraintProto::ConstraintCase::kRoutes:
4515  return PresolveRoutes(ct);
4516  case ConstraintProto::ConstraintCase::kAutomaton:
4517  return PresolveAutomaton(ct);
4518  case ConstraintProto::ConstraintCase::kReservoir:
4519  return PresolveReservoir(ct);
4520  default:
4521  return false;
4522  }
4523 }
4524 
4525 // We deal with all the all 3 x 3 possible types of inclusion.
4526 //
4527 // Returns false iff the model is UNSAT.
4528 bool CpModelPresolver::ProcessSetPPCSubset(
4529  int c1, int c2, const std::vector<int>& c2_minus_c1,
4530  const std::vector<int>& original_constraint_index,
4531  std::vector<bool>* removed) {
4532  if (context_->ModelIsUnsat()) return false;
4533 
4534  CHECK(!(*removed)[c1]);
4535  CHECK(!(*removed)[c2]);
4536 
4537  ConstraintProto* ct1 = context_->working_model->mutable_constraints(
4538  original_constraint_index[c1]);
4539  ConstraintProto* ct2 = context_->working_model->mutable_constraints(
4540  original_constraint_index[c2]);
4541 
4542  if ((ct1->constraint_case() == ConstraintProto::kBoolOr ||
4543  ct1->constraint_case() == ConstraintProto::kExactlyOne) &&
4544  (ct2->constraint_case() == ConstraintProto::kAtMostOne ||
4545  ct2->constraint_case() == ConstraintProto::kExactlyOne)) {
4546  context_->UpdateRuleStats("setppc: bool_or in at_most_one.");
4547 
4548  // Fix extras in c2 to 0, note that these will be removed from the
4549  // constraint later.
4550  for (const int literal : c2_minus_c1) {
4551  if (!context_->SetLiteralToFalse(literal)) return false;
4552  context_->UpdateRuleStats("setppc: fixed variables");
4553  }
4554 
4555  // Change c2 to exactly_one if not already.
4556  if (ct2->constraint_case() != ConstraintProto::kExactlyOne) {
4557  ConstraintProto copy = *ct2;
4558  (*ct2->mutable_exactly_one()->mutable_literals()) =
4559  copy.at_most_one().literals();
4560  }
4561 
4562  // Remove c1.
4563  (*removed)[c1] = true;
4564  ct1->Clear();
4565  context_->UpdateConstraintVariableUsage(original_constraint_index[c1]);
4566  return true;
4567  }
4568 
4569  if ((ct1->constraint_case() == ConstraintProto::kBoolOr ||
4570  ct1->constraint_case() == ConstraintProto::kExactlyOne) &&
4571  ct2->constraint_case() == ConstraintProto::kBoolOr) {
4572  context_->UpdateRuleStats("setppc: removed dominated constraints");
4573 
4574  (*removed)[c2] = true;
4575  ct2->Clear();
4576  context_->UpdateConstraintVariableUsage(original_constraint_index[c2]);
4577  return true;
4578  }
4579 
4580  if (ct1->constraint_case() == ConstraintProto::kAtMostOne &&
4581  (ct2->constraint_case() == ConstraintProto::kAtMostOne ||
4582  ct2->constraint_case() == ConstraintProto::kExactlyOne)) {
4583  context_->UpdateRuleStats("setppc: removed dominated constraints");
4584  (*removed)[c1] = true;
4585  ct1->Clear();
4586  context_->UpdateConstraintVariableUsage(original_constraint_index[c1]);
4587  return true;
4588  }
4589 
4590  // We can't deduce anything in the last remaining case:
4591  // ct1->constraint_case() == ConstraintProto::kAtMostOne &&
4592  // ct2->constraint_case() == ConstraintProto::kBoolOr
4593 
4594  return true;
4595 }
4596 
4597 // TODO(user,user): TransformIntoMaxCliques() convert the bool_and to
4598 // at_most_one, but maybe also duplicating them into bool_or would allow this
4599 // function to do more presolving.
4600 bool CpModelPresolver::ProcessSetPPC() {
4601  const int num_constraints = context_->working_model->constraints_size();
4602 
4603  // Signatures of all the constraints. In the signature the bit i is 1 if it
4604  // contains a literal l such that l%64 = i.
4605  std::vector<uint64> signatures;
4606 
4607  // Graph of constraints to literals. constraint_literals[c] contains all the
4608  // literals in constraint indexed by 'c' in sorted order.
4609  std::vector<std::vector<int>> constraint_literals;
4610 
4611  // Graph of literals to constraints. literals_to_constraints[l] contains the
4612  // vector of constraint indices in which literal 'l' or 'neg(l)' appears.
4613  std::vector<std::vector<int>> literals_to_constraints;
4614 
4615  // vector of booleans indicating if the constraint was already removed.
4616  std::vector<bool> removed;
4617 
4618  // The containers above use the local indices for setppc constraints. We store
4619  // the original constraint indices corresponding to those local indices here.
4620  std::vector<int> original_constraint_index;
4621 
4622  // Fill the initial constraint <-> literal graph, compute signatures and
4623  // initialize other containers defined above.
4624  int num_setppc_constraints = 0;
4625  for (int c = 0; c < num_constraints; ++c) {
4626  ConstraintProto* ct = context_->working_model->mutable_constraints(c);
4627  if (ct->constraint_case() == ConstraintProto::ConstraintCase::kBoolOr ||
4628  ct->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne ||
4629  ct->constraint_case() == ConstraintProto::ConstraintCase::kExactlyOne) {
4630  // Because TransformIntoMaxCliques() can detect literal equivalence
4631  // relation, we make sure the constraints are presolved before being
4632  // inspected.
4633  if (PresolveOneConstraint(c)) {
4634  context_->UpdateConstraintVariableUsage(c);
4635  }
4636  if (context_->ModelIsUnsat()) return false;
4637  }
4638  if (ct->constraint_case() == ConstraintProto::ConstraintCase::kBoolOr ||
4639  ct->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne ||
4640  ct->constraint_case() == ConstraintProto::ConstraintCase::kExactlyOne) {
4641  constraint_literals.push_back(GetLiteralsFromSetPPCConstraint(*ct));
4642 
4643  uint64 signature = 0;
4644  for (const int literal : constraint_literals.back()) {
4645  const int positive_literal = PositiveRef(literal);
4646  signature |= (int64{1} << (positive_literal % 64));
4647  DCHECK_GE(positive_literal, 0);
4648  if (positive_literal >= literals_to_constraints.size()) {
4649  literals_to_constraints.resize(positive_literal + 1);
4650  }
4651  literals_to_constraints[positive_literal].push_back(
4652  num_setppc_constraints);
4653  }
4654  signatures.push_back(signature);
4655  removed.push_back(false);
4656  original_constraint_index.push_back(c);
4657  num_setppc_constraints++;
4658  }
4659  }
4660  VLOG(1) << "#setppc constraints: " << num_setppc_constraints;
4661 
4662  // Set of constraint pairs which are already compared.
4663  absl::flat_hash_set<std::pair<int, int>> compared_constraints;
4664  for (const std::vector<int>& literal_to_constraints :
4665  literals_to_constraints) {
4666  for (int index1 = 0; index1 < literal_to_constraints.size(); ++index1) {
4667  if (context_->time_limit()->LimitReached()) return true;
4668 
4669  const int c1 = literal_to_constraints[index1];
4670  if (removed[c1]) continue;
4671  const std::vector<int>& c1_literals = constraint_literals[c1];
4672 
4673  for (int index2 = index1 + 1; index2 < literal_to_constraints.size();
4674  ++index2) {
4675  const int c2 = literal_to_constraints[index2];
4676  if (removed[c2]) continue;
4677  if (removed[c1]) break;
4678 
4679  // TODO(user): This should not happen. Investigate.
4680  if (c1 == c2) continue;
4681 
4682  CHECK_LT(c1, c2);
4683  if (gtl::ContainsKey(compared_constraints,
4684  std::pair<int, int>(c1, c2))) {
4685  continue;
4686  }
4687  compared_constraints.insert({c1, c2});
4688 
4689  // Hard limit on number of comparisons to avoid spending too much time
4690  // here.
4691  if (compared_constraints.size() >= 50000) return true;
4692 
4693  const bool smaller = (signatures[c1] & ~signatures[c2]) == 0;
4694  const bool larger = (signatures[c2] & ~signatures[c1]) == 0;
4695  if (!(smaller || larger)) continue;
4696 
4697  // Check if literals in c1 is subset of literals in c2 or vice versa.
4698  const std::vector<int>& c2_literals = constraint_literals[c2];
4699 
4700  // TODO(user): Try avoiding computation of set differences if
4701  // possible.
4702  std::vector<int> c1_minus_c2;
4703  gtl::STLSetDifference(c1_literals, c2_literals, &c1_minus_c2);
4704  std::vector<int> c2_minus_c1;
4705  gtl::STLSetDifference(c2_literals, c1_literals, &c2_minus_c1);
4706 
4707  if (c1_minus_c2.empty()) { // c1 included in c2.
4708  if (!ProcessSetPPCSubset(c1, c2, c2_minus_c1,
4709  original_constraint_index, &removed)) {
4710  return false;
4711  }
4712  } else if (c2_minus_c1.empty()) { // c2 included in c1.
4713  if (!ProcessSetPPCSubset(c2, c1, c1_minus_c2,
4714  original_constraint_index, &removed)) {
4715  return false;
4716  }
4717  }
4718  }
4719  }
4720  }
4721 
4722  return true;
4723 }
4724 
4725 void CpModelPresolver::TryToSimplifyDomain(int var) {
4728  if (context_->ModelIsUnsat()) return;
4729  if (context_->IsFixed(var)) return;
4730  if (context_->VariableIsNotUsedAnymore(var)) return;
4731 
4732  const AffineRelation::Relation r = context_->GetAffineRelation(var);
4733  if (r.representative != var) return;
4734 
4735  if (context_->VariableIsOnlyUsedInEncoding(var)) {
4736  // TODO(user): We can remove such variable and its constraints by:
4737  // - Adding proper constraints on the enforcement literals. Simple case is
4738  // exactly one (or at most one) in case of a full (or partial) encoding.
4739  // - Moving all the old constraints to the mapping model.
4740  // - Updating the search heuristics/hint properly.
4741  context_->UpdateRuleStats("TODO variables: only used in encoding.");
4742  }
4743 
4744  // Only process discrete domain.
4745  const Domain& domain = context_->DomainOf(var);
4746 
4747  // Special case for non-Boolean domain of size 2.
4748  if (domain.Size() == 2 && (domain.Min() != 0 || domain.Max() != 1)) {
4749  context_->CanonicalizeDomainOfSizeTwo(var);
4750  return;
4751  }
4752 
4753  if (domain.NumIntervals() != domain.Size()) return;
4754 
4755  const int64 var_min = domain.Min();
4756  int64 gcd = domain[1].start - var_min;
4757  for (int index = 2; index < domain.NumIntervals(); ++index) {
4758  const ClosedInterval& i = domain[index];
4759  CHECK_EQ(i.start, i.end);
4760  const int64 shifted_value = i.start - var_min;
4761  CHECK_GE(shifted_value, 0);
4762 
4763  gcd = MathUtil::GCD64(gcd, shifted_value);
4764  if (gcd == 1) break;
4765  }
4766  if (gcd == 1) return;
4767 
4768  int new_var_index;
4769  {
4770  std::vector<int64> scaled_values;
4771  for (int index = 0; index < domain.NumIntervals(); ++index) {
4772  const ClosedInterval& i = domain[index];
4773  CHECK_EQ(i.start, i.end);
4774  const int64 shifted_value = i.start - var_min;
4775  scaled_values.push_back(shifted_value / gcd);
4776  }
4777  new_var_index = context_->NewIntVar(Domain::FromValues(scaled_values));
4778  }
4779  if (context_->ModelIsUnsat()) return;
4780 
4781  CHECK(context_->StoreAffineRelation(var, new_var_index, gcd, var_min));
4782  context_->UpdateRuleStats("variables: canonicalize affine domain");
4784 }
4785 
4786 // Adds all affine relations to our model for the variables that are still used.
4787 void CpModelPresolver::EncodeAllAffineRelations() {
4788  int64 num_added = 0;
4789  for (int var = 0; var < context_->working_model->variables_size(); ++var) {
4790  if (context_->IsFixed(var)) continue;
4791 
4792  const AffineRelation::Relation r = context_->GetAffineRelation(var);
4793  if (r.representative == var) continue;
4794 
4795  if (!context_->keep_all_feasible_solutions) {
4796  // TODO(user): It seems some affine relation are still removable at this
4797  // stage even though they should be removed inside PresolveToFixPoint().
4798  // Investigate. For now, we just remove such relations.
4799  if (context_->VariableIsNotUsedAnymore(var)) continue;
4800  if (!PresolveAffineRelationIfAny(var)) break;
4801  if (context_->VariableIsNotUsedAnymore(var)) continue;
4802  if (context_->IsFixed(var)) continue;
4803  }
4804 
4805  ++num_added;
4806  ConstraintProto* ct = context_->working_model->add_constraints();
4807  auto* arg = ct->mutable_linear();
4808  arg->add_vars(var);
4809  arg->add_coeffs(1);
4810  arg->add_vars(r.representative);
4811  arg->add_coeffs(-r.coeff);
4812  arg->add_domain(r.offset);
4813  arg->add_domain(r.offset);
4815  }
4816 
4817  // Now that we encoded all remaining affine relation with constraints, we
4818  // remove the special marker to have a proper constraint variable graph.
4820 
4821  if (context_->log_info() && num_added > 0) {
4822  LOG(INFO) << num_added << " affine relations still in the model.";
4823  }
4824 }
4825 
4826 // Presolve a variable in relation with its representative.
4827 bool CpModelPresolver::PresolveAffineRelationIfAny(int var) {
4828  if (context_->VariableIsNotUsedAnymore(var)) return true;
4829 
4830  const AffineRelation::Relation r = context_->GetAffineRelation(var);
4831  if (r.representative == var) return true;
4832 
4833  // Propagate domains.
4834  if (!context_->PropagateAffineRelation(var)) return false;
4835 
4836  // Once an affine relation is detected, the variables should be added to
4837  // the kAffineRelationConstraint. The only way to be unmarked is if the
4838  // variable do not appear in any other constraint and is not a representative,
4839  // in which case it should never be added back.
4840  if (context_->IsFixed(var)) return true;
4841  CHECK(context_->VarToConstraints(var).contains(kAffineRelationConstraint));
4842  CHECK(!context_->VariableIsNotUsedAnymore(r.representative));
4843 
4844  // If var is no longer used, remove. Note that we can always do that since we
4845  // propagated the domain above and so we can find a feasible value for a for
4846  // any value of the representative.
4847  if (context_->VariableIsUniqueAndRemovable(var)) {
4848  // Add relation with current representative to the mapping model.
4849  ConstraintProto* ct = context_->mapping_model->add_constraints();
4850  auto* arg = ct->mutable_linear();
4851  arg->add_vars(var);
4852  arg->add_coeffs(1);
4853  arg->add_vars(r.representative);
4854  arg->add_coeffs(-r.coeff);
4855  arg->add_domain(r.offset);
4856  arg->add_domain(r.offset);
4858  }
4859  return true;
4860 }
4861 
4862 void CpModelPresolver::PresolveToFixPoint() {
4863  if (context_->ModelIsUnsat()) return;
4864 
4865  // Limit on number of operations.
4866  const int64 max_num_operations =
4867  context_->params().cp_model_max_num_presolve_operations() > 0
4868  ? context_->params().cp_model_max_num_presolve_operations()
4869  : kint64max;
4870 
4871  // This is used for constraint having unique variables in them (i.e. not
4872  // appearing anywhere else) to not call the presolve more than once for this
4873  // reason.
4874  absl::flat_hash_set<std::pair<int, int>> var_constraint_pair_already_called;
4875 
4876  TimeLimit* time_limit = context_->time_limit();
4877 
4878  // The queue of "active" constraints, initialized to the non-empty ones.
4879  std::vector<bool> in_queue(context_->working_model->constraints_size(),
4880  false);
4881  std::deque<int> queue;
4882  for (int c = 0; c < in_queue.size(); ++c) {
4883  if (context_->working_model->constraints(c).constraint_case() !=
4884  ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
4885  in_queue[c] = true;
4886  queue.push_back(c);
4887  }
4888  }
4889 
4890  // When thinking about how the presolve works, it seems like a good idea to
4891  // process the "simple" constraints first in order to be more efficient.
4892  // In September 2019, experiment on the flatzinc problems shows no changes in
4893  // the results. We should actually count the number of rules triggered.
4894  if (context_->params().permute_presolve_constraint_order()) {
4895  std::shuffle(queue.begin(), queue.end(), *context_->random());
4896  } else {
4897  std::sort(queue.begin(), queue.end(), [this](int a, int b) {
4898  const int score_a = context_->ConstraintToVars(a).size();
4899  const int score_b = context_->ConstraintToVars(b).size();
4900  return score_a < score_b || (score_a == score_b && a < b);
4901  });
4902  }
4903 
4904  while (!queue.empty() && !context_->ModelIsUnsat()) {
4905  if (time_limit->LimitReached()) break;
4906  if (context_->num_presolve_operations > max_num_operations) break;
4907  while (!queue.empty() && !context_->ModelIsUnsat()) {
4908  if (time_limit->LimitReached()) break;
4909  if (context_->num_presolve_operations > max_num_operations) break;
4910  const int c = queue.front();
4911  in_queue[c] = false;
4912  queue.pop_front();
4913 
4914  const int old_num_constraint =
4915  context_->working_model->constraints_size();
4916  const bool changed = PresolveOneConstraint(c);
4917  if (context_->ModelIsUnsat() && context_->log_info()) {
4918  LOG(INFO) << "Unsat after presolving constraint #" << c
4919  << " (warning, dump might be inconsistent): "
4920  << context_->working_model->constraints(c).ShortDebugString();
4921  }
4922 
4923  // Add to the queue any newly created constraints.
4924  const int new_num_constraints =
4925  context_->working_model->constraints_size();
4926  if (new_num_constraints > old_num_constraint) {
4928  in_queue.resize(new_num_constraints, true);
4929  for (int c = old_num_constraint; c < new_num_constraints; ++c) {
4930  queue.push_back(c);
4931  }
4932  }
4933 
4934  // TODO(user): Is seems safer to simply remove the changed Boolean.
4935  // We loose a bit of preformance, but the code is simpler.
4936  if (changed) {
4937  context_->UpdateConstraintVariableUsage(c);
4938  }
4939  }
4940 
4941  // We also make sure all affine relations are propagated and any not
4942  // yet canonicalized domain is.
4943  //
4944  // TODO(user): maybe we can avoid iterating over all variables, but then
4945  // we already do that below.
4946  const int current_num_variables = context_->working_model->variables_size();
4947  for (int v = 0; v < current_num_variables; ++v) {
4948  if (context_->ModelIsUnsat()) return;
4949  if (!PresolveAffineRelationIfAny(v)) return;
4950 
4951  // Try to canonicalize the domain, note that we should have detected all
4952  // affine relations before, so we don't recreate "canononical" variables
4953  // if they already exist in the model.
4954  TryToSimplifyDomain(v);
4956  }
4957 
4958  // Re-add to the queue the constraints that touch a variable that changed.
4959  //
4960  // TODO(user): Avoid reprocessing the constraints that changed the variables
4961  // with the use of timestamp.
4962  if (context_->ModelIsUnsat()) return;
4963  in_queue.resize(context_->working_model->constraints_size(), false);
4964  for (const int v : context_->modified_domains.PositionsSetAtLeastOnce()) {
4965  if (context_->VariableIsNotUsedAnymore(v)) continue;
4966  if (context_->IsFixed(v)) context_->ExploitFixedDomain(v);
4967  for (const int c : context_->VarToConstraints(v)) {
4968  if (c >= 0 && !in_queue[c]) {
4969  in_queue[c] = true;
4970  queue.push_back(c);
4971  }
4972  }
4973  }
4974 
4975  // Re-add to the queue constraints that have unique variables. Note that to
4976  // not enter an infinite loop, we call each (var, constraint) pair at most
4977  // once.
4978  const int num_vars = context_->working_model->variables_size();
4979  for (int v = 0; v < num_vars; ++v) {
4980  const auto& constraints = context_->VarToConstraints(v);
4981  if (constraints.size() != 1) continue;
4982  const int c = *constraints.begin();
4983  if (c < 0) continue;
4984 
4985  // Note that to avoid bad complexity in problem like a TSP with just one
4986  // big constraint. we mark all the singleton variables of a constraint
4987  // even if this constraint is already in the queue.
4988  if (gtl::ContainsKey(var_constraint_pair_already_called,
4989  std::pair<int, int>(v, c))) {
4990  continue;
4991  }
4992  var_constraint_pair_already_called.insert({v, c});
4993 
4994  if (!in_queue[c]) {
4995  in_queue[c] = true;
4996  queue.push_back(c);
4997  }
4998  }
4999 
5000  // Make sure the order is deterministic! because var_to_constraints[]
5001  // order changes from one run to the next.
5002  std::sort(queue.begin(), queue.end());
5003  context_->modified_domains.SparseClearAll();
5004  }
5005 
5006  if (context_->ModelIsUnsat()) return;
5007 
5008  // Detect & exploit dominance between variables, or variables that can move
5009  // freely in one direction. Or variables that are just blocked by one
5010  // constraint in one direction.
5011  //
5012  // TODO(user): We can support assumptions but we need to not cut them out of
5013  // the feasible region.
5014  if (!context_->keep_all_feasible_solutions &&
5015  context_->working_model->assumptions().empty()) {
5016  VarDomination var_dom;
5017  DualBoundStrengthening dual_bound_strengthening;
5018  DetectDominanceRelations(*context_, &var_dom, &dual_bound_strengthening);
5019  if (!dual_bound_strengthening.Strengthen(context_)) return;
5020 
5021  // TODO(user): The Strengthen() function above might make some inequality
5022  // tight. Currently, because we only do that for implication, this will not
5023  // change who dominate who, but in general we should process the new
5024  // constraint direction before calling this.
5025  if (!ExploitDominanceRelations(var_dom, context_)) return;
5026  }
5027 
5028  // Second "pass" for transformation better done after all of the above and
5029  // that do not need a fix-point loop.
5030  //
5031  // TODO(user): Also add deductions achieved during probing!
5032  //
5033  // TODO(user): ideally we should "wake-up" any constraint that contains an
5034  // absent interval in the main propagation loop above. But we currently don't
5035  // maintain such list.
5036  const int num_constraints = context_->working_model->constraints_size();
5037  for (int c = 0; c < num_constraints; ++c) {
5038  ConstraintProto* ct = context_->working_model->mutable_constraints(c);
5039  switch (ct->constraint_case()) {
5040  case ConstraintProto::ConstraintCase::kNoOverlap:
5041  // Filter out absent intervals.
5042  if (PresolveNoOverlap(ct)) {
5043  context_->UpdateConstraintVariableUsage(c);
5044  }
5045  break;
5046  case ConstraintProto::ConstraintCase::kNoOverlap2D:
5047  // TODO(user): Implement if we ever support optional intervals in
5048  // this constraint. Currently we do not.
5049  break;
5050  case ConstraintProto::ConstraintCase::kCumulative:
5051  // Filter out absent intervals.
5052  if (PresolveCumulative(ct)) {
5053  context_->UpdateConstraintVariableUsage(c);
5054  }
5055  break;
5056  case ConstraintProto::ConstraintCase::kBoolOr: {
5057  // Try to infer domain reductions from clauses and the saved "implies in
5058  // domain" relations.
5059  for (const auto& pair :
5060  context_->deductions.ProcessClause(ct->bool_or().literals())) {
5061  bool modified = false;
5062  if (!context_->IntersectDomainWith(pair.first, pair.second,
5063  &modified)) {
5064  return;
5065  }
5066  if (modified) {
5067  context_->UpdateRuleStats("deductions: reduced variable domain");
5068  }
5069  }
5070  break;
5071  }
5072  default:
5073  break;
5074  }
5075  }
5076 
5078 }
5079 
5081  LOG(INFO) << "- " << context->NumAffineRelations()
5082  << " affine relations were detected.";
5083  LOG(INFO) << "- " << context->NumEquivRelations()
5084  << " variable equivalence relations were detected.";
5085  std::map<std::string, int> sorted_rules(context->stats_by_rule_name.begin(),
5086  context->stats_by_rule_name.end());
5087  for (const auto& entry : sorted_rules) {
5088  if (entry.second == 1) {
5089  LOG(INFO) << "- rule '" << entry.first << "' was applied 1 time.";
5090  } else {
5091  LOG(INFO) << "- rule '" << entry.first << "' was applied " << entry.second
5092  << " times.";
5093  }
5094  }
5095 }
5096 
5097 // =============================================================================
5098 // Public API.
5099 // =============================================================================
5100 
5102  std::vector<int>* postsolve_mapping) {
5103  CpModelPresolver presolver(context, postsolve_mapping);
5104  return presolver.Presolve();
5105 }
5106 
5108  std::vector<int>* postsolve_mapping)
5109  : postsolve_mapping_(postsolve_mapping), context_(context) {
5110  // TODO(user): move in the context.
5111  context_->keep_all_feasible_solutions =
5112  context_->params().keep_all_feasible_solutions_in_presolve() ||
5113  context_->params().enumerate_all_solutions() ||
5114  context_->params().fill_tightened_domains_in_response() ||
5115  !context_->params().cp_model_presolve();
5116 
5117  // We copy the search strategy to the mapping_model.
5118  for (const auto& decision_strategy :
5119  context_->working_model->search_strategy()) {
5120  *(context_->mapping_model->add_search_strategy()) = decision_strategy;
5121  }
5122 
5123  // Initialize the initial context.working_model domains.
5124  context_->InitializeNewDomains();
5125 
5126  // Initialize the objective.
5127  context_->ReadObjectiveFromProto();
5128  if (!context_->CanonicalizeObjective()) {
5129  (void)context_->NotifyThatModelIsUnsat();
5130  }
5131 
5132  // Note that we delay the call to UpdateNewConstraintsVariableUsage() for
5133  // efficiency during LNS presolve.
5134 }
5135 
5136 // The presolve works as follow:
5137 //
5138 // First stage:
5139 // We will process all active constraints until a fix point is reached. During
5140 // this stage:
5141 // - Variable will never be deleted, but their domain will be reduced.
5142 // - Constraint will never be deleted (they will be marked as empty if needed).
5143 // - New variables and new constraints can be added after the existing ones.
5144 // - Constraints are added only when needed to the mapping_problem if they are
5145 // needed during the postsolve.
5146 //
5147 // Second stage:
5148 // - All the variables domain will be copied to the mapping_model.
5149 // - Everything will be remapped so that only the variables appearing in some
5150 // constraints will be kept and their index will be in [0, num_new_variables).
5152  context_->enable_stats = context_->log_info();
5153 
5154  // If presolve is false, just run expansion.
5155  if (!context_->params().cp_model_presolve()) {
5157  ExpandCpModel(context_);
5158  if (context_->log_info()) LogInfoFromContext(context_);
5159  return true;
5160  }
5161 
5162  // Before initializing the constraint <-> variable graph (which is costly), we
5163  // run a bunch of simple presolve rules. Note that these function should NOT
5164  // use the graph, or the part that uses it should properly check for
5165  // context_->ConstraintVariableGraphIsUpToDate() before doing anything that
5166  // depends on the graph.
5167  for (int c = 0; c < context_->working_model->constraints_size(); ++c) {
5168  ConstraintProto* ct = context_->working_model->mutable_constraints(c);
5169  PresolveEnforcementLiteral(ct);
5170  switch (ct->constraint_case()) {
5171  case ConstraintProto::ConstraintCase::kBoolOr:
5172  PresolveBoolOr(ct);
5173  break;
5174  case ConstraintProto::ConstraintCase::kBoolAnd:
5175  PresolveBoolAnd(ct);
5176  break;
5177  case ConstraintProto::ConstraintCase::kAtMostOne:
5178  PresolveAtMostOne(ct);
5179  break;
5180  case ConstraintProto::ConstraintCase::kExactlyOne:
5181  PresolveExactlyOne(ct);
5182  break;
5183  case ConstraintProto::ConstraintCase::kLinear:
5184  CanonicalizeLinear(ct);
5185  break;
5186  default:
5187  break;
5188  }
5189  if (context_->ModelIsUnsat()) break;
5190  }
5194 
5195  // Main propagation loop.
5196  for (int iter = 0; iter < context_->params().max_presolve_iterations();
5197  ++iter) {
5198  context_->UpdateRuleStats("presolve: iteration");
5199  // Save some quantities to decide if we abort early the iteration loop.
5200  const int64 old_num_presolve_op = context_->num_presolve_operations;
5201  int old_num_non_empty_constraints = 0;
5202  for (int c = 0; c < context_->working_model->constraints_size(); ++c) {
5203  const auto type =
5204  context_->working_model->constraints(c).constraint_case();
5205  if (type == ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) continue;
5206  old_num_non_empty_constraints++;
5207  }
5208 
5209  // TODO(user): The presolve transformations we do after this is called might
5210  // result in even more presolve if we were to call this again! improve the
5211  // code. See for instance plusexample_6_sat.fzn were represolving the
5212  // presolved problem reduces it even more.
5213  PresolveToFixPoint();
5214 
5215  // Call expansion.
5216  ExpandCpModel(context_);
5218 
5219  // TODO(user): do that and the pure-SAT part below more than once.
5220  if (context_->params().cp_model_probing_level() > 0) {
5221  if (!context_->time_limit()->LimitReached()) {
5222  Probe();
5223  PresolveToFixPoint();
5224  }
5225  }
5226 
5227  // Runs SAT specific presolve on the pure-SAT part of the problem.
5228  // Note that because this can only remove/fix variable not used in the other
5229  // part of the problem, there is no need to redo more presolve afterwards.
5230  if (context_->params().cp_model_use_sat_presolve()) {
5231  if (!context_->time_limit()->LimitReached()) {
5232  PresolvePureSatPart();
5233  }
5234  }
5235 
5236  // Extract redundant at most one constraint form the linear ones.
5237  //
5238  // TODO(user): more generally if we do some probing, the same relation will
5239  // be detected (and more). Also add an option to turn this off?
5240  //
5241  // TODO(user): instead of extracting at most one, extract pairwise conflicts
5242  // and add them to bool_and clauses? this is some sort of small scale
5243  // probing, but good for sat presolve and clique later?
5244  if (!context_->ModelIsUnsat() && iter == 0) {
5245  const int old_size = context_->working_model->constraints_size();
5246  for (int c = 0; c < old_size; ++c) {
5247  ConstraintProto* ct = context_->working_model->mutable_constraints(c);
5248  if (ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear) {
5249  continue;
5250  }
5251  ExtractAtMostOneFromLinear(ct);
5252  }
5254  }
5255 
5256  if (iter == 0) TransformIntoMaxCliques();
5257 
5258  // TODO(user): Decide where is the best place for this. Fow now we do it
5259  // after max clique to get all the bool_and converted to at most ones.
5260  if (context_->params().symmetry_level() > 0 && !context_->ModelIsUnsat() &&
5261  !context_->time_limit()->LimitReached() &&
5262  !context_->keep_all_feasible_solutions) {
5264  }
5265 
5266  // Process set packing, partitioning and covering constraint.
5267  if (!context_->time_limit()->LimitReached()) {
5268  ProcessSetPPC();
5269  ExtractBoolAnd();
5270 
5271  // Call the main presolve to remove the fixed variables and do more
5272  // deductions.
5273  PresolveToFixPoint();
5274  }
5275 
5276  // Exit the loop if the reduction is not so large.
5277  if (context_->num_presolve_operations - old_num_presolve_op <
5278  0.8 * (context_->working_model->variables_size() +
5279  old_num_non_empty_constraints)) {
5280  break;
5281  }
5282  }
5283 
5284  // Regroup no-overlaps into max-cliques.
5285  if (!context_->ModelIsUnsat()) {
5286  MergeNoOverlapConstraints();
5287  }
5288 
5289  // Tries to spread the objective amongst many variables.
5290  if (context_->working_model->has_objective() && !context_->ModelIsUnsat()) {
5291  ExpandObjective();
5292  }
5293 
5294  // Adds all needed affine relation to context_->working_model.
5295  if (!context_->ModelIsUnsat()) {
5296  EncodeAllAffineRelations();
5297  }
5298 
5299  // Remove duplicate constraints.
5300  //
5301  // TODO(user): We might want to do that earlier so that our count of variable
5302  // usage is not biased by duplicate constraints.
5303  const std::vector<int> duplicates =
5305  if (!duplicates.empty()) {
5306  for (const int c : duplicates) {
5307  const int type =
5308  context_->working_model->constraints(c).constraint_case();
5309  if (type == ConstraintProto::ConstraintCase::kInterval) {
5310  // TODO(user): we could delete duplicate identical interval, but we need
5311  // to make sure reference to them are updated.
5312  continue;
5313  }
5314 
5315  context_->working_model->mutable_constraints(c)->Clear();
5316  context_->UpdateConstraintVariableUsage(c);
5317  context_->UpdateRuleStats("removed duplicate constraints");
5318  }
5319  }
5320 
5321  if (context_->ModelIsUnsat()) {
5322  if (context_->log_info()) LogInfoFromContext(context_);
5323 
5324  // Set presolved_model to the simplest UNSAT problem (empty clause).
5325  context_->working_model->Clear();
5326  context_->working_model->add_constraints()->mutable_bool_or();
5327  return true;
5328  }
5329 
5330  // The strategy variable indices will be remapped in ApplyVariableMapping()
5331  // but first we use the representative of the affine relations for the
5332  // variables that are not present anymore.
5333  //
5334  // Note that we properly take into account the sign of the coefficient which
5335  // will result in the same domain reduction strategy. Moreover, if the
5336  // variable order is not CHOOSE_FIRST, then we also encode the associated
5337  // affine transformation in order to preserve the order.
5338  absl::flat_hash_set<int> used_variables;
5339  for (DecisionStrategyProto& strategy :
5340  *context_->working_model->mutable_search_strategy()) {
5341  DecisionStrategyProto copy = strategy;
5342  strategy.clear_variables();
5343  for (const int ref : copy.variables()) {
5344  const int var = PositiveRef(ref);
5345 
5346  // Remove fixed variables.
5347  if (context_->IsFixed(var)) continue;
5348 
5349  // There is not point having a variable appear twice, so we only keep
5350  // the first occurrence in the first strategy in which it occurs.
5351  if (gtl::ContainsKey(used_variables, var)) continue;
5352  used_variables.insert(var);
5353 
5354  if (context_->VarToConstraints(var).empty()) {
5355  const AffineRelation::Relation r = context_->GetAffineRelation(var);
5356  if (!context_->VarToConstraints(r.representative).empty()) {
5357  const int rep = (r.coeff > 0) == RefIsPositive(ref)
5358  ? r.representative
5360  strategy.add_variables(rep);
5361  if (strategy.variable_selection_strategy() !=
5362  DecisionStrategyProto::CHOOSE_FIRST) {
5363  DecisionStrategyProto::AffineTransformation* t =
5364  strategy.add_transformations();
5365  t->set_var(rep);
5366  t->set_offset(r.offset);
5367  t->set_positive_coeff(std::abs(r.coeff));
5368  }
5369  } else {
5370  // TODO(user): this variable was removed entirely by the presolve (no
5371  // equivalent variable present). We simply ignore it entirely which
5372  // might result in a different search...
5373  }
5374  } else {
5375  strategy.add_variables(ref);
5376  }
5377  }
5378  }
5379 
5380  // Sync the domains.
5381  for (int i = 0; i < context_->working_model->variables_size(); ++i) {
5382  FillDomainInProto(context_->DomainOf(i),
5383  context_->working_model->mutable_variables(i));
5384  DCHECK_GT(context_->working_model->variables(i).domain_size(), 0);
5385  }
5386 
5387  // Set the variables of the mapping_model.
5388  context_->mapping_model->mutable_variables()->CopyFrom(
5389  context_->working_model->variables());
5390 
5391  // Remove all the unused variables from the presolved model.
5392  postsolve_mapping_->clear();
5393  std::vector<int> mapping(context_->working_model->variables_size(), -1);
5394  for (int i = 0; i < context_->working_model->variables_size(); ++i) {
5395  if (context_->VariableIsNotUsedAnymore(i) &&
5396  !context_->keep_all_feasible_solutions) {
5397  continue;
5398  }
5399  mapping[i] = postsolve_mapping_->size();
5400  postsolve_mapping_->push_back(i);
5401  }
5402 
5403  if (context_->params().permute_variable_randomly()) {
5404  std::shuffle(postsolve_mapping_->begin(), postsolve_mapping_->end(),
5405  *context_->random());
5406  for (int i = 0; i < postsolve_mapping_->size(); ++i) {
5407  mapping[(*postsolve_mapping_)[i]] = i;
5408  }
5409  }
5410 
5412  ApplyVariableMapping(mapping, *context_);
5413 
5414  // Compact all non-empty constraint at the beginning.
5416 
5417  // Hack to display the number of deductions stored.
5418  if (context_->deductions.NumDeductions() > 0) {
5419  context_->UpdateRuleStats(absl::StrCat(
5420  "deductions: ", context_->deductions.NumDeductions(), " stored"));
5421  }
5422 
5423  // Stats and checks.
5424  if (context_->log_info()) LogInfoFromContext(context_);
5425 
5426  // One possible error that is difficult to avoid here: because of our
5427  // objective expansion, we might detect a possible overflow...
5428  //
5429  // TODO(user): We could abort the expansion when this happen.
5430  {
5431  const std::string error = ValidateCpModel(*context_->working_model);
5432  if (!error.empty()) {
5433  if (context_->log_info()) {
5434  LOG(INFO) << "Error while validating postsolved model: " << error;
5435  }
5436  return false;
5437  }
5438  }
5439  {
5440  const std::string error = ValidateCpModel(*context_->mapping_model);
5441  if (!error.empty()) {
5442  if (context_->log_info()) {
5443  LOG(INFO) << "Error while validating mapping_model model: " << error;
5444  }
5445  return false;
5446  }
5447  }
5448  return true;
5449 }
5450 
5451 void ApplyVariableMapping(const std::vector<int>& mapping,
5452  const PresolveContext& context) {
5453  CpModelProto* proto = context.working_model;
5454 
5455  // Remap all the variable/literal references in the constraints and the
5456  // enforcement literals in the variables.
5457  auto mapping_function = [&mapping](int* ref) {
5458  const int image = mapping[PositiveRef(*ref)];
5459  CHECK_GE(image, 0);
5460  *ref = RefIsPositive(*ref) ? image : NegatedRef(image);
5461  };
5462  for (ConstraintProto& ct_ref : *proto->mutable_constraints()) {
5463  ApplyToAllVariableIndices(mapping_function, &ct_ref);
5464  ApplyToAllLiteralIndices(mapping_function, &ct_ref);
5465  }
5466 
5467  // Remap the objective variables.
5468  if (proto->has_objective()) {
5469  for (int& mutable_ref : *proto->mutable_objective()->mutable_vars()) {
5470  mapping_function(&mutable_ref);
5471  }
5472  }
5473 
5474  // Remap the assumptions.
5475  for (int& mutable_ref : *proto->mutable_assumptions()) {
5476  mapping_function(&mutable_ref);
5477  }
5478 
5479  // Remap the search decision heuristic.
5480  // Note that we delete any heuristic related to a removed variable.
5481  for (DecisionStrategyProto& strategy : *proto->mutable_search_strategy()) {
5482  DecisionStrategyProto copy = strategy;
5483  strategy.clear_variables();
5484  for (const int ref : copy.variables()) {
5485  const int image = mapping[PositiveRef(ref)];
5486  if (image >= 0) {
5487  strategy.add_variables(RefIsPositive(ref) ? image : NegatedRef(image));
5488  }
5489  }
5490  strategy.clear_transformations();
5491  for (const auto& transform : copy.transformations()) {
5492  const int ref = transform.var();
5493  const int image = mapping[PositiveRef(ref)];
5494  if (image >= 0) {
5495  auto* new_transform = strategy.add_transformations();
5496  *new_transform = transform;
5497  new_transform->set_var(RefIsPositive(ref) ? image : NegatedRef(image));
5498  }
5499  }
5500  }
5501 
5502  // Remap the solution hint.
5503  if (proto->has_solution_hint()) {
5504  auto* mutable_hint = proto->mutable_solution_hint();
5505  int new_size = 0;
5506  for (int i = 0; i < mutable_hint->vars_size(); ++i) {
5507  const int old_ref = mutable_hint->vars(i);
5508  const int64 old_value = mutable_hint->values(i);
5509 
5510  // Note that if (old_value - r.offset) is not divisible by r.coeff, then
5511  // the hint is clearly infeasible, but we still set it to a "close" value.
5512  const AffineRelation::Relation r = context.GetAffineRelation(old_ref);
5513  const int var = r.representative;
5514  const int64 value = (old_value - r.offset) / r.coeff;
5515 
5516  const int image = mapping[var];
5517  if (image >= 0) {
5518  mutable_hint->set_vars(new_size, image);
5519  mutable_hint->set_values(new_size, value);
5520  ++new_size;
5521  }
5522  }
5523  if (new_size > 0) {
5524  mutable_hint->mutable_vars()->Truncate(new_size);
5525  mutable_hint->mutable_values()->Truncate(new_size);
5526  } else {
5527  proto->clear_solution_hint();
5528  }
5529  }
5530 
5531  // Move the variable definitions.
5532  std::vector<IntegerVariableProto> new_variables;
5533  for (int i = 0; i < mapping.size(); ++i) {
5534  const int image = mapping[i];
5535  if (image < 0) continue;
5536  if (image >= new_variables.size()) {
5537  new_variables.resize(image + 1, IntegerVariableProto());
5538  }
5539  new_variables[image].Swap(proto->mutable_variables(i));
5540  }
5541  proto->clear_variables();
5542  for (IntegerVariableProto& proto_ref : new_variables) {
5543  proto->add_variables()->Swap(&proto_ref);
5544  }
5545 
5546  // Check that all variables are used.
5547  for (const IntegerVariableProto& v : proto->variables()) {
5548  CHECK_GT(v.domain_size(), 0);
5549  }
5550 }
5551 
5552 std::vector<int> FindDuplicateConstraints(const CpModelProto& model_proto) {
5553  std::vector<int> result;
5554 
5555  // We use a map hash: serialized_constraint_proto hash -> constraint index.
5556  ConstraintProto copy;
5557  absl::flat_hash_map<int64, int> equiv_constraints;
5558 
5559  std::string s;
5560  const int num_constraints = model_proto.constraints().size();
5561  for (int c = 0; c < num_constraints; ++c) {
5562  if (model_proto.constraints(c).constraint_case() ==
5563  ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
5564  continue;
5565  }
5566 
5567  // We ignore names when comparing constraints.
5568  //
5569  // TODO(user): This is not particularly efficient.
5570  copy = model_proto.constraints(c);
5571  copy.clear_name();
5572  s = copy.SerializeAsString();
5573 
5574  const int64 hash = std::hash<std::string>()(s);
5575  const auto insert = equiv_constraints.insert({hash, c});
5576  if (!insert.second) {
5577  // Already present!
5578  const int other_c_with_same_hash = insert.first->second;
5579  copy = model_proto.constraints(other_c_with_same_hash);
5580  copy.clear_name();
5581  if (s == copy.SerializeAsString()) {
5582  result.push_back(c);
5583  }
5584  }
5585  }
5586 
5587  return result;
5588 }
5589 
5590 } // namespace sat
5591 } // namespace operations_research
int64 min
Definition: alldiff_cst.cc:138
int64 max
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:495
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:886
#define CHECK_LT(val1, val2)
Definition: base/logging.h:700
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:697
#define CHECK_GE(val1, val2)
Definition: base/logging.h:701
#define CHECK_GT(val1, val2)
Definition: base/logging.h:702
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:889
#define CHECK_NE(val1, val2)
Definition: base/logging.h:698
#define DCHECK_GT(val1, val2)
Definition: base/logging.h:890
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:888
#define LOG(severity)
Definition: base/logging.h:420
#define DCHECK(condition)
Definition: base/logging.h:884
#define CHECK_LE(val1, val2)
Definition: base/logging.h:699
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:885
#define VLOG(verboselevel)
Definition: base/logging.h:978
bool IsIncludedIn(const Domain &domain) const
Returns true iff D is included in the given domain.
Domain MultiplicationBy(int64 coeff, bool *exact=nullptr) const
Returns {x ∈ Int64, ∃ e ∈ D, x = e * coeff}.
static Domain FromValues(std::vector< int64 > values)
Creates a domain from the union of an unsorted list of integer values.
int64 Size() const
Returns the number of elements in the domain.
Domain InverseMultiplicationBy(const int64 coeff) const
Returns {x ∈ Int64, ∃ e ∈ D, x * coeff = e}.
Domain AdditionWith(const Domain &domain) const
Returns {x ∈ Int64, ∃ a ∈ D, ∃ b ∈ domain, x = a + b}.
ClosedInterval front() const
Domain UnionWith(const Domain &domain) const
Returns the union of D and domain.
Domain IntersectionWith(const Domain &domain) const
Returns the intersection of D and domain.
Domain RelaxIfTooComplex() const
If NumIntervals() is too large, this return a superset of the domain.
Domain DivisionBy(int64 coeff) const
Returns {x ∈ Int64, ∃ e ∈ D, x = e / coeff}.
static int64 GCD64(int64 x, int64 y)
Definition: mathutil.h:107
const std::vector< IntegerType > & PositionsSetAtLeastOnce() const
Definition: bitset.h:813
void Set(IntegerType index)
Definition: bitset.h:803
bool LimitReached()
Returns true when the external limit is true, or the deterministic time is over the deterministic lim...
Definition: time_limit.h:532
void AdvanceDeterministicTime(double deterministic_duration)
Advances the deterministic time.
Definition: time_limit.h:226
CpModelPresolver(PresolveContext *context, std::vector< int > *postsolve_mapping)
std::vector< std::pair< int, Domain > > ProcessClause(absl::Span< const int > clause)
void AddDeduction(int literal_ref, int var, Domain domain)
bool StoreAbsRelation(int target_ref, int ref)
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
int GetOrCreateVarValueEncoding(int ref, int64 value)
bool DomainContains(int ref, int64 value) const
bool StoreLiteralImpliesVarNEqValue(int literal, int var, int64 value)
bool DomainOfVarIsIncludedIn(int var, const Domain &domain)
bool VariableWithCostIsUniqueAndRemovable(int ref) const
ABSL_MUST_USE_RESULT bool SetLiteralToTrue(int lit)
bool StoreLiteralImpliesVarEqValue(int literal, int var, int64 value)
std::vector< absl::flat_hash_set< int > > var_to_ub_only_constraints
const std::vector< int > & ConstraintToVars(int c) const
ABSL_MUST_USE_RESULT bool NotifyThatModelIsUnsat(const std::string &message="")
void StoreBooleanEqualityRelation(int ref_a, int ref_b)
bool SubstituteVariableInObjective(int var_in_equality, int64 coeff_in_equality, const ConstraintProto &equality, std::vector< int > *new_vars_in_objective=nullptr)
const absl::flat_hash_map< int, int64 > & ObjectiveMap() const
void UpdateRuleStats(const std::string &name, int num_times=1)
ABSL_MUST_USE_RESULT bool CanonicalizeObjective()
const SatParameters & params() const
AffineRelation::Relation GetAffineRelation(int ref) const
const absl::flat_hash_set< int > & VarToConstraints(int var) const
ABSL_MUST_USE_RESULT bool SetLiteralToFalse(int lit)
absl::flat_hash_set< int > tmp_literal_set
bool GetAbsRelation(int target_ref, int *ref)
bool StoreAffineRelation(int ref_x, int ref_y, int64 coeff, int64 offset)
Block * next
CpModelProto proto
CpModelProto const * model_proto
SharedTimeLimit * time_limit
const std::string name
const Constraint * ct
int64 value
IntVar * var
Definition: expr_array.cc:1858
GRBmodel * model
GurobiMPCallbackContext * context
static const int64 kint64max
int64_t int64
static const int32 kint32min
uint64_t uint64
static const int64 kint64min
const int WARNING
Definition: log_severity.h:31
const int INFO
Definition: log_severity.h:31
int64 hash
Definition: matrix_utils.cc:60
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition: stl_util.h:58
void STLSetDifference(const In1 &a, const In2 &b, Out *out, Compare compare)
Definition: stl_util.h:595
bool ContainsKey(const Collection &collection, const Key &key)
Definition: map_util.h:170
const Collection::value_type::second_type & FindOrDie(const Collection &collection, const typename Collection::value_type::first_type &key)
Definition: map_util.h:176
bool DetectAndExploitSymmetriesInPresolve(PresolveContext *context)
bool LoadConstraint(const ConstraintProto &ct, Model *m)
bool RefIsPositive(int ref)
void SetToNegatedLinearExpression(const LinearExpressionProto &input_expr, LinearExpressionProto *output_negated_expr)
const LiteralIndex kNoLiteralIndex(-1)
bool HasEnforcementLiteral(const ConstraintProto &ct)
void ExpandCpModel(PresolveContext *context)
constexpr int kAffineRelationConstraint
bool PresolveCpModel(PresolveContext *context, std::vector< int > *postsolve_mapping)
void ApplyToAllLiteralIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
void SubstituteVariable(int var, int64 var_coeff_in_definition, const ConstraintProto &definition, ConstraintProto *ct)
void DetectDominanceRelations(const PresolveContext &context, VarDomination *var_domination, DualBoundStrengthening *dual_bound_strengthening)
void ApplyToAllIntervalIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
int ReindexArcs(IntContainer *tails, IntContainer *heads)
Definition: circuit.h:168
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
std::vector< int > FindDuplicateConstraints(const CpModelProto &model_proto)
void LogInfoFromContext(const PresolveContext *context)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
void ApplyToAllVariableIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
constexpr int kObjectiveConstraint
bool ExploitDominanceRelations(const VarDomination &var_domination, PresolveContext *context)
void ApplyVariableMapping(const std::vector< int > &mapping, const PresolveContext &context)
std::string ValidateCpModel(const CpModelProto &model)
The vehicle routing library lets one model and solve generic vehicle routing problems ranging from th...
Literal literal
Definition: optimization.cc:84
int index
Definition: pack.cc:508
if(!yyg->yy_init)
Definition: parser.yy.cc:965
ColIndex representative
int64 time
Definition: resource.cc:1683
int64 demand
Definition: resource.cc:123
IntervalVar * interval
Definition: resource.cc:98
int64 tail
int64 head
int64 capacity