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