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