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